Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Creating a WCF RIA Services Class Library for a Silverlight Application with Code First Approach

4.67/5 (12 votes)
18 Apr 2012CPOL8 min read 87.5K   3.5K  
Create and configure WCF RIA Services class library for Silverlight app
This article shows how to create and configure a WCF RIA Services class library for a Silverlight application using the ADO.NET Entity Framework Code First approach.

Introduction

The WCF RIA Services class library components is the preferred option used by any Silverlight application that needs a clearly separated middle tier. The architecture and provided advantage are described in details in MSDN documents. There is also a walkthrough for creating a RIA services class library using the ADO.NET Entity Framework Database First approach. Something is different when implementing the RIA Services class library project using the Code First approach and no detailed document is available. This article will provide a full step-by-step tutorial regarding how to create and configure Code First RIA Services class library projects.

Required Tools and Components

Creating New or Modifying an Existing Silverlight Application

If you create a new Silverlight application solution, follow the steps from this walkthrough in the MSDN but do not check the Enable WCF RIA Services box in the New Silverlight Application dialog box.

If you add the RIA Services class library into an existing Silverlight application with default structure (RIA services reside in the Silverlight Web server project), remove the RIA Services link from the existing application solution by selecting <No Project Set> in the WCF RIA Services link dropdown list on the Silverlight tab of the Properties screen from the Silverlight client project.

1.png

The Silverlight application does not need a RIA Services link between the client project and the server project because the RIA Services link will only exist between the projects in the class library as illustrated in the MSDN documents. For this tutorial, the Silverlight application projects are named as ProductApp and ProductApp.Web.

Adding RIA Services Class Library Projects

2.png

This creates the ProductRiaLib (service client proxy) and ProductRiaLib.Web (service server project) under the ProductRiaLib solution virtual folder. We can rearrange projects using separate solution folder groups, which does not change the physical directory structure. For this tutorial, we just leave the virtual solution folder there. When the RIA Services class library projects are created, the RIA Service Link has automatically been added between the server and client proxy projects.

3.png

  1. Right-click the solution on Solution Explorer, select Add, and then New Project.
  2. In the Silverlight category Installed Templates, select the WCF RIA Services Class Library template and name it ProductRiaLib.
  3. Delete the default Class1.cs file in both the server and client proxy library projects. The initial solution should look like this.

Setting Entity Framework and Database Context

4.png

Entity Framework is listed in the All section of the Online side menu. Click the Install button to start the package installation process. Follow the prompts to complete the installation of Entity Framework 4.3.1. You can install Entity Framework by executing commands in the Package Manager Console, especially when you need different versions of the Entity Framework. Details are described on the NuGet site.

5.png

The Product.cs file holds the data entity classes:

C#
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace ProductRiaLib.Web.Models
{
    public class Product
    {
        public int ProductID { get; set; }
        public string ProductName { get; set; }
        public int? CategoryID { get; set; }
        public Decimal? UnitPrice { get; set; }                
        public bool OutOfStock { get; set; }
        
        public virtual Category Category { get; set; }
    }

    public class Category
    {
        public int CategoryID { get; set; }
        public string CategoryName { get; set; }     

        public virtual ICollection<Product> Products { get; set; }
    }
}

The ProductDBContext.cs file contains the ProductDbContext class for creating the database context and the ProductDbContextInitializer for seeding test data. In this sample, test data seeding is initialized when instantiating the ProductDbContext class.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.ComponentModel.DataAnnotations;

namespace ProductRiaLib.Web.Models
{
    public class ProductDbContext : DbContext
    {
        public ProductDbContext()
        {
            if (HttpContext.Current != null)
            {
                Database.SetInitializer<ProductDbContext>(new ProductDbContextInitializer());
            }
        }

