Click here to Skip to main content
15,867,704 members
Articles / Desktop Programming / XAML
Article

A Silverlight application with the WCF RIA Services Class Library

Rate me:
Please Sign up or sign in to vote.
4.72/5 (11 votes)
3 Mar 2010CPOL8 min read 62.6K   1.8K   39   3
A Silverlight application using the WCF RIA Services Class Library. Demonstrates how to implement a custom Authorization Service, utilize localized resources, and add unit tests for DAL.

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:

Business Application

Requisites

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

  • Microsoft Visual Studio 2008 SP1
  • Microsoft Silverlight 3
  • Microsoft Silverlight 3 SDK
  • Microsoft Silverlight 3 Toolkit November 2009
  • Microsoft Silverlight 3 Tools for Visual Studio 2008 SP1
  • WCF RIA Services Beta
  • SQL Server 2008 Express

Project structure

The solution contains the following projects:

Project structure

BizApp.WebWeb project hosting this Silverlight application. It is our server project.
BizAppSilverlight client application.
BizApp.ControlsAuxiliary project, containing UI controls.
BizApp.Services.ServerWCF RIA Services Class Library. Server-tier project.
BizApp.ServicesWCF RIA Services Class Library. Client-tier project. Contains generated code; visible if you click the "Show All Files" icon.
BizApp.Services.TestUnit tests.

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:

C#
[Dependency]
public IRepository<UserAccount> UserAccountRepository { get; set; }

[Dependency]
public IRepository<UserRole> UserRoleRepository { get; set; }

But, how does the services initialize these properties? That happens during the creation of a service in the DomainServiceFactory class:

C#
public DomainService CreateDomainService(Type domainServiceType, 
                     DomainServiceContext context)
{
    var domainService = 
      DependencyInjectionProvider.UnityContainer.Resolve(
      domainServiceType) as DomainService;
    domainService.Initialize(context);

    return domainService;
}

You do not need to call this method explicitly. All you need is just to initialize our DomainServiceFactory in the Global.asax.cs file:

C#
protected void Application_Start(object sender, EventArgs e)
{
    // Create data context for all services
    AppDatabaseDataContext dataContext = new AppDatabaseDataContext();

    // Configure unity container
    DependencyInjectionProvider.Configure(
       (System.Data.Linq.DataContext)dataContext);

    // Set new domain service factory, creating domain services
    DomainService.Factory = new DomainServiceFactory();
}

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:

XML
<Application.ApplicationLifetimeObjects>
    <app:WebContext>
        <app:WebContext.Authentication>
            <appsvc:FormsAuthentication 
                DomainContextType=
                  "BizApp.Services.Server.DomainServices.AuthenticationContext, 
                   BizApp.Services, Version= 1.0.0.0"/>
        </app:WebContext.Authentication>
    </app:WebContext>
</Application.ApplicationLifetimeObjects>

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

C#
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:

Authorization Cookie

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:

C#
protected override void OnError(DomainServiceErrorInfo errorInfo)
{
   if (errorInfo != null && errorInfo.Error != null)
   {
      EventLog eventLog = new EventLog("Application");
      eventLog.Source = "BizApp";
      eventLog.WriteEntry(errorInfo.Error.Message, EventLogEntryType.Error);
   }

   base.OnError(errorInfo);
}

Localized resources

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

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:

Resources

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:

XML
<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:

XML
<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Assets/Styles.xaml"/>
            <ResourceDictionary>
                <res:ApplicationResources x:Key="LocalStrings" />
            </ResourceDictionary>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

Now, we can use our localized strings in XAML:

XML
<Button Content="{Binding Source={StaticResource LocalStrings}, 
                 Path=LocalizedStrings.LoginButton}"
    Click="Login_Click" Width="75" Height="23" />

To change the UI language, we have to change the culture in the current thread and reset the resources (see AppMenu.xaml.cs):

C#
private void Language_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (cbxLanguage != null)
    {
        ComboBoxItem item = cbxLanguage.SelectedItem as ComboBoxItem;
        Thread.CurrentThread.CurrentCulture = 
                             new CultureInfo(item.Content.ToString());
        Thread.CurrentThread.CurrentUICulture = 
                             new CultureInfo(item.Content.ToString());
        ((ApplicationResources)Application.Current.
          Resources["LocalStrings"]).LocalizedStrings = 
          new BizApp.Resources.LocalizedStrings();
    }
}

Localized text on the Service Layer

The System.ComponentModel.DataAnnotations namespace provides a convenient way to decorate our data with metadata for their validation (for example, see RegistrationData.cs):

C#
[Display(Name = "FullNameLabel", Description = "FullNameDescription", 
    ResourceType = typeof(RegistrationDataResources))]
[StringLength(100, ErrorMessageResourceName = "ValidationErrorBadFullNameLength", 
    ErrorMessageResourceType = typeof(ErrorResources))]
public string FullName { get; set; }

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:

XML
<Compile Include="..\BizApp.Services.Server\Resources\ErrorResources.Designer.cs">

Add <AutoGen>, <DesignTime>, and <DependentUpon> sub-sections. The final result shall look like the following:

XML
<Compile Include="..\BizApp.Services.Server\Resources\ErrorResources.Designer.cs">
   <AutoGen>True</AutoGen>
   <DesignTime>True</DesignTime>
   <DependentUpon>ErrorResources.resx</DependentUpon>
   <Link>Server\Resources\ErrorResources.Designer.cs</Link>
</Compile>

Now, you can reload the project in Visual Studio, build it, and run.

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 :)

References

History

  • 3 March, 2010: Initial post.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Latvia Latvia
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

 
GeneralMy vote of 5 Pin
Richard Waddell30-Nov-12 17:37
Richard Waddell30-Nov-12 17:37 
GeneralMy vote of 5 Pin
MohamedSoumare30-Jun-10 5:14
MohamedSoumare30-Jun-10 5:14 
GeneralObject Reference is null Pin
MohamedSoumare30-Jun-10 2:36
MohamedSoumare30-Jun-10 2:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.