July 2010 - Posts
This prior post described the code that is generated when you compile a Silverlight application that uses WCF RIA Services and your own plain old CLR objects (POCOs). This post provides some ideas if something goes horribly wrong and the compiler does not generate the appropriate code.
If you build your solution and you don't get the generated code described in this prior post, here are some things to check:
- In your business layer component (where your business classes are defined):
- The class(es) you want to access from Silverlight must be a public.
- The properties that you want to access from Silverlight must be public.
- The Key attribute must be defined on one of the business class public properties.
- In your Silverlight project:
- Ensure you have a WCF RIA Services link.
- Open the Silverlight project properties. The WCF RIA Services link is defined at the bottom of the properties dialog
- In your ASP.NET project (that was created automatically when you created your Silverlight project)
- You must have a reference defined to your business layer component.
- In your Domain Service class:
- The Domain Service class must be defined within the ASP.NET project (or in a class referenced by the ASP.NET project).
- It must have the EnableClientAccess attribute on the class.
- It must inherit from the DomainService class (or one of its specialized classes such as LinqToEntiesDomainService if you are using Entity Framework)
- If you define a Query method to retrieve data from your business class, the Query method:
- Must be a method and not a property.
- Must return one of the following:
- A single entity (instance of the business object class)
- An IEnumerable<T> where T is the entity
- An IQueryable<T> where T is the entity
- Can have any parameters.
- Can have any name.
- Can (but does not have to) have a Query attribute to define the method as a query.
- If you define an Update method to update data from your business class, the Update method:
- Must be a method.
- Must have no return value.
- Must pass the entity as a parameter.
- Must either:
- Start with "Update", "Change" or "Modify
- Or have an Update attribute
The first time I tried using WCF RIA services, I did not know about the Key attribute, so did not get any generated code.
More recently, I used a converter to convert some working C# code from my Domain Service class into VB and then could not get any generated code from the VB solution. I did not immediately notice that the converter translated IEnumerable<Customer> to just IEnumerable (non-generic). So the compiler did not know which entity to process.
I hope the above checklist will help you quickly find why you are not getting generated code. If you have other things to add to this checklist, I'd love to hear from you. Please add a comment to this post.
Enjoy!
EDITED 8/12/10: Incorporated Colin Blair's comment from 8/11/10.
This post provides a high-level description of what happens when you compile a Silverlight application that uses WCF RIA Services and plain old CLR objects (POCOs) and the resulting generated code. (See this prior post for a code example.)
After you define your Silverlight project, enable WCF RIA Services, and add your Domain Service class with appropriate method(s), you have enough to build your solution and view the generated code.
In Solution Explorer, select your Silverlight project and click the Show All Files button in the toolbar. You will then see a Generated_Code folder containing a *.g.cs file. This contains the code generated by Visual Studio and defines the classes detailed below.
Entity class
This class is generated from information in the business layer (your POCO classes on the server) and includes all of the class's properties. It inherits from the DomainServices Entity class (not to be confused with the Entity Framework (EF) Entity class).
The compiler looks at the properties of the class and any attributes on those properties. It then generates a copy of those properties with their associated attributes in the generated code file.
How does it know which business layer class to use for the Entity class? It determines the appropriate class based on the parameters and/or return values on the methods in your Domain Service class.
If you have a query style method, it returns either a single entity or an IEnumerable or IQueryable of the entity. The compiler uses that information to determine which business layer class to use.
If you have an insert, update, or delete method, the parameter defines the entity. The compiler uses that information to determine which business layer class to use.
The compiler attributes the generated class as a DataContract and each property as a DataMember so it can be processed appropriate when the class is used as part of a WCF service. (WCF RIA Services uses WCF "under the covers".)
Domain Context class
This class is generated from information in the Domain Service class that you created. It inherits from DomainContext.
The compiler defines a WCF service contract in this class. It also defines wrapper methods for the methods in your Domain Service class.
When your Silverlight application calls methods in this class, the class makes the WCF service calls to the Domain Service class in the ASP.NET project.
Here it is in picture form:
Enjoy!
The prior post here detailed how to build a Silverlight application using WCF RIA Services and your own plain old CLR objects (POCOs). That application displayed the data in a grid and allowed updates using a set of labels and textboxes. But the order of the fields was alphabetical, which is rarely the order that you want.
So you could spend some time in the XAML, reordering the controls.
OR, you could add a few more attributes to your business object in your business layer and the fields will be ordered as you want.
Use the DisplayAttribute to:
- Define a field name different from the business object property name.
- Define a short name for use in the grid.
- Define a field to use as a watermark.
- Define the order of the field when building the UI.
- Define the resource type if the strings are defined in a resource file.
First, be sure to import the System.ComponentModel.DataAnnotations namespace. Then add the DisplayAttribute to the properties as shown in this example.
In C#:
[Display(Order=1, Description="Customer's last name", ShortName="LName", Name="Name (Last)")]
public string LastName { get; set; }
In VB:
<Display(Order:=1, Description:="Customer's last name", ShortName:="LName", Name:="Name (Last)")>
Public Property LastName() As String
The result looks like this:
Notice the use of the ShortName in the grid column header and of the Name in the field label. But more importantly for this column, notice the order of the fields both in the grid and in the data entry form.
Use this technique to define the field ordering before you drag and drop a grid or data entry form onto your Silverlight page.
Enjoy!
The prior post here detailed how to build a simple Silverlight application that used WCF RIA Services to communicate with your server-side business layer to retrieve data and display it in a Silverlight DataGrid. This post adds to the prior post, providing an update feature.
In keeping with the general theme of the prior post, this post is going to provide the simplest technique for performing an update in a Silverlight application using your own Plain Old CLR Objects (POCOs) that reside on the server.
So first, the prerequisites:
1) Follow the steps in the prior post here.
2) Build the Data Access Layer.
The prior post "mocked" the data access by returning a simple list from the business layer retrieve method. To try out the save, this won't suffice. The code needs real data access.
Use the information in the post defined here to build your data access layer.
3) Change your Customer class to call the Data Access layer.
In the prior post, the Customer class Retrieve method returned a hard-coded set of customers. This method needs to change to use the Data Access layer. In addition, the code needs a Save method to save the customer data.
In C#:
public List<Customer> Retrieve()
{
List<Customer> custList = new List<Customer>();
DataTable dt = Dac.ExecuteDataTable("CustomerRetrieve");
foreach (DataRow dr in dt.Rows)
{
Customer cust = new Customer
{
CustomerId = (int)dr["CustomerId"],
LastName = dr["LastName"].ToString(),
FirstName = dr["FirstName"].ToString(),
EmailAddress = dr["EmailAddress"].ToString()
};
custList.Add(cust);
}
return custList;
}
public bool Save()
{
bool success = true;
// Call the save
int returnId = Dac.ExecuteNonQuery("CustomerUpdate",
new SqlParameter("CustomerId", CustomerId),
new SqlParameter("LastName", LastName),
new SqlParameter("FirstName", FirstName),
new SqlParameter("EmailAddress", EmailAddress));
if (returnId != 0)
this.CustomerId = returnId;
return success;
}
In VB:
Public Function Retrieve() As List(Of Customer)
Dim custList As New List(Of Customer)
Dim dt As DataTable = Dac.ExecuteDataTable("CustomerRetrieve")
For Each dr As DataRow In dt.Rows
Dim cust As New Customer With {
.CustomerId = CType(dr("CustomerId"), Integer),
.LastName = dr("LastName").ToString(),
.FirstName = dr("FirstName").ToString(),
.EmailAddress = dr("EmailAddress").ToString()}
custList.Add(cust)
Next
Return custList
End Function
Public Function Save() As Boolean
Dim success As Boolean = True
' Call the save
Dim returnId As Integer = Dac.ExecuteNonQuery("CustomerUpdate",
New SqlParameter("CustomerId", Me.CustomerId),
New SqlParameter("LastName", Me.LastName),
New SqlParameter("FirstName", Me.FirstName),
New SqlParameter("EmailAddress", Me.EmailAddress))
If returnId <> 0 Then
Me.CustomerId = returnId
End If
Return success
End Function
NOTE: Normally I build a Customer class (singular) with the properties and methods that mange a single customer and a Customers class (plural) that works with a list of those customers. The functionality for both were added to one class to keep this example as simple as possible.
Once the prerequisites are in place, we can get started.
STEP 1: Add an Update method to the Domain Service class
Open the Domain Service class and add an update method.
In C#:
public void UpdateCustomer(Customer updatedCustomer)
{
}
In VB:
Public Sub UpdateCustomer(ByVal updatedCustomer As Customer)
End Sub
A few notes about this UpdateCustomer method:
- It must be a method.
- Must have no return value.
- Must pass the entity as a parameter.
- Must either:
- Start with "Update", "Change" or "Modify
- Or have an Update attribute
WCF RIA Services recognizes the method as an update method because of its name or Update attribute. It recognizes it as an update of a Customer by its parameter. This method is required for an update operation to be valid in the Silverlight application.
This method does not require any code, unless you want to perform some additional operations on an update. However, the method still needs to exist for the DomainDataSource on the page to accept updates.
This method is called when the code calls SubmitChanges, as shown later in this post.
STEP 2: Override the PersistChangeSet method
The PersistChangeSet method is called when the code calls SubmitChanges, after the code calls the Update method. Use this method to call the Save method on your business objects.
In C#:
protected override bool PersistChangeSet()
{
Customer changedCustomer;
bool success = true;
foreach (var item in ChangeSet.ChangeSetEntries)
{
if (item.Entity.GetType() == typeof(Customer))
{
changedCustomer = (Customer)item.Entity;
success = changedCustomer.Save();
if (!success)
break;
}
}
return success;
}
In VB:
Protected Overrides Function PersistChangeSet() As Boolean
Dim changedCustomer As Customer
Dim success As Boolean = True
For Each item In ChangeSet.ChangeSetEntries
If item.Entity.GetType() = GetType(Customer) Then
changedCustomer = DirectCast(item.Entity, Customer)
success = changedCustomer.Save()
If Not success Then
Exit For
End If
End If
Next
Return success
End Function
WCF RIA Services keeps track of any changes to the entities in the application (Customer in this example). You can access the set of changes using the ChangeSet property.
The Customer class in this example has a Save method that saves one specific customer. So the above code loops through the customers in the ChangeSet and calls the Save method on the customer.
This would be more efficient if you had a Customers class (as mentioned in the Note earlier in this post). Your Customers class could have a save method against a list of customers. You could then call Customers.Save to save all of your customer change instead of calling Save on each individual customer.
STEP 3: Build the UI
Again, keeping with the goal of staying as simple as possible, the UI in this example is a simple set of properties.
1) Open the designer for MainPage.xaml by double-clicking on the file.
2) Open the Data Sources window (Data | Show Data Sources).
Visual Studio automatically added the defined domain context class to your data sources. Change the default UI from a data grid to a details view:
3) Drag and drop the Customer data source onto the designer to the right of the grid (created in the prior post) and Visual Studio creates the user interface for you:
4) Add the Save button as shown above.
In the code behind for the Save button, add code as follows.
In C#:
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
if (customerDomainDataSource.HasChanges)
customerDomainDataSource.SubmitChanges();
}
In VB:
Private Sub SaveButton_Click(ByVal sender As System.Object,
ByVal e As System.Windows.RoutedEventArgs) Handles SaveButton.Click
If CustomerDomainDataSource.HasChanges Then
CustomerDomainDataSource.SubmitChanges()
End If
End Sub
This code checks for changes and if the DomainDataSource on the page is changed, it calls SubmitChanges. SubmitChanges first calls the UpdateCustomer method and then the PersistChangeSet method, which in turn calls the Save method of the Customer business object.
STEP 4: Run
If you run the application, you should get this:
This works because when you drag and drop the data source onto the page, Visual Studio defines the labels and textboxes and binds the textboxes to the DomainDataSource that was added in the prior post. The DomainDataSource takes care of managing the updates.
For a more complete example, download the extended source from here.
If you want to learn more about using Silverlight and RIA Services, come and see my talk at VSLive in Redmond Washington August 4, 2010. See this post for more information.
Use these techniques as a starting point when performing update operations using your server-side business layer from a Silverlight application.
Enjoy!
This post demonstrates how to use Silverlight and WCF RIA Services to access YOUR server-side business objects to build a line of business application.
This particular example is as absolutely simple as possible to help you get up and running with these technologies. To meet this goal, this first post covers only how to retrieve data. Later posts will cover update operations.
So first, the prerequisites:
1) Download and install Silverlight 4 and WCF RIA Services 1.0.
Visual Studio 2010 comes with Silverlight 3. You need to download and install the Silverlight 4 Tools for Visual Studio 2010 from here. It comes with WCF RIA Services, so no additional download is required for RIA.
2) Download the Silverlight Toolkit.
This download provides many additional controls and themes. You can download it from here.
3) Build the Business Layer component.
When building a Silverlight/RIA application, you have probably already built your business layer and have business objects that you want to use in your Silverlight application. But if not, you need to do that first.
This example uses an overly simplified business layer component with the Customer class shown below.
Build a standard Class Library project and add the following code:
In C#:
using System.Collections.Generic;
namespace ACM.BLCSharp
{
/// <summary>
/// Manages a customer
/// </summary>
public class Customer
{
public int CustomerId { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string EmailAddress { get; set; }
/// <summary>
/// Retrieves a list of customers.
/// </summary>
/// <returns></returns>
/// <remarks>
/// In a "real" application, this code would
/// call a data access component that retrieves
/// the data from a database.
/// </remarks>
public List<Customer> Retrieve()
{
List<Customer> custList = new List<Customer>
{new Customer()
{ CustomerId = 1,
FirstName="Bilbo",
LastName = "Baggins",
EmailAddress = "bb@hob.me"},
new Customer()
{ CustomerId = 2,
FirstName="Frodo",
LastName = "Baggins",
EmailAddress = "fb@hob.me"},
new Customer()
{ CustomerId = 3,
FirstName="Samwise",
LastName = "Gamgee",
EmailAddress = "sg@hob.me"},
new Customer()
{ CustomerId = 4,
FirstName="Rosie",
LastName = "Cotton",
EmailAddress = "rc@hob.me"}};
return custList;
}
}
}
In VB:
''' <summary>
''' Manages a customer
''' </summary>
''' <remarks></remarks>
''' <editHistory></editHistory>
Public Class Customer
Public Property CustomerId As Integer
Public Property FirstName() As String
Public Property LastName() As String
Public Property EmailAddress() As String
''' <summary>
''' Retrieves a list of customers.
''' </summary>
''' <returns></returns>
''' <remarks>
''' In a "real" application, this code would
''' call a data access component that retrieves
''' the data from a database.
''' </remarks>
Public Function Retrieve() As List(Of Customer)
Dim custList As New List(Of Customer) From
{New Customer() With
{.CustomerId = 1,
.FirstName = "Bilbo",
.LastName = "Baggins",
.EmailAddress = "bb@hob.me"},
New Customer() With
{.CustomerId = 2,
.FirstName = "Frodo",
.LastName = "Baggins",
.EmailAddress = "fb@hob.me"},
New Customer() With
{.CustomerId = 3,
.FirstName = "Samwise",
.LastName = "Gamgee",
.EmailAddress = "sg@hob.me"},
New Customer() With
{.CustomerId = 4,
.FirstName = "Rosie",
.LastName = "Cotton",
.EmailAddress = "rc@hob.me"}}
Return custList
End Function
End Class
NOTE: This code does not use a data access layer at this time. To keep this example as simple as possible, the Retrieve method "mocks" a retrieve that would call your data access component and retrieve the data from the database. This code will be replaced in a later post to actually access a database.
NOTE: Normally I build a Customer class (singular) with the properties and methods that mange a singe customer and a Customers class (plural) that works with a list of those customers. The functionality for both were added to one class to keep this example as simple as possible.
Once the prerequisites are in place, we can get started.
STEP 1: Attribute your business objects to work with Silverlight
There are several different attributes you can define in your business objects so they work better with Silverlight, but in keeping with our goal of creating the simplest Silverlight/RIA/POCO example, you are only required to have one.
For WCF RIA services to work properly, you must have the Key attribute defined on the property that represents the unique key for your business object. This is most often the property of the class that stores the primary key value from your underlying table. In this example, it is the CustomerId property.
The Key attribute is defined in System.ComponentModel.DataAnnotations. So you must add a reference to that namespace and import it to use it.
In C#:
using System.ComponentModel.DataAnnotations;
…
[Key()]
public int CustomerId { get; set; }
In VB:
Imports System.ComponentModel.DataAnnotations
…
<Key()>
Public Property CustomerId As Integer
STEP 2: Add the Silverlight Project
1) Right-click on the solution and select Add | New Project.
2) Select Silverlight | Silverlight Application.
Be sure to select the template under Visual Basic for VB applications or the template under Visual C# for C# applications.
3) Check Enable WCF RIA Services.
Be sure to check the WCF RIA Services checkbox as shown below.
When you add a Silverlight project, Visual Studio creates both a Silverlight project and a Web (ASP.NET) project. The ASP.NET project is crucial because:
- It provides the startup code to start up your Silverlight project.
- It provides the WCF service "behind the scenes" to communicate between your Silverlight application and your business objects.
NOTE: If you already have a Silverlight project, you can still enable WCF RIA Services by accessing the Silverlight project properties and defining the WCF RIA Services link as shown below.
STEP 3: Set a reference in the ASP.NET project to your business layer component.
Select the ASP.NET project. Then add a reference to your business layer component.
The solution for this post has both the C# and VB components. If you are working through this example in your language of choice, you should only have one business layer component project on this list.
By setting this reference, the ASP.NET project code can call your business layer.
STEP 4: Add a Domain Service Class
Add a domain service class to the ASP.NET application. This is the class that calls your business layer methods.
The class must be in the ASP.NET application and not the Silverlight application because you cannot define a direct reference between your Silverlight application and a standard class library project. This makes sense because the Silverlight application runs in the client browser and the ASP.NET application runs on the server where it can access your business layer component.
To add a domain service class, right-click on the ASP.NET application and select Add | New Item. Then pick the Domain Service Class template.
This is where this example varies greatly from an Entity Framework example. If you are using Entity Framework, you will get the list of entities here and can set some entity attributes. If you are using your own business objects, you don't have anything shown in the Entities grid. That's fine. Just click OK.
Since you are building your own Domain Service Class, Visual Studio will inherit your Domain Service class from DomainService. If you were building the class using Entity Framework, the class would instead inherit from LinqToEntitiesDomainService.
In most cases, you will build one Domain Service Class for each business class that you want to access from your Silverlight project.
STEP 5: Add a Query method to the Domain Service class
The query method in the Domain Service class will in turn call the Retrieve method in the business layer component.
In C#:
namespace ACM.SilverlightCSharp.Web
{
using System.Collections.Generic;
using System.ServiceModel.DomainServices.Hosting;
using System.ServiceModel.DomainServices.Server;
using ACM.BLCSharp;
[EnableClientAccess()]
public class CustomerDomainService : DomainService
{
public IEnumerable<Customer> GetCustomers()
{
Customer cust = new Customer();
return cust.Retrieve();
}
}
}
In VB:
Imports System.ServiceModel.DomainServices.Hosting
Imports System.ServiceModel.DomainServices.Server
Imports ACM.BLVB
<EnableClientAccess()> _
Public Class CustomerDomainService
Inherits DomainService
Public Function GetCustomers() As IEnumerable(Of Customer)
Dim cust As New Customer
Return cust.Retrieve
End Function
End Class
A few notes about this GetCustomers method:
- It must be a method and not a property.
- Must return one of the following:
- A single entity
- An IEnumerable<T> where T is the entity
- An IQueryable<T> where T is the entity
- Can have any parameters.
- Can have any name.
- Can (but does not have to) have a Query attribute to define the method as a query.
WCF RIA Services recognizes the method as a query method because of its return value. Therefore, the return value must specify the entity either by returning it or returning an IEnumerable or IQueryable of the entity.
If you build at this point and view all files in your Silverlight project, you will see a Generated_Code folder containing a *.g.cs file. This contains the code generated by Visual Studio and defines the following classes:
- Entity class (Customer in this example): This class is generated from the business layer and includes all of the entity's properties.
- Domain Context class (CustomerDomainContext in this example): This class makes the WCF service calls to the Domain Service class in the ASP.NET project.
In addition, the generated code creates a service contract for the ASP.NET Domain Service class.
[For more information on the generated code, see this post.]
Step 6: Build the UI
Again, keeping with the goal of staying as simple as possible, the UI in this example is a data grid.
1) Open the designer for MainPage.xaml by double-clicking on the file.
2) Open the Data Sources window (Data | Show Data Sources).
Visual Studio automatically added the defined domain context class to your data sources:
Simply drag and drop the Customer data source onto the designer and Visual Studio creates the user interface for you:
STEP 7: Run
If you run the application, you should get this:
This works because when you drag and drop the data source onto the page, Visual Studio defines a DomainDataSource control and a DataGrid control on the page. It sets the DomainDataSource QueryName property to "GetCustomersQuery" and sets the DataGrid ItemsSource property to the DomainDataSource. This causes the code to call your GetCustomers method and use the results to populate the grid.
NOTE: The generated code appends the "Query" suffix to your method name in the domain context class (CustomerDomainContext). That is the method that is called from the UI, which in turn calls the GetCustomers method that you created in the domain service class (CustomerDomainService), which in turn calls the Retrieve method in your business layer via a WCF service.
So your Silverlight application is calling your business layer using the ASP.NET application as a WCF service. Here it is in picture form:
To add a feature to update your data, see this post.
For a more complete example, download the extended source from here.
If you want to learn more about using Silverlight and RIA Services, come and see my talk at VSLive in Redmond Washington August 4, 2010. See this post for more information.
Use these techniques as a starting point when accessing your server-side business layer from a Silverlight application.
Enjoy!
In this prior post, I provided information on registering for the VSLive! conference on August 2-6 in Redmond Washington and on the talks I am presenting at that conference. If you would like a "sneak peak" at the source code for my two talks, you can get it all from my company's Web site.
Enjoy!
If you are working on a large project, your Solution Explorer can get unwieldy and difficult to manage. Solution Folders can help.
Solution folders are logical folder groupings of projects or files within a project.
For example, here is a solution with a set of projects:
There are only 11 projects here, which may be small compared to a "real" application, but it will demonstrate the usefulness of solution folders.
To add folders at the solution level to organize your projects:
- Click on the solution in Solution Explorer.
- Select Add | New Solution Folder.
- Give the folder a logical name.
- Drag and drop projects in Solution Explorer into the new folder.
Note: This does not create folders in your file system. It only defines logical folders within your solution.
After Step 2, the folder then appears in Solution Explorer:
In this example, we want to move all of the test projects into a folder, so the solution folder is named "Test Projects". The test projects are then dragged to the new folder. The result is shown below:
When you aren't working with your test projects, you can close the folder, making Solution Explorer a little easier to work with.
To add folders at the project level to organize your project files:
- Click on the project in Solution Explorer.
- Select Add | New Folder.
- Give the folder a logical name.
- Drag and drop project file in Solution Explorer into the new folder.
Note: This does create folders in your file system and moves the dragged files to that folder.
In this example, we want to group the python files into their own folder.
The result is shown below:
Use this technique any time you want to organize your projects or project files into folders.
Enjoy!
EDIT [7/13/10]: Added a second note to make it clear that folders defined at the solution level do *not* create folders in the file system. Folders defined within a project *do* created folders in the file system.