        public DbSet<Product> Products { get; set; }
        public DbSet<Category> Categories { get; set; }
        
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        }
    }

    public class ProductDbContextInitializer : 
                 DropCreateDatabaseIfModelChanges<ProductDbContext>    
    {
        protected override void Seed(ProductDbContext context)
        {
            Category[] categories = new Category[]
            {
                new Category { CategoryID = 1, CategoryName = "Bath",
                    Products = new Product[] { 
                        new Product { ProductID = 1, 
                                      ProductName = "Bath Rug", UnitPrice = 24.5m},
                        new Product { ProductID = 2, ProductName = "Soap Dispenser", 
                                      UnitPrice = 12.4m, OutOfStock = true},                        
                        new Product { ProductID = 3, ProductName = "Shower Curtain", 
                                      UnitPrice = 30.99m},
                        new Product { ProductID = 4, ProductName = "Toilet Tissue", 
                                      UnitPrice = 15},
                    }.ToList()},
                new Category { CategoryID = 2, CategoryName = "Bedding",
                    Products = new Product[] { 
                        new Product { ProductID = 5, 
                                      ProductName = "Branket", UnitPrice = 60},
                        new Product { ProductID = 6, ProductName = "Mattress Protector", 
                                      UnitPrice = 30.4m, OutOfStock = true },
                        new Product { ProductID = 7, ProductName = "Sheet Set", 
                                      UnitPrice = 40.69m},
                    }.ToList()},
                new Category { CategoryID = 3, CategoryName = "kitchen",
                    Products = new Product[] { 
                        new Product { ProductID = 8, ProductName = "Baking Pan", 
                                      UnitPrice = 10.99m},
                        new Product { ProductID = 9, ProductName = "Coffee Maker", 
                                      UnitPrice = 49.39m},
                        new Product { ProductID = 10, ProductName = "Knife Set", 
                                      UnitPrice = 70},
                        new Product { ProductID = 11, ProductName = "Pressure Cooker", 
                                      UnitPrice = 90.5m, OutOfStock = true },
                        new Product { ProductID = 12, ProductName = "Water Pitcher", 
                                      UnitPrice = 29.99m},
                    }.ToList()},
            };
            foreach (var category in categories)
            {
                context.Categories.Add(category);
            }
            context.SaveChanges();
        }
    }
}

As shown in the code, the database context initializing and data seeding only occur when the HTTP process from the linked Silverlight application website runs.

  1. Right-click References in the ProductRiaLib.Web project and select Manage NuGet Packages…
  2. Add a folder named Models into the ProductRiaLib.Web service server project and then add two files into the folder.
  3. Add the System.Web assembly reference to the ProductRiaLib.Web project if it does not exist.
  4. Build the solution by pressing Ctrl+Shift+B. This makes the database context available to the domain service that will be created in the next section.

Adding the Domain Service Class and Modifying the Configuration

The domain service exposes data entities and operations in the class library server project to the client proxy using CRUD (create, read, update, delete) methods and enforcing the business logic.

  1. Add two folders named App_Data and Services into the ProductRiaLib.Web service server project.
  2. Right-click the Services folder, select Add and then New Item. In the Add New Item dialog, select Web from the Installed Templates, and then select Domain Service Class.
  3. Set the file name as ProductDomainService.cs and click Add.
  4. In the Add New Domain Service Class dialog, check the Category and Product boxes and also check the two Enable editing boxes, and then click OK. Note that if the entity list is empty, you may close and reopen the solution in Visual Studio, rebuild the solution, and then try again.

    6.png

    The ProductDomainService.cs file is created in the Services folder with three additional references, System.ServiceModel.DomainServices.Hosting, System.ServiceModel.DomainServices.Server, and Microsoft.ServiceModel.DomainServices.EntityFramework added to the project during this step. The service library server project in the solution now looks like this:

    7.png

    At this point, if rebuilding the solution, the service client proxy library folder and the file Generated_Code\ProductRiaLib.Web.g.cs should be created in the ProductRiaLib project. This is true only for the RIA Services class library created using Database First, not the Code First, approach. The problem is caused by some settings related to the App.config file. We need to tweak the configurations to make it work.

  5. Add the following code lines to the App.config files just before the closing </configuration> tag. Although we use Entity Framework 4.3.1 installed freshly with NuGet, other supporting assemblies are older and not compatible with the Code First approach.
    XML
    <runtime>
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
          <dependentAssembly>
            <assemblyIdentity name="EntityFramework" 
               publicKeyToken="b77a5c561934e089" culture="neutral" />
            <bindingRedirect oldVersion="0.0.0.0-4.3.1.0" newVersion="4.3.1.0" />
          </dependentAssembly>
        </assemblyBinding>
    </runtime>

    When rebuilding the solution, the client proxy file, however, is still not generated.

  6. Rename the App.config to Web.config and rebuild the solution. The client proxy file is now successfully generated. In the class library server project, Database First works with the App.config file but Code First does not. This is due probably to a bug in Entity Framework/RIA Services in that the code cannot find the App.config file name for the final mergence to the machine.config. Instead, the Web.config file name exists along the searchable tree.

