Walkthrough: Creating a RIA Service with the Code First Approach
[WCF RIA Services Version 1 Service Pack 2 is compatible with either .NET framework 4 or .NET Framework 4.5, and with either Silverlight 4 or Silverlight 5.]
This topic shows how to create a WCF RIA Services application that retrieves data from a database that is defined and initialized using a code first approach, and then makes this data available to a Silverlight client where it is presented for editing and viewing. This approach is enabled by RIA Services v1 SP2 and Entity Framework 4.1 technologies.
The solution creates and initializes the data model in code and then creates a domain service that inherits from the new DbContext type, a more simplified and intuitive API that now wraps the original ObjectContext that had been used in previous versions of RIA Services.
Prerequisites
The procedure described here requires several prerequisite programs, such as Visual Studio 2010 and the Silverlight 4 or 5 Developer Runtime and SDK, be installed and configured properly, in addition to WCF RIA Services v1 SP2. The latest WCF RIA Services Toolkit and Entity Framework 4.1 are also required. Instructions with links to each of the sites from which these prerequisite programs can be installed are provided by the topics within the Prerequisites for WCF RIA Services node. Follow the instructions provided there for the programs required before proceeding with this how-to to ensure that you encounter as few problems as possible.
Create a RIA Services solution
Create a new RIA Services project in Visual Studio 2010 by selecting File, New, and then Project.
The New Project dialog box appears.
Select the Silverlight Business Application template from the Visual C# (or Visual Basic) Silverlight group of the Installed Templates and name the new project RiaServicesCodeFirstExample.
Click OK to create the solution.
The solution created by the template contains two projects: a client project and a server (or web) project.
RIAServicesCodeFirstExample: the client project that contains the Silverlight code that you use to create the presentation tier.
RIAServicesCodeFirstExample.Web: the server (or web) project that contains the middle tier code.
The template also creates a RIA Services link between the client project and the server project.
Add the following three references to the RIAServicesCodeFirstExample.Web project. To accomplish this, right-click on the References folder in that project, select Add Reference, and search in the appropriate tab (itemized, respectively, below) for each of the assemblies. Select them one at a time and click OK to add each of them to the project.
Microsoft.ServiceModel.DomainService.EntityFramework: located in the %Program Files (x86)\Microsoft SDKs\RIA Services\v1.0\Libraries\Server folder, which is reached by navigating from the BROWSE tab.
System.Data.Entity: located in the list on the .NET tab.
EntityFramework: located in the %Program Files (x86)\Microsoft ADO.NET Entity Framework 4.1\Binaries folder, which is reached by navigating from the Browse tab.
The Data Model
This section adds an Entity Framework Model to the web project using the Code-First approach. The data model is defined and initialized in code. It consists of two entity types, a Product and a Category. A custom DbContext type is defined that declares the two types defined above as entities and establishes their context.
Add the Data Model with code first
Right-click on the Models directory of the RIAServicesCodeFirstExample.Web project and select Add, and then New Item.
From the Visual C# (or Visual Basic) Web group of the Installed Templates, select the Class template and name the new file NWModel.cs.
In the NWModel.cs file, replace the template code within brackets of namespace RiaServicesCodeFirstExample.Web.Models with the following code snippet.
/// <summary> /// Entity defined directly in code. /// </summary> 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 Discontinued { get; set; } public virtual Category Category { get; set; } } /// <summary> /// Another entity defined in code. /// </summary> public class Category { public int CategoryID { get; set; } public string CategoryName { get; set; } public string Description { get; set; } public byte[] Picture { get; set; } public virtual ICollection<Product> Products { get; set; } } /// <summary> /// Custom DbContext type that declares the 2 types defined above as entities. /// </summary> public class ProductDbContext : DbContext { public ProductDbContext() { /// It also sets the initializer if the HttpContext is not null, which indicates we are in the Runtime Context. If the HttpContext is null, we are in the DesignTime context. /// Alternately you can set the Database Initializer in the Application_Start in the Global.asax file. if (HttpContext.Current != null) { Database.SetInitializer<ProductDbContext>(new ProductDbContextInitializer()); } } public DbSet<Product> Products { get; set; } public DbSet<Category> Categories { get; set; } } public class ProductDbContextInitializer : DropCreateDatabaseAlways<ProductDbContext> { protected override void Seed(ProductDbContext context) { Category[] categories = new Category[] { new Category { CategoryID = 1, CategoryName = "Food", Description = "Edible stuff", Products = new Product[] { new Product { ProductID = 1, ProductName = "Candy", UnitPrice = 10}, new Product { ProductID = 2, ProductName = "Fruits", UnitPrice = 20, Discontinued = true }, new Product { ProductID = 5, ProductName = "Cereal", UnitPrice = 10}, }.ToList()}, new Category { CategoryID = 2, CategoryName = "Clothes", Description = "Personal stuff", Products = new Product[] { new Product { ProductID = 3, ProductName = "Shirt", UnitPrice = 50}, new Product { ProductID = 4, ProductName = "Cap", UnitPrice = 15}, }.ToList()}, new Category { CategoryID = 3, CategoryName = "Furniture", Description = "Household stuff", Products = new Product[] { new Product { ProductID = 6, ProductName = "Table", UnitPrice = 100}, new Product { ProductID = 7, ProductName = "Chair", UnitPrice = 60}, new Product { ProductID = 8, ProductName = "Lamp", UnitPrice = 20, Discontinued = true }, }.ToList()}, }; foreach (var category in categories) { context.Categories.Add(category); } }
So each Product in our data model belongs to a Category. The ProductDbContext is a DbContext type that has a DbSet of type Product and a DbSet of type Category. The DbSet tells Entity Framework that Product and Category are entity types. It also uses the current value of the HttpContext to distinguish between the Runtime and Design time contexts.
Add the using System.Data.Entity statement at the top of the file and then build (F6) the solution to make sure you are compiling cleanly and that you have not inadvertently generated any errors.
To create the domain service
In this procedure, you create a NWDomainService and add it to the (middle-tier) Web project. The domain service exposes the data entities and operations in the Web project to the client project using CRUD (create, read, update, delete) methods.
Right-click the server project, select Add and New Item.
In the list of categories, select Web and then select the Domain Service Class template.
Name the class NWDomainService.cs (or NWDomainService.vb).
Click Add.
The Add New Domain Service Class dialog box appears.
Make sure that the Enable client access box is checked.
Select the Category and Product entities and then check the Enable Editing boxes for both of them.
Click OK.
The NWDomainService class is generated in a new NWDomainService.cs (or NWDomainService.vb) file.
Open this file. Notice that the file has the following characteristics:
The NWDomainService class derives from DbDomainService class, which is an abstract base class in the RIA Services framework. This base class was used automatically because a code first based Entity Framework model was used.
The NWDomainService class exposes the entities on the ProductDbContext class, which, in turn, derives from the DbContext base class.
The NWDomainService class is marked with the EnableClientAccessAttribute attribute to indicate that it is visible to the client tier.
Query methods named GetProduct and GetCategory were generated. These methods return every item of their respective entity types without any filtering or sorting.
CRUD methods to create (insert), read (the query method), update, and delete customers from the records have been generated.
Creating the Silverlight Client
Generate the client proxy classes by building the solution (F6). The RIA Services link that was established between the client project and the server project by the Silverlight Business Application template make this code generation possible. These client proxy classes provide access to the data from the client.
In Solution Explorer, select the RIAServicesCodeFirstExample client project and click the Show All Files icon at the top of the window. The client proxy code generated is contained in the RIAServicesCodeFirstExample.Web.g.cs (or RIAServicesCodeFirstExample.Web.g.vb) file located in the Generated_Code folder. Notice that this (rather complicated) file is divided up into three namespaces which contain (among many other bits) the following items.
The RiaServicesCodeFirstExample namespace: contains a WebContext class that derives from the WebContextBase class is generated.
The RiaServicesCodeFirstExample.Web namespace: contains a NWDomainContext class that derives from the DomainContext class is generated. This class has methods named GetProductsQuery and GetCategoriesQuery that corresponds to the query methods created in the domain service.
The RiaServicesCodeFirstExample.Web.Models namespace: contains Product and Category classes that derive from the Entity class, that are generated for the entities exposed by the domain service. The Product and Category entity classes in the client project correspond to the Product and Category entities on the server.
The next procedure shows how to use the client proxy code from the client application to access and edit the data stored by the new NWDomainService. The UI requires some new references, a pop up window for creating new data entries, The xaml for the presentation layer, and the code-behind to provide the functionality.
To provide the UI for the Silverlight client
Add references in the RIAServicesCodeFirstExample client project to System.Windows.Controls.Data.dll and System.Windows.Data.dll. To accomplish this, right-click on the References folder in that project, select Add Reference, then the Browse tab, and navigate to the % Program Files (x86)\Microsoft SDKs\Silverlight\v5.0\Libraries\Client folder (or to % Program Files (x86)\Microsoft SDKs\Silverlight\v4.0\Libraries\Client, if you are using Silverlight 4.0). Select the two relevant assemblies while holding down the Ctrl key, then click OK. Confirm that they have shown in in the References folder
We need to create a window that will pop up when we want to add new data values. Right-click on the Views directory of the RIAServicesCodeFirstExample client project and select Add, and then New Item.
Select the Silverlight Child Window template from the Visual C# (or Visual Basic) Silverlight group of the Installed Templates and name the new file AddWindow.xaml.
Replace the code within the brackets of namespace RiaServicesCodeFirstExample.Views with the following code snippet.
public partial class AddWindow : ChildWindow { public AddWindow(IEnumerable<string> categories) { InitializeComponent(); this.Categories = categories; this.comboBox1.ItemsSource = this.Categories; } public string Name { get; set; } public int Price { get; set; } public IEnumerable<string> Categories { get; set; } public string SelectedCategory { get; set; } public event EventHandler AddEvent; private void OKButton_Click(object sender, RoutedEventArgs e) { this.Name = this.textBox1.Text; this.Price = Convert.ToInt32(this.textBox2.Text); this.SelectedCategory = this.comboBox1.SelectedItem as string; this.DialogResult = true; this.AddEvent(this, new EventArgs()); } private void CancelButton_Click(object sender, RoutedEventArgs e) { this.DialogResult = false; } private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e) { } }
Next, we need to add the UI to this pop up window. To do this, open the AddWindow.xaml file and change the value of the Title of the ChildWindow control from "AddWindow" to "Add a new Project". Then, scroll down to the bottom of the file and paste the following code in below the OKButton element, within the Grid element.
<TextBlock Height="19" HorizontalAlignment="Left" Margin="41,43,0,0" Name="textBlock1" Text="Name" VerticalAlignment="Top" Width="40" /> <TextBox Height="23" HorizontalAlignment="Left" Margin="166,39,0,0" Name="textBox1" VerticalAlignment="Top" Width="160" /> <TextBlock Height="23" HorizontalAlignment="Left" Margin="41,107,0,0" Name="textBlock2" Text="Price" VerticalAlignment="Top" /> <TextBox Height="23" HorizontalAlignment="Left" Margin="166,103,0,0" Name="textBox2" VerticalAlignment="Top" Width="160" /> <ComboBox Height="23" HorizontalAlignment="Left" Margin="166,169,0,0" Name="comboBox1" VerticalAlignment="Top" Width="160" SelectionChanged="comboBox1_SelectionChanged" /> <TextBlock Height="23" HorizontalAlignment="Left" Margin="41,169,0,0" Name="textBlock3" Text="Category" VerticalAlignment="Top" />
We need to add the Add, Submit, and Delete, buttons to our UI. To do this, open the MainPage.xaml in the client project and past in the following code (from the XAML view) at the bottom of the page, just after the Login Container border style element (so below the insertion will be only three elements: two close </Grid> elements and the final </UserControl> element.
<sdk:DataGrid AutoGenerateColumns="True" Height="238" HorizontalAlignment="Left" Margin="6,128,0,0" Name="dataGrid1" VerticalAlignment="Top" Width="628" /> <Button Content="Add" Height="23" HorizontalAlignment="Left" Margin="36,382,0,0" Name="addButton" VerticalAlignment="Top" Width="75" Click="addButton_Click" /> <Button Content="Submit" Height="23" HorizontalAlignment="Left" Margin="150,382,0,0" Name="submitButton" VerticalAlignment="Top" Width="75" Click="submitButton_Click" /> <Button Content="Delete" Height="23" HorizontalAlignment="Left" Margin="266,382,0,0" Name="deleteButton" VerticalAlignment="Top" Width="75" Click="deleteButton_Click" />
Add the sdk namespace prefix definition xmlns:sdk="https://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" to the other xml namespace definitions at the top of the MainPage.xaml file.
Finally, we need to add the code behind functionality to make these controls work. To do this, open the MainPage.xaml.cs in the client project and add the following using statements.
using RiaServicesCodeFirstExample.Views; using RiaServicesCodeFirstExample.Web; using RiaServicesCodeFirstExample.Web.Models; using System.Linq; using System.Collections.Generic;
Add the following two declarations at the start of the MainPage class, just above the MainPage method.
AddWindow addWindow; RiaServicesCodeFirstExample.Web.NWDomainContext ctx;
Add the following code to the MainPage method, just below the InitializeComponent(); statement.
ctx = new NWDomainContext(); ctx.Load(ctx.GetProductsQuery()); ctx.Load(ctx.GetCategoriesQuery()); this.dataGrid1.ItemsSource = ctx.Products;
Add the following code at the bottom of the MailPage.xzml.cs file, below the NavigationFailed event handler.
private void addButton_Click(object sender, RoutedEventArgs e) { List<string> categories = new List<string>(); foreach (var category in ctx.Categories) { categories.Add(category.CategoryName); } addWindow = new AddWindow(categories); addWindow.Show(); addWindow.AddEvent += new System.EventHandler(addWindow_AddEvent); } void addWindow_AddEvent(object sender, System.EventArgs e) { AddWindow addWindow = sender as AddWindow; Category cat = ctx.Categories.Where(c => c.CategoryName == addWindow.SelectedCategory).FirstOrDefault(); Product p = new Product { ProductName = addWindow.Name, UnitPrice = addWindow.Price, Category = cat }; ctx.Products.Add(p); } private void submitButton_Click(object sender, RoutedEventArgs e) { ctx.SubmitChanges(); } private void deleteButton_Click(object sender, RoutedEventArgs e) { Product deletedProduct = this.dataGrid1.SelectedItem as Product; ctx.Products.Remove(deletedProduct); }
Next Steps
Build the solution (F5) and Start Without Debugging (Ctrl+F5). The Silverlight client application should start. Test the Add, Submit and Delete functionality.