Introduction

It's better to familiarize yourself with new technologies by creating a prototype using the basic architectural patterns. For complex applications, it is:

dividing into logical layers

user authorization

exception handling and auditing

resource localization

unit testing

and many others. But for the first time, it's quite enough.

The example presented in the article uses WCF RIA Services Class Library to group Business Logic components into a separate project. It turned out to be a not so trivial task, the more so because most examples in the WCF RIA Services Code Gallery do not use this library - nothing to crib :)

These samples were a good launch pad, but I needed a bit different code. First, I wanted to use my own database for user authorization. Besides, I decided to use LINQ to SQL for my Data Access Layer (DAL) and implement the Repository pattern to provide dependency-free access to my data and facilitate unit testing.

And finally, resource localization itself was not a challenging task, but required some handiwork.

This is what we shall see when the example is built:

Requisites

But before we build the application, I would like to enumerate what was used during my work with the project:

Repositories and unit testing

The Repository pattern is used to encapsulate data access methods in an abstract layer. That gives a possibility to create mock objects, implementing the IRepository interface, and using them in unit tests, not touching a real database.

I took the Testing Domain Services example and Vijay's article Unit Testing Business Logic in .NET RIA Services as a basis. The only thing left to do was to initialize repository classes, not using hard-coding. For that purpose, I used dependency injection implemented with the Microsoft Unity Application Block 1.2. I think that requires some explanation.

First of all, my services (AuthenticationService, UserRegistrationService) use the following properties, decorated with the Dependency attribute, for access to data repositories:

Coming back to the CreateDomainService() method, I would like to note that the UnityContainer resolves encountered dependencies using our instructions in the unity.config file. Particularly, the IRepository<UserAccount> interface is mapped to the LinqToSqlRepository<UserAccount> class.

But, if you look at the class, you will see that it can be instantiated using a public constructor with a parameter of type DataContext. The trick is that the UnityContainer already knows about it and will use AppDatabaseDataContext to initialize the LinqToSqlRepository<UserAccount> object (see the DependencyInjectionProvider.Configure() method). Thereby, our repositories are configured to use AppDatabaseDataContext generated from the database.

Now, we can test our services, replacing the repositories with mock objects. Open the BizApp.Services.Test project containing the unit tests. Each of them initializes testing services with mock repositories, implementing the IRepository<T> interface. A more detailed description can be found in Vijay's blog.

User authorization

This example uses Forms authentication mode, i.e., a user enters his or her login name and password and the program tries to identify the user browsing through the list of user accounts in the AppDatabase.mdf database. Authentication logic is implemented in the AuthenticationService class exposing the IAuthentication<User> interface. To enable authentication, we have to specify the domain context generated from our authentication domain service in the App.xaml file:

The main function of the service is the Login() method. And, the key point of the method is the statement:

FormsAuthentication.SetAuthCookie(userName, isPersistent);

MSDN states that it adds a forms-authentication ticket to either the cookies collection or the URL, and the ticket supplies forms-authentication information to the next request made by the browser. In other words, you do not need to make an effort to identify a user - WCF RIA Services does it for you. All that you need is stored in the ServiceContext.User.Identity property. Or, you can call the GetUser() method to get an authenticated user.

For the more inquisitive developer, I would recommend to install Fiddler and WCF Binary-encoded Message Inspector for Fiddler and try to capture HTTP traffic between the Silverlight application and your server. But, be aware of one drawback - Fiddler cannot capture local traffic (see troubleshooting). To get around this, just place a dot after localhost in the browser:

http://localhost.:3121/BizApp.aspx

Start Fiddler. Run the application, create a user account, and login. Choose an authentication request sent by the application in Fiddler. If you look at the server response, you will see authorization cookies like that:

The forms authentication ticket, stored in the cookies, is encrypted and signed using the machine's key. So, this information can be considered as secure. More detailed information about forms authentication cookies can be found here.

Exception handling

Sooner or later, you will have to design your exception-management strategy. WCF RIA Services provides a way to handle exceptions occurred on the server-side in the DomainService.OnError method. To perform exception logging, I inherited my services from the DomainServiceBase class with an overridden OnError method. It helps to catch exceptions at layer boundaries:

Localized resources

If you run the example, you will notice a drop-down list for language selection:

There are two places in the application containing some text that can be localized. First, the static text displayed on labels, buttons, checkboxes, and so on. Second - text resources used for data annotation on the Service Layer.

Localized text on the UI

To localize UI elements, we shall create resource files for the chosen languages:

Besides, we have to perform the following steps:

Set "Custom Tool" to PublicResXFileCodeGenerator for the default resource only. It is LocalizedStrings.resx in our case.

Set "Access Modifier" to Public for the default resource file

Specify SupportedCultures in the .csproj file.

Visual Studio does not provide a way to set SupportedCultures for a project. So, we have to edit our BizApp.csproj file manually. Open it in an editor, and add a tag with the languages you are going to use:

<SupportedCultures>de-de;es-es;fr-fr</SupportedCultures>

I created the ApplicationResources class containing the LocalizedStrings property. Then, I made it available for the whole application, adding it to application resources in the App.xaml file:

Messages are stored in resource files and can be localized. It's not a problem to create resource files - it's a problem to get it compiled if you use the WCF RIA Services Class Library. The BizApp.Services project contains some code, generated from classes defined in the BizApp.Services.Server project. But some of them can contain references to resources not existing in the BizApp.Services project. I'll show you how to overcome it.

First of all, resources in the BizApp.Services.Server project shall have the Public access modifier. Then, we have to create a folder Server\Resources in the BizApp.Services project. Note that the folder structure must match the resource file namespace in the BizApp.Services.Server project!

The next step is to add resources to BizApp.Services. Select the Server\Resources folder and bring up the Add Existing Item... dialog. Select the *Resources.resx and *Resources.Designer.cs files and add them as link files to the BizApp.Services project. Save the project and unload it from the Solution. Open BizApp.Services.csproj in an editor and find sections with our *Resources.Designer.cs files:

Conclusion

My article does not claim to newness or completeness. It's rather a collection of recipes or how-to's found during my attempt to build this application. Below, you will find some sources of my wisdom :)

License

Share

About the Author

Jevgenij lives in Riga, Latvia. He started his programmer's career in 1983 developing software for radio equipment CAD systems. Created computer graphics for TV. Developed Internet credit card processing systems for banks.
Now he is System Analyst in Accenture.

Comments and Discussions

An example of exception handling in WCF RIA Services that actually does something. Now if I can only get my database write to work. For some reason I can only sucessfully write to a database if an exception is not thrown, including one caught by OnError.