Silverlight 5 LOB Development : Validation, Advanced Topics, and MVVM

Exclusive offer: get 50% off this eBook here
Mastering LOB Development for Silverlight 5: A Case Study in Action

Mastering LOB Development for Silverlight 5: A Case Study in Action — Save 50%

Develop a full LOB Silverlight 5 application from scratch with the help of expert advice and an accompanying case study with this book and ebook

$35.99    $18.00
by Braulio Díez | March 2012 | Enterprise Articles Microsoft

Most Line of Business (LOB) applications have to interact with a database. The recommended technology in order to cover this interaction is RIA Services.

In this article by Braulio Díez Botella,co-author of Mastering LOB Development for Silverlight 5: A Case Study in Action we will cover:

  • Validation
  • Advanced topics
  • RIA Services and MVVM

(For more resources on Silverlight, see here.)

Validation

One of the most important parts of the Silverlight application is the correct implementation of validations in our business logic. These can be simple details, such as the fact that the client must provide their name and e-mail address to sign up, or that before selling a book, it must be in stock.

In RIA Services, validations can be defined on two levels:

  • In entities, via DataAnnotations.
  • In our Domain Service, server or asynchronous validations via Invoke.

DataAnnotations

The space named System.ComponentModel.DataAnnotations implements a series of attributes allowing us to add validation rules to the properties of our entities. The following table shows the most outstanding ones:

Validation Attribute

Description

DataTypeAttribute

Specifies a particular type of data such as date or

an e-mail

EnumDataTypeAttribute

Ensures that the value exists in an enumeration

RangeAttribute

Designates minimum and maximum constraints

RegularExpressionAttribute

Uses a regular expression to determine valid values

RequiredAttribute

Specifies that a value must be provided

StringLengthAttribute

Designates a maximum and minimum number

of characters

CustomValidationAttribute

Uses a custom method for validation

The following code shows us how to add a field as "required":

[Required()]
public string Name
{
get
{
return this._name;
}
set
{
(...)
}
}

In the UI layer, the control linked to this field (a TextBox, in this case), automatically detects and displays the error. It can be customized as follows:

These validations are based on the launch of exceptions. They are captured by user controls and bound to data elements. If there are errors, these are shown in a friendly way. When executing the application in debug mode with Visual Studio, it is possible to find that IDE captures exceptions. To avoid this, refer to the following link, where the IDE configuration is explained: http://bit.ly/riNdmp.

Where can validations be added? The answer is in the metadata definition, entities, in our Domain Service, within the server project. Going back to our example, the server project is SimpleDB.Web and the Domain Service is MyDomainService. medatada.cs. These validations are automatically copied to the entities definition file and the context found on the client side.

In the Simple.DB.Web.g.cs file, when the hidden folder Generated Code is opened, you will be surprised to find that some validations are already implemented. For example, the required field, field length, and so on. These are inferred from the Entity Framework model.

Simple validations

For validations that are already generated, let's see a simple example on how to implement those of the "required" field and "maximum length":

[Required()]
[StringLength(60)]

public string Name
{
get
{
return this._name;
}
set
{
(...)
}
}

Now, we will implement the syntactic validation for credit cards (format dddddddd- dddd-dddd). To do so, use the regular expression validator and add the server file MyDomainService.metadata.cs, as shown in the following code:

[RegularExpression(@"\d{4}-\d{4}-\d{4}-\d{4}",
ErrorMessage="Credit card not valid format should be:
9999-9999-9999-9999")]
public string CreditCard { get; set; }

To know how regular expressions work, refer to the following link: http://bit.ly/115Td0 and refer to this free tool to try them in a quick way: http://bit.ly/1ZcGFC.

Custom and shared validations

Basic validations are acceptable for 70 percent of validation scenarios, but there are still 30 percent of validations which do not fit in these patterns. What do you do then? RIA Services offers CustomValidatorAttribute. It permits the creation of a method which makes a validation defined by the developer. The benefits are listed below:

  • Its code: The necessary logic can be implemented to make validations.
  • It can be oriented for validations to be viable in other modules (for instance, the validation of an IBAN [International Bank Account]).
  • It can be chosen if a validation is executed on only the server side (for example, a validation requiring data base readings) or if it is also copied to the client.

To validate the checksum of the CreditCard field, follow these steps:

  1. Add to the SimpleDB.Web project, the class named ClientCustomValidation. Within this class, define a static model, ValidationResult, which accepts the value of the field to evaluate as a parameter and returns the validation result.

    public class ClientCustomValidation
    {
    public static ValidationResult ValidMasterCard(string
    strcardNumber)
    }

  2. Implement the summarized validation method (the part related to the result call back is returned).

    public static ValidationResult ValidMasterCard(string
    strcardNumber)
    {
    // Let us remove the "-" separator
    string cardNumber = strcardNumber.Replace("-", "");
    // We need to keep track of the entity fields that are
    // affected, so the UI controls that have this property
    / bound can display the error message when applies
    List<string> AffectedMembers = new List<string>();
    AffectedMembers.Add("CreditCard");
    (...)
    // Validation succeeded returns success
    // Validation failed provides error message and indicates
    // the entity fields that are affected
    return (sum % 10 == 0) ? ValidationResult.Success :
    new ValidationResult("Failed to validate", AffectedMembers);
    }

    To make validation simpler, only the MasterCard has been covered. To know more and cover more card types, refer to the page http://bit.ly/aYx39u. In order to find examples of valid numbers, go to http://bit.ly/gZpBj.

  3. Go to the file MyDomainService.metadata.cs and, in the Client entity, add the following to the CreditCard field:

    [CustomValidation(typeof(ClientCustomValidation),
    "ValidMasterCard")]
    public string CreditCard { get; set; }

If it is executed now and you try to enter an invalid field in the CreditCard field, it won't be marked as an error. What happens? Validation is only executed on the server side. If it is intended to be executed on the client side as well, rename the file called ClientCustomValidation.cs to ClientCustomValidation.shared. cs. In this way, the validation will be copied to the Generated_code folder and the validation will be launched.

In the code generated on the client side, the entity validation is associated.

/// <summary>
/// Gets or sets the 'CreditCard' value.
/// </summary>
[CustomValidation(typeof(ClientCustomValidation),
"ValidMasterCard")]

[DataMember()]
[RegularExpression("\\d{4}-\\d{4}-\\d{4}-\\d{4}", ErrorMessage="Credit
card not valid format should be: 9999-9999-9999-9999")]
[StringLength(30)]
public string CreditCard
{

This is quite interesting. However, what happens if more than one field has to be checked in the validation? In this case, one more parameter is added to the validation method. It is ValidationContext, and through this parameter, the instance of the entity we are dealing with can be accessed.

public static ValidationResult ValidMasterCard( string strcardNumber,
ValidationContext validationContext)
{
client currentClient =
(client)validationContext.ObjectInstance;

Entity-level validations

Fields validation is quite interesting, but sometimes, rules have to be applied in a higher level, that is, entity level. RIA Services implements some machinery to perform this kind of validation. Only a custom validation has to be defined in the appropriate entity class declaration.

Following the sample we're working upon, let us implement one validation which checks that at least one of the two payment methods (PayPal or credit card) is informed. To do so, go to the ClientCustomValidation.shared.cs (SimpleDB web project) and add the following static function to the ClientCustomValidation class:

public static ValidationResult ValidatePaymentInformed(client
CurrentClient)
{
bool atLeastOnePaymentInformed = ((CurrentClient.PayPalAccount !=
null
&& CurrentClient.PayPalAccount != string.Empty) ||
(CurrentClient.CreditCard != null && CurrentClient.CreditCard !=
string.Empty));
return (atLeastOnePaymentInformed) ?
ValidationResult.Success : new ValidationResult("One payment method
must be informed at least");
}

Next, open the MyDomainService.metadata file and add, in the class level, the following annotation to enable that validation:

[CustomValidation(typeof(ClientCustomValidation),
ValidatePaymentInformed")]

[MetadataTypeAttribute(typeof(client.clientMetadata))]
public partial class client

When executing and trying the application, it will be realized that the validation is not performed. This is due to the fact that, unlike validations in the field level, the entity validations are only launched client-side when calling EndEdit or TryValidateObject. The logic is to first check if the fields are well informed and then make the appropriate validations.

In this case, a button will be added, making the validation and forcing it to entity level.

To know more about validation on entities, go to http://bit.ly/qTr9hz.

Define the command launching the validation on the current entity in the ViewModel as the following code:

private RelayCommand _validateCommand;
public RelayCommand ValidateCommand
{
get
{
if (_validateCommand == null)
{
_validateCommand = new RelayCommand(() =>
{
// Let us clear the current validation list
CurrentSelectedClient.ValidationErrors.Clear();
var validationResults = new List<ValidationResult>();
ValidationContext vcontext = new
ValidationContext(CurrentSelectedClient, null, null);
// Let us run the validation
Validator.TryValidateObject(CurrentSelectedClient, vcontext,
validationResults);
// Add the errors to the entities validation error
// list
foreach (var res in validationResults)
{
CurrentSelectedClient.ValidationErrors.Add(res);
}
},(() => (CurrentSelectedClient != null)) );
}
return _validateCommand;
}
}

Define the button in the window and bind it to the command:

<Button
Content="Validate"
Command="{Binding Path=ValidateCommand}"
/>

While executing, it will be appreciated that the fields be blank, even if we click the button. Nonetheless, when adding a breaking point, the validation is shown. What happens is, there is a missing element showing the result of that validation. In this case, the choice will be to add a header whose DataContext points to the current entity. If entity validations fail, they will be shown in this element.

For more information on how to show errors, check the link http://bit.ly/ad0JyD.

The TextBox added will show the entity validation errors. The final result will look as shown in the following screenshot:

Mastering LOB Development for Silverlight 5: A Case Study in Action Develop a full LOB Silverlight 5 application from scratch with the help of expert advice and an accompanying case study with this book and ebook
Published: February 2012
eBook Price: $35.99
Book Price: $59.99
See more
Select your format and quantity:

(For more resources on Silverlight, see here.)

Domain Services validations

All validations made so far could be replicated on the client side. However, there are scenarios where validation must only be executed on the server side, either because it needs to access local resources, such as a database lookup, or because intermediate data used for validations cannot be exposed on the client side due to security reasons.

Let us see how to execute validations on the server side only and how to perform, from our Silverlight application, calls to asynchronous validations.

Server validations

In order to implement a server-side validation, we have the option to define a custom validation (as previously seen) without modifying the name of the file from .cs to .shared.cs. In this way, the validation won't be copied to the client side and will only be executed on the server side. This approach is not wrong, but sometimes it is advisable to be more explicit, that is, before making an insertion or an update, it may be adequate to execute some validations.

RIA Services allows us to launch validation exceptions from an operation in our Domain Service. To see how this works, add the following validation to the UpdateClients method to check whether the credit card number is not used by another user.

The following are the steps:

  1. Define a server-side function to check if the card number is doubled as the following code:

    public bool CreditCardNumberAlreadyExists(client currentclient)
    {
    List<client> cliensWithSameCreditCard = null;
    if (currentclient.CreditCard != null && currentclient.CreditCard
    != string.Empty)
    {
    cliensWithSameCreditCard = (from c in
    this.ObjectContext.clients where c.CreditCard ==
    currentclient.CreditCard && c.ID != currentclient.ID select
    c).ToList<client>();
    }
    return (cliensWithSameCreditCard != null &&
    cliensWithSameCreditCard.Count > 0);
    }

  2. Update the update server-side method. To do so, it is necessary to read the database, check it and, if there is a clash, launch an exception using the following code:

    public void UpdateClient(client currentclient)
    {
    // Is there a collision? Throw the exception
    if (CreditCardNumberAlreadyExists(currentclient))
    {
    // Let us mark the field affected (it will show up the
    // error on the UI binded element)
    ValidationResult error = new ValidationResult("Credit card
    already exists for another account", new string[] {
    "CreditCard" });
    throw new ValidationException(error, null, currentclient);
    }
    // if no error just perform the update
    this.ObjectContext.clients.AttachAsModified(currentclient,
    this.ChangeSet.GetOriginal(currentclient));
    }

  3. Control the client-side error, when the call is made to SubmitChanges. Moreover, it will show an error message if the error occurs.

    _context.SubmitChanges(s =>
    {
    if (s.HasError)
    {
    foreach (var validationError in
    CurrentSelectedClient.ValidationErrors)
    {
    MessageBox.Show(validationError.ErrorMessage);
    }
    s.MarkErrorAsHandled();
    }
    }
    , null);

    To keep the sample as easy as possible, the message is being shown from the ViewModel. If automatic Unit Testing is going to be added later, or if the ViewModel is reused in a WP7 application, you should use one of the mechanisms described in the previous chapter (IDialogService or Messenger) and decouple the UI from the ViewModel.

Asynchronous validations

Server validation, defined in the previous section, is very interesting. Nevertheless, wouldn't it be interesting to make the validation without submitting changes? Yes, it would. The method previously defined, CreditCardNumberAlreadyExists, can be reused and invoked in an asynchronous way.

In this case, add to the validation command, the invoke to the validation itself. When you get the result, check it and, if an error occurs, it is included in the notification to be displayed in the UI.

// Let us perform as well the server invoke (credit card
// validation)
InvokeOperation<bool> inv;
// we will use this to get the result of the operation
inv = _context.CreditCardNumberAlreadyExists(CurrentSelectedClient);
_context.CreditCardNumberAlreadyExists(CurrentSelectedClient).
Completed
+= ((s, e) =>
{
if (inv.Value == true)
{
ValidationResult creditcardExists =
new ValidationResult(
"Credit Card already registered",
new string[] { "CreditCard" });
CurrentSelectedClient.ValidationErrors.Add(creditcardExists);
}
}
);

Advanced topics

Now that we have covered the basics, let's check some advanced topics that we will come across in live project developments.

Cancelling changes

When working with RIA Services, something of a data island is brought client side. We can work with it and, once we are ready, send it to the server. What happens if we want to cancel changes and start again? For instance, a user is modifying a client file and realizes that they are working on the wrong client, so they want to cancel the changes made. The entities with which we are working implement the IRrevertibleChangeTracking. This interface defines a method named Reject, which restores the affected entity and the associated ones (if applicable) to the original value.

In this case, if changes are to be cancelled, it will only be necessary to implement the following code lines (to see it working, press the button Cancel Changes in the sample application):

// Let us clear the current validation list
CurrentSelectedClient.ValidationErrors.Clear();
// Let us cast the entity IRevertibleChange
IRevertibleChangeTracking revertible = CurrentSelectedClient as
IRevertibleChangeTracking;
// Reject Changes
revertible.RejectChanges();

The entity also implements a method called GetOriginal. Why shouldn't it be used? Because it returns a disconnected entity. If the entity had data associated from other entities, they will not be reflected (see association later).

Transactions

Also, it may happen that one of the entities returned an error when trying to save it. In this case, WCF RIA Services calls the function SaveChanges, which internally wraps all those changes (Unit of work) in a transaction. That is, if any of them fail, none of them will be saved.

What if we want to configure the transaction in detail? What if we are not using the ADO.NET Entity Framework? We can override the Submit method and configure the transaction at our convenience (see http://bit.ly/nTv5yb). For more information in this area, refer to the link http://bit.ly/6G0xp4.

Another interesting topic is to add audit and save a changes log. For more information, check the link.

Domain Service and partial classes

At the beginning of this chapter, we pointed out that Visual Studio wizards generate a Domain Service class, which was to be taken as a starting point and customized according to our needs.

What happens if changes are entered in the ADO.NET Entity Framework model? For instance, when adding a new table or changing a field type, is it necessary to regenerate the Domain Service and manually enter customizations again? No, it is not. Partial classes can be used to implement our customized methods. Therefore, we can refresh our Domain Service without the fear of losing all our changes made. Let us now see, step by step, how to add a partial class to the sample.

  1. Go to the server project, Packt.Booking.Server.Web, open the MyDomainService.cs file and add Partial to the class definition.

    public partial class MyDomainService : LinqToEntitiesDomainService
    <SimpleClientEntities>

  2. Add a new class called MyDomainServicep (Add New Class|).
  3. Change the header defining that class by the same one placed previously to create an extension of it:

    public partial class MyDomainService : LinqToEntitiesDomainService
    <SimpleClientEntities>

  4. Add the using namespace as the following code:

    using System.ServiceModel.DomainServices.EntityFramework;
    using System.ComponentModel.DataAnnotations;

  5. Now, open the MyDomainService.cs file and cut the customized methods to paste them in the new file, MyDomainServiceP.cs:

    public partial class MyDomainService : LinqToEntitiesDomainService
    <SimpleClientEntities>
    {
    public bool CreditCardNumberAlreadyExists(client currentclient)
    {
    (...)

    What about the entities file? If changes are not many, it is worthwhile to enter changes manually so as to avoid losing the information we have manually entered (that is, validations).

Include

When having a look at the entities that have been created so far, it is seen that they have links to other related entities. For instance, in the client entity, apart from the country number identifier, we can find a property of the country type. If a breaking point is added when loading these data, the property will be null. What is happening then? By default, the queries generated by RIA Services do not include those bound entities. The bad use of this technique could make our application consume too much bandwidth, as well as resources. What to do then? In the cases where it is justified (for instance, when loading the entity of the associated country or a master-detail association), these entities can be added to our queries.

What if queries return a lot of registers? Associating more entities means more load. The ideal thing to do here is to use pagination (bear in mind that a few users are capable of processing more than 100 registers at one time). To see pagination solutions, check these two links, http://bit. ly/90ZNtA and http://bit.ly/87REa4 (server paging).

Let us see how to include the country entity when loading every client record:

  1. First, edit the Domain Service in the server project and include the entity in the query, bringing the clients as the following code:

    public IQueryable<client> GetClients()
    {
    return this.ObjectContext.clients.Include("country");
    }

  2. Still in the server project, open the file containing the entities, MyDomainService.metadata.cs and search for the client entity. In the nested class, the country property will be found. Add the annotation include to it using the following code:

    public partial class client
    {
    internal sealed class clientMetadata
    {
    (...)
    [Include]
    public country country { get; set; }

  3. Doing so, the country entity of each client will be brought when loading. In the sample, the DataGrid can be modified to add the column indicating the Name field.

    <sdk:DataGrid.Columns>
    <sdk:DataGridTextColumn
    Binding="{Binding Path=country.Name, Mode=OneWay}"/>

Composition

RIA services also knows a special type of association. In a hierarchy of entities, one entity is referred to as the parent entity and the other related entities are referred to as descendant entities. The child entities cannot exist without the master entity and usually these entities are always displayed or modified together. A typical example is a shop system where we have one entity for each order. This entity also has a list of items; each one has a reference to a product and a quantity. In our sample application, we decided to make a composition between a floor and the rooms of this floor.

Most of the readers probably know compositions from UML class diagrams as the following image:

These data classes typically have the following characteristics:

  • The relationship between the entities can be represented as a tree with the descendant entities connected to a single parent entity. The descendant entities can extend for any number of levels. This means that we can also decide to configure a composition between a building and the relating floors. This can be a suitable approach in case that, for instance, if we develop a graphic designer, where all the information is needed to render a map of a given building.
  • The lifetime of a descendant entity is contained within the lifetime of the parent entity. This means if you delete the floor, all rooms will be deleted as well.
  • The descendant entity does not have a meaningful identity outside of the context of the parent entity.
  • Data operations on the entities require the entities to be treated as a single unit. For example, adding, deleting, or updating a record in the descendant entity requires a corresponding change in the parent entity.

Let us move from theory to practical work. Defining a composition with RIA services is very simple, Just apply the CompositionAttribute attribute to the property that defines the association in the metadata of the entity, as in the following code:

[MetadataTypeAttribute(typeof(Floor.FloorMetadata))]
public partial class Floor
{
internal sealed class FloorMetadata
{
[Include]
[Composition]
public EntityCollection<Room> Rooms { get; set; }
}
}

When applying the CompositionAttribute attribute to a property, the data from the descendant entity is not automatically retrieved with the parent entity. To include the descendent entity in the query results, you must apply the IncludeAttribute attribute, as previously described.

Compositions in RIA services gain the following behaviors:

  • Hierarchical change tracking: When a child entity is modified, the parent also transitions to the Modified state. When a parent is in the Modified state, all of its children are included in the change-set that is sent to the server, including any unmodified children. Therefore, our update method must be modified, which will be seen later.
  • Public entity sets for child Types are not generated on the code-generated DomainContext. Children are only accessible via their parent relationship.

Effectively, this means that you do not have to manage your child entities manually. Just create a new room, add this room to the floor where it belongs and call the SaveChanges method of the DomainContext. The client will send all entities, including the child entities, to the server, which updates them in the correct order, for example, first updating the master entity and next an insertion for the child entities.

In addition to this code, our update method must also be changed for the Floor. The problem is that the Entity Framework makes a so called deep-attach, which means that it also adds all child entities of this composition to the DataContext object. If more than one room is added at one Unit of Work to your client context, the system will send all the entities to the server, but two of them have the same primary key.

Therefore, if you do not change the update method, an exception will be thrown with the following message:
InValidOperationException was unhandled by user code: An entity with the same identity already exists in this EntitySet.

public void UpdateFloor(Floor currentFloor)
{
currentFloor.Rooms.Clear();
if (currentFloor.EntityState == EntityState.Detached)
{
Floor original = ChangeSet.GetOriginal(currentFloor);
if (original != null)
{
ObjectContext.Floors.AttachAsModified(currentFloor,
original);
}
else
{
ObjectContext.Floors.Attach(currentFloor);
}
}
foreach (Room change in ChangeSet.
GetAssociatedChanges(currentFloor, p => p.Rooms))
{
ChangeOperation changeOperation =
ChangeSet.GetChangeOperation(change);
switch (changeOperation)
{
case ChangeOperation.Insert:
if (change.EntityState == EntityState.Added) break;
if (change.EntityState != EntityState.Detached)
{
ObjectContext.ObjectStateManager.
ChangeObjectState(change, EntityState.Added);
}
else
{
ObjectContext.Rooms.AddObject(change);
}
break;
case ChangeOperation.Update:
ObjectContext.Rooms.AttachAsModified(change,
ChangeSet.GetOriginal(change));
break;
case ChangeOperation.Delete:
if (change.EntityState == EntityState.Detached)
{
ObjectContext.Rooms.AttachAsModified(change);
}
ObjectContext.DeleteObject(change);
break;
}
}
}

The code is not as complicated as it probably seems. The following steps must be followed:

  1. Remove all child entities from the collection. Do not worry, the changes do not get lost, because the Entity Framework tracks all changes and they will be taken care of later.
  2. If the parent entity is detached, attach it. It is important to check if the original entity is not null. If any property has not been changed directly, but only added, removed, or changed some of its child entities, the original entity will be null and the method AttachAsModified fails.
  3. Get all changed child entities from the change tracker and handle them, depending on their change operation.

You probably recognized that this code snippet will look the same for all compositions you may have in your application and that it is not a good idea to just copy and paste it.

It is good practice to follow the DRY principle (Don't Repeat Yourself), and then provide a generic solution in the demo application, which can be found in the UpdateFloor method of the BookingDomainService.

More information about composition in general can be found at the MSDN website: http://msdn.microsoft.com/ en-us/library/ee707346%28v=VS.91%29.aspx. Some information about why a custom update method is required can be found at http://blogs.msdn.com/b/digital_ ruminations/archive/2009/11/18/compositionsupport- in-ria-services.aspx.

Solving the many-to-many relationship issue

A limitation of RIA services is that it does not support many-to-many relationships (for more information, refer to http://bit.ly/p8O8IE). What workarounds are available?

  • The easiest one consists of adding a dummy field to our table and regenerating the model. The linked table will be shown correctly. Once the update has been made, the additional column can be deleted.
  • Another option, although a little more complicated, consists of adding the entity to the model yourself, mapping it to the linking table, and then adding the foreign key relationships to the other two tables.
  • As a third option, there is a Codeplex project, solving the problem of many-to-many in RIA services (http://bit.ly/g2WrUJ).

RIA services and MVVM

RIA services is a great technology, but how does it fit into the MVVM pattern? Can we easily encapsulate it in a Model? How can we isolate RIA services in the model definition in order to allow developers to implement automated unit testing?

Encapsulating RIA services in a model

When RIA services came into the market, its pros were highlighted as a RAD (Rapid Application Development) technology. It was praised so much that most members of the community have the wrong perception that it cannot be used with applications building an architecture (that is, based on the MVVM pattern).

On the contrary, RIA services can be encapsulated in a model by using one of the following approaches:

  • Database first: get advantage of the entities extracted from the database and use them as the transport layer.
  • Use objects POCO and T4 templates to generate the code. This means hard work (http://bit.ly/eFwcJ2).
  • Use CodeFirst and POCO objects (at the time of printing, the RTM version of the RIA services SP2 was not available yet).

Another wrong perception is that RIA services only works with ADO.NET Entity Framework. In fact, it can be combined with NHibernate and other technologies, although it means more work on our behalf (http://bit.ly/c7zvxN).

Which approach should be taken to implement our model layer?

Define the operations in a contract (interface). The contract will only expose the entities we are dealing with (nothing about context or RIA services particularities).

Implement a model which inherits from this contract, so that:

  • We will work internally with RIA services and instantiate a context to work with.
  • For it to be consumed by one or several ViewModels, we will only deal with the contract previously defined (make sure the RIA services part is present).
  • The model which will be created, unlike other models, will have a status. That is to say, it will bear the record of the elements that have been modified or inserted, for instance. It will track the 'island' of objects or, as in Unit of Work say, anything we have brought from the server. As it has a status, it must be decided how to instantiate it. A singleton for the whole application? A model instance for every ViewModel? In the following section, this issue will be dealt in depth and we will provide a solution based on the Factory pattern.

As in the previous chapter, a contract and a model were defined. In this one, we will see how, except for refactoring and using RIA services entities, we will be able to use it almost entirely and replace the Model Mock implementation with the real one based on RIA services.

Two interesting entries about it can be found on the Internet, by Shawn Wildermuth—RIA services and MVVM (http://bit.ly/bhoap2), and by John Papa—MVVM why and how (http://bit.ly/hWleiP).

Context lifetime discussion and model factory

The RIA services context is inspired by the Entity Framework and other O/R Mapping Tools. Most of them implement the Unit of Work pattern, which is described by Martin Fowler under his site, http://martinfowler.com/ eaaCatalog/unitOfWork.html.

Martin Fowler, a well-known author and software architect, published a list of patterns for Enterprise Applications on his side as a short summary of his book Patterns of Enterprise Application Architecture which can be found at http://bit.ly/2gSwXH

This is how it works:

  1. A Unit of Work is started, typically the first time when data is retrieved or queried from the database. The entities itself will be stored in the session object and the changes are tracked by framework. In RIA services, you start it by instantiating a new context object.
  2. The user manipulates the data and the entities will be added or removed from this context and single properties or even complex relationships are updated and changed. Often, there is also an in-memory caching system to ensure that only one entity exists for one record of the database.
  3. The job is done and when it's time to commit, the framework decides what to do. It can open a new transaction, handle concurrency, and write all changes to the database. In RIA, a commit is done by the SaveChanges method.

This pattern is great and provides a lot of advantages. For example, by writing changes to the database at one point of the time, the system is able to make optimizations, for example, when a lot of entities are added to the session object, it is more efficient to make a bulk insert than a lot of single insert operations. The change tracking system also allows to only update the changed fields, instead of sending the whole entity to the database.

Furthermore, we are free to define what a Unit of Work, in our context, is. For a normal web application, it is very easy. Typically, you define that one request is a Unit of Work. In a desktop or RIA application, it is more complicated and we have multiple options. For the sake of simplicity, we decided to use one domain context in our sample application only and to avoid losing the changes that have to be asked to the user. Whenever we start editing another context, the changes are lost when they do not save it.

But this is not the best approach, especially when we have some background progresses (the Domain Context is not thread-safe) and also for scenarios where the user should be able to modify multiple entities in parallel, for example, when they edit their notes or other documents.

Because Managed Extensibility Framework (MEF) is being used, there is the option to configure our model implementation without using shared instances, which means that each view model gets its own model object. Because this means that the smallest Unit of Work is equal to the lifetime of a view model, a better approach is necessary. Therefore, a model factory that has a method to create a new model must be defined as the following code:

public interface IModelFactory
{
IModel CreateModel();
}

This factory is injected as a shared instance to each view model, and whenever they want to start a Unit of Work, they can use the factory to create a new model object.

In our test scenario, person entities must be edited. The main requirement is that each person can be edited and saved without affecting the other items in our list.

Therefore, implement the following approach:

  1. Use a main model to load all person objects from the RIA service.
  2. Whenever a person is changed, create a new object for this person only to start a new Unit of Work. Now we must detach this person from its old model and attach it to the new model, but because of the fact that the changes are lost if we do so, we have to get a copy of the person from the new model and copy the changes from the old person to the new person.
  3. Replace the old person in the list with the new person object. When it is changed we do not have to do anything because there is already a separate model for this person.
  4. If the user wants to save the person, the SaveChanges method of our model can be used to finish the Unit of Work. This domain context can still be reused in case the person is edited again.

We provide a full example for a very simple scenario, which can be extended following the same approach for more advanced applications.

Summary

In this article, we took a look at validation, advanced topics, RIA Services, and MVVM in Silverlight 5.


Further resources related to this subject:


Mastering LOB Development for Silverlight 5: A Case Study in Action Develop a full LOB Silverlight 5 application from scratch with the help of expert advice and an accompanying case study with this book and ebook
Published: February 2012
eBook Price: $35.99
Book Price: $59.99
See more
Select your format and quantity:

About the Author :


Braulio Díez

Braulio Díez is a freelancer specializing in Microsoft technologies who has more than 15 years of experience working on international projects. He is also a Silverlight MVP, consultant, technical writer, open source developer, trainer, and speaker.

Books From Packt


Microsoft Silverlight 5: Building Rich Enterprise Dashboards
Microsoft Silverlight 5: Building Rich Enterprise Dashboards

Microsoft Silverlight 4 Data and Services Cookbook
Microsoft Silverlight 4 Data and Services Cookbook

Windows Phone 7 Silverlight Cookbook
Windows Phone 7 Silverlight Cookbook

Microsoft Silverlight 4 and SharePoint 2010 Integration
Microsoft Silverlight 4 and SharePoint 2010 Integration

MVVM Survival Guide for Enterprise Architectures in Silverlight and WPF: RAW
MVVM Survival Guide for Enterprise Architectures in Silverlight and WPF: RAW

Microsoft Silverlight 4 Business Application Development: Beginner’s Guide
Microsoft Silverlight 4 Business Application Development: Beginner’s Guide

Microsoft Silverlight 5 Data and Services Cookbook: RAW
Microsoft Silverlight 5 Data and Services Cookbook: RAW

Silverlight 4 User Interface Cookbook
Silverlight 4 User Interface Cookbook


No votes yet

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
Z
D
6
3
m
r
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software