Consuming Data Provided by the Service Class Library

  1. Copy <system.webServer>, <system.serviceModel>, <httpModules> under the <system.web>, and <runtime> nodes from the ProductRiaLib.Web\Web.config file to the Silverlight application server project ProductApp.Web\Web.config.
  2. Add a database connection string to ProductApp.Web\Web.config. The final content in the ProductApp.Web\Web.config file is shown below. You need to replace the code in italics with your database file location.
    XML
    <configuration>
      <connectionStrings>
        <add name="ProductDbContext" 
           connectionString="Data Source=[Your-ProductRiaLib.web-Path]\
                  App_Data\ProductData.sdf" providerName="System.Data.SqlServerCe.4.0" />
    </connectionStrings>
      <system.web>
        <compilation debug="true" targetFramework="4.0" />
        <httpModules>
          <add name="DomainServiceModule" 
             type="System.ServiceModel.DomainServices.Hosting.DomainServiceHttpModule, 
                   System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, 
                   Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
        </httpModules>
      </system.web>
      <system.webServer>
        <modules runAllManagedModulesForAllRequests="true">
          <add name="DomainServiceModule" preCondition="managedHandler" 
             type="System.ServiceModel.DomainServices.Hosting.DomainServiceHttpModule, 
                   System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, 
                   Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
        </modules>
        <validation validateIntegratedModeConfiguration="false" />
      </system.webServer>
      <system.serviceModel>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true" 
         multipleSiteBindingsEnabled="true" />
      </system.serviceModel>
      <runtime>
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
          <dependentAssembly>
            <assemblyIdentity name="EntityFramework" 
             publicKeyToken="b77a5c561934e089" culture="neutral" />
            <bindingRedirect oldVersion="0.0.0.0-4.3.1.0" newVersion="4.3.1.0" />
          </dependentAssembly>
        </assemblyBinding>
      </runtime>
    </configuration>

    If you would like to connect to a full-fledged SQL Server instance or SQL Server Express, just change the connection string for your choice.

    The connection string for SQL Server 2008 R2:

    XML
    <add name="ProductDbContext" 
      connectionString="Server=[YourSQLServerInstanceName];Database=ProductData;
         Integrated Security=True;" providerName="System.Data.SqlClient" />

    The connection string for SQL Server Express 2008 R2:

    XML
    <add name="ProductDbContext" 
       connectionString="Data Source=.\SQLExpress;Integrated Security=SSPI; 
         Database=ProductData; AttachDBFilename=[Your-ProductRiaLib.web-Path]\
         App_Data\ProductData.mdf; User Instance=true" 
         providerName="System.Data.SqlClient" />

    If the connection string is not specified, Entity Framework will use the built-in defaultConnectionFactory setting to create the database in the local SQL Server Express instance, if it exists, with the database name "ProductRiaLib.Web.Models.ProductDbContext". Most people would actually not like this behavior.

    If you use the SQL Server Compact database and don't have a version of SQL Server Express installed or stop the SQL Server Express service, you may encounter an error "Failed to get the MetadataWorkspace for the DbContext type" when running the application to create the database file and initialize the data context. By default, the RIA domain service for the code first still uses or references the SQL Server Express for metadata workspace even you delete the defaultConnectionFactory setting. If this happens, you can add a dummy connection string with the same value in the name attribute into the Web.config of the ProductRiaLib.Web project.

    XML
    <connectionStrings>
        <add name="ProductDbContext" connectionString="" 
         providerName="System.Data.SqlServerCe.4.0" />
    </connectionStrings>
  3. To check if any changes in the class library from the server side can be reflected in the client side, just add a function that returns product lists based on the selected categories. Add the code lines into ProductRiaLib.Web\ProductDomainService.cs under the GetProducts() method.
    C#
    public IQueryable<Product> GetCategorizedProduct(int categoryId)
    {
        return this.DbContext.Products.Where(
          e => e.CategoryID == categoryId).OrderBy(e => e.ProductName);
    }
  4. Rebuild the solution. Confirm that the newly added method with the name GetCategorizedProductQuery() is available from Generated_Code\ProductRiaLib.Web.g.cs in the client proxy of the class library.
  5. To complete testing the Silverlight client project using the data provided by RIA Services, please download the source code files using the link shown at the beginning of the article. If you have created your own solution, you can merge some files to your existing ProductApp project and resolve some .NET assembly references if missing.
    • Assets\Styles.xaml
    • Views\ProductList.xaml and its cs file
    • Views\AddProductWindow.xaml and its cs file
    • Views\ErrorWindow.xaml and its cs file
    • App.xaml and its cs file
  6. Make sure that the ProductApp.Web project is set as the start project. Run the application by pressing F5. The Product List page will be rendered and data will be shown in the grid when selecting any category from the dropdown. You can edit and delete the product data. There is also a child window for adding new products.

    8.png

    9.png

    10.png

  7. After running the application, the database should automatically be created. Below is shown a sample using SQL Server Compact 4.0. We can check the tables and data inside the database using Visual Studio Server Explorer by double-clicking the ProductData.sdf file in the ProductRiaLib.Web\App_Data folder.

    11.png

Summary

The Code First approach for data-centric application development is becoming more popular. Using this approach to create a WCF RIA Services class library is useful for building true middle tiers and portable components for Silverlight applications in any scale. Hope this step by step tutorial is helpful for developers who need such details.

History

  • 26th March, 2012: Initial version

License

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