Tuesday, November 24, 2009

RIA POCO DomainService with Existing Business Applications in Silverlight

Your boss suddenly decides that Silverlight is the "next big thing" and asks you to begin converting your line of business application to use the new features. Being a solid architect, of course, you have already built a nice application that is structured in layers. You have a data access layer that abstracts persistence behind interfaces, a domain layer that describes business models, a business logic layer for heavy lifting, a services layer for interconnectivity, and a presentation layer that is ASP.NET or something similar.

RIA services looks promising, but there is one problem. You see that you can create a LINQ to SQL based service, or an Entity Framework service, but you're not comfortable going to production with either of those because then it would introduce an entirely new way of accessing data. You want to keep your data access layer (maybe it's built using Enterprise Library or NHibernate or something similar).

What do you do?

Fortunately, RIA services is not limited to LINQ or EF. You can build a domain service that handles your POCO (plain-ole' CLR objects) domain models. If you are willing to extend your classes a bit and haven't already begun decorating your POCO objects with data annotations, you can handle that, too! Here's how.

Let's assume I've got a decent entity model based on entities that have an integer for their identifier. I do like INotifyPropertyChanged but had reservations about decorating my model with data annotations or validation attributes. Just didn't seem right. So what I have is a base entity that looks like this:


public abstract class BaseEntity : INotifyPropertyChanged
{
    private bool _isDirty = false;

    private bool _isNew = true;

    private int _id = -1;
  
    public virtual int ID
    {
        get { return _id; }
        set
        {
            if (!value.Equals(_id))
            {
                _id = value;
                OnPropertyChanged("ID", true);

                if (_isNew && value > 0)
                {
                    _isNew = false;
                    OnPropertyChanged("IsNew", true);
                }
            }
        }
    }

    public virtual bool IsNew
    {
        get { return _isNew; }
    }

    public virtual bool IsDirty
    {
        get { return _isDirty; }
    }

    public void Reset()
    {
        if (_isDirty)
        {
            _isDirty = false;
            OnPropertyChanged("IsDirty", false);
        }            
    }

    protected void OnPropertyChanged(string property)
    {
        OnPropertyChanged(property, true); 
    }


    private void OnPropertyChanged(string property, bool setDirtyFlag)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(property));
        }

        if (setDirtyFlag)
        {
            if (!_isDirty)
            {
                _isDirty = true;
                if (handler != null)
                {
                    handler(this, new PropertyChangedEventArgs("IsDirty"));
                }
            }
        }
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion

    protected abstract bool _Equals(object obj);

    public override bool Equals(object obj)
    {
        return _Equals(obj);
    }

    public override int GetHashCode()
    {
        return _id.GetHashCode();
    }
}

I have a user entity defined as well. This contains properties for a username, a first and last name, and an email address. Only the username is required. The entity looks like this:


public class UserEntity : BaseEntity 
{
    private string _userName, _firstName, _lastName, _email;

    public virtual string UserName
    {
        get { return _userName; }
        set
        {
            if (value == null || !value.Equals(_userName))
            {
                _userName = value;
                OnPropertyChanged("UserName"); 
            }
        }
    }

    public virtual string FirstName
    {
        get { return _firstName; }
        set
        {
            if (value == null || !value.Equals(_firstName))
            {
                _firstName = value;
                OnPropertyChanged("FirstName"); 
            }
        }
    }

    public virtual string LastName
    {
        get { return _lastName; }
        set
        {
            if (value == null || !value.Equals(_lastName))
            {
                _lastName = value;
                OnPropertyChanged("LastName");
            }
        }
    }

    public virtual string Email
    {
        get { return _email; }
        set
        {
            if (value == null || !value.Equals(_email))
            {
                _email = value;
                OnPropertyChanged("Email");
            }
        }
    }

    protected override bool _Equals(object obj)
    {
        return obj is UserEntity && ((UserEntity)obj).Equals(ID);
    }
}

Note: it's quite possible that you didn't declare your properties as virtual. While I won't make you rewrite your domain model layer, it would be helpful if you're willing to make that one change ... I'll explain why later. Virtual gives us some flexibility to extend later on.

Now for my data access class, I have an interface that defines my basic data handling needs. How I implement these could be anything from NHibernate to Enterprise Library to my own SQL provider that directly accesses the database and parses readers and a data sets into my POCO objects. The point is, I can do this all through a clean interface, like this:


public interface IDataHandler<TEntity> where TEntity : BaseEntity 
{
    TEntity Load(int id);

    void Delete(int id);

    int Save(TEntity entity);

    IEnumerable<TEntity> List();
}

Great. So now I can implement and extend as needed. For example, I might create a concrete UserDataHandler that is based on the IDataHandler<UserEntity> interface. Sound good?

Now we want to build our domain service in RIA but maintain backwards compatibility with our existing system. After all, a large line of business application wasn't written overnight. It could take months to convert all of those screens to Silverlight and we might not even touch them all. It's important to maintain a consistent data access layer so I can fix things in one place and still reuse the existing connectors in my legacy code.

The first thing I'll do is fire up a new Silverlight Business Project. This puts in some plumbing that I might use, or I might throw out, but more importantly it integrates the structure for my Silverlight project to be able to seamlessly communicate with my RIA services.

It makes sense for those of you new to RIA to take a step back and mention two things.

First, RIA is "magic" and exposes a neat class that I can work with, without having to deal with the nuances of WCF services. In the end, however, RIA generates WCF endpoints. These are 100% bona-fide WCF service points that anything can plug into ... I'm just going to do it with my Silverlight application for now.

Second, RIA is "magic" and performs some code generation. I don't see these in a special designer.cs file, but it's there. Code generation isn't a popular term because of some bad projects in the past, so I think the cool word for it now is "projection." We say RIA projects code to my Silverlight application. This means I'll have access to the host web namespace from my Silverlight application.

Let's dig in. What I want to do is create a Domain Service that allows me to reuse my existing data access layer and POCO classes. What I'll do is add a new item to my web application (the one that's hosting the Silverlight, not the Silverlight application itself) and I'll call it LOBDomainService (line of business domain service). The shell that is provided for me looks like this:


[EnableClientAccess()]
public class LOBDomainService : DomainService
{
}

This is the start of a workable service. Because I called my class LOBDomainService, RIA will project for me a LOBDomainContext in the Silverlight client. We'll get to that in a moment.

First, we need to get our data access layer into the domain service. There are several ways to do this:

Factory


IDataHandler<UserEntity> dataHandler = MyDataFactory.GetDataAccess<UserEntity>();

Constructor Injection using something like StructureMap or Unity


private IDataHandler<UserEntity> _dataHandler;

public LOBDomainService(IDataHandler<UserEntity> dataHandler)
{
    _dataHandler = dataHandler;
}


Managed Extensibility Framework (MEF)


[Import]
IDataHandler<UserEntity> DataHandler { get; set; }

... or maybe you just want to new it up. It's up to you.

At this point, however, we have a problem. While I'm able to import my data handler and my objects, the RIA framework doesn't know much about my entities. This is because it depends on data annotations to get hints. With data annotations, I can specify which field is the key field for the entity. I can provide user friendly column names, hints, and descriptions, flag which fields are required and even provide validation.

Some shops may already use these annotations, as they are encapsulated in System.ComponentModel.DataAnnotations and are database and data-strategy independent. However, if you either haven't used these, or simply don't want to "dirty" your base domain models, there is a way to "cheat" a bit and get this to work.

Remember how I mentioned using virtual on your properties would come into play? Let's create a new class called UserEntityExtension. I'll go ahead and base it on my POCO class, but add some annotations.


public class UserEntityExtension : UserEntity
{
    public UserEntityExtension()
    {
    }

    public UserEntityExtension(UserEntity baseEntity)
    {
        if (baseEntity.ID > 0)
        {
            ID = baseEntity.ID;
        }
        UserName = baseEntity.UserName; 
        FirstName = baseEntity.FirstName;
        LastName = baseEntity.LastName;
        Email = baseEntity.Email;
        Reset();
    }

    [Key]
    [ReadOnly(true)]
    [Display(AutoGenerateField=false)]
    public override int ID
    {
        get
        {
            return base.ID;
        }
        set
        {
            base.ID = value;
        }
    }

    [Display(Name="Username")]
    [Required]
    [RegularExpression("^[A-Za-z0-9]+$", ErrorMessage="Please enter a valid user name using only alphanumeric characters without spaces.")]
    public override string UserName
    {
        get
        {
            return base.UserName;
        }
        set
        {
            base.UserName = value;
        }
    }

    [Display(Name="First Name")]
    public override string FirstName
    {
        get
        {
            return base.FirstName;
        }
        set
        {
            base.FirstName = value;
        }
    }

    [Display(Name="Last Name")]
    public override string LastName
    {
        get
        {
            return base.LastName;
        }
        set
        {
            base.LastName = value;
        }
    }

    [Display(Name="Email Address")]
    [RegularExpression(@"^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$", ErrorMessage="Please enter a valid email address.")]
    public override string Email
    {
        get
        {
            return base.Email;
        }
        set
        {
            base.Email = value;
        }
    }

    [Display(AutoGenerateField=false)]
    public override bool IsNew
    {
        get
        {
            return base.IsNew;
        }
    }

    [Display(AutoGenerateField=false)]
    public override bool IsDirty
    {
        get
        {
            return base.IsDirty;
        }
    }
}

As you can see, I'm simply passing through to the base properties, but annotating these with various tags. The constructor takes the base class and populates itself, making it easy to convert from the base type to the extended type. Now I'm ready to complete my domain service model:


[EnableClientAccess()]
public class LOBDomainService : DomainService
{
    public LOBDomainService()
    {
        // MEF set up goes here
    }

    [Import(AllowRecomposition=true)]
    IDataHandler<UserEntity> UserContext { get; set; }

    public IQueryable<UserEntityExtension> GetUsers()
    {
        return UserContext.List().ToList().ConvertAll(u => new UserEntityExtension(u)).AsQueryable(); 
    }

    public void InsertUser(UserEntityExtension entity)
    {
        UserContext.Save(entity);
    }

    public void UpdateUser(UserEntityExtension entity)
    {
        UserContext.Save(entity);
    }

    public void DeleteUser(UserEntityExtension entity)
    {
        UserContext.Delete(entity.ID);
    }
}

The Liskov Substitution Principle (LSP) states that "if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program." In this case, we can safely pass the extended class to our data handler that only knows about the base class without worry.

On the Silverlight side, I can now wire up a fast grid and form to see the results of my hard work. I'll sneak into the Views/Home.xaml template and add the following references:


xmlns:ria="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Ria"
  xmlns:riaData="clr-namespace:System.Windows.Data;assembly=System.Windows.Controls.Ria"
  xmlns:local="clr-namespace:LOBApp.Web.Services"
  xmlns:dataForm="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm.Toolkit"
  xmlns:dataGrid="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"

Then, I'll wire in the data source, a grid, and a data form, like this:

 <ria:DomainDataSource AutoLoad="True" Name="userSource" QueryName="GetUsers">
                    <ria:DomainDataSource.DomainContext>
                        <local:LOBDomainContext/>
                    </ria:DomainDataSource.DomainContext>
                </ria:DomainDataSource>
          
                <dataGrid:DataGrid RowDetailsVisibilityMode="VisibleWhenSelected" SelectionMode="Single" AutoGenerateColumns="True" 
                                   ItemsSource="{Binding Path=Data, ElementName=userSource}">
                    <dataGrid:DataGrid.RowDetailsTemplate>
                        <DataTemplate>
                            <dataForm:DataForm x:Name="dataForm" AutoGenerateFields="True" CurrentItem="{Binding Path=DataContext, ElementName=dataForm}"/>
                        </DataTemplate>
                    </dataGrid:DataGrid.RowDetailsTemplate>
                </dataGrid:DataGrid>
 

With just that little bit of code, I can compile and generate and get something like this (notice it takes on the field names as well as validations, etc):

RIA Services using POCO

Obviously there's much more to do (we haven't touched security or made a submit button or added delete functionality, for example) but this should give you an idea of not only how powerful RIA services truly are, but also how there is plenty in place to enable you to leverage your existing architecture with the new model.

Jeremy Likness

12 comments:

  1. Jeremy:
    Thanks for this interesting post.

    I'm trying a small conversion step using the same style, however i'm stuck with the LOBDomainContext.UserContext ...

    Could you please provide some insight into the next steps following your example (security, submit button, delete button, etc.)?

    Thanks,
    Yarla.

    ReplyDelete
  2. This is great stuff. Do you have a demo app I can play with?

    ReplyDelete
  3. Jeremy,

    Love you writing style.

    Have you figured out how to use the entity poco objects that do have all the required data annotations that also reside in another assembly?

    I've been going crazy trying to actually use my current pocos.

    Cheers,

    Karl

    ReplyDelete
  4. Hi Jeremy,

    This is a great start article. Our current issue is that to leverage it with nHibernate when the entity object has one-to-many relationship to another entity object.

    Eg, Order -< OrderItem,

    How can I attach OrderItems into the Order object and call the Session.Save(Order) to cascade update all the items in the server side?

    ReplyDelete
  5. This is cool - however I have one problem with this that I haven't been able to figure out how to overcome:

    Basically it's your implementation of IQueryable GetUsers()

    return UserContext.List().ToList().ConvertAll(u => new UserEntityExtension(u)).AsQueryable();

    When you do UserContext.List().ToList() you're effectifly pulling back your entire database of users. From a performance standpoint this requires a ton of processing and RAM on the server.

    With any kind of custom data provider (i.e. no LINQ to SQL or LINQ to Entities) I don't see how you can avoid this performance hit.
    Presumably with say LINQ to SQL the IQueryable object you are returning doesn't have to pull anything from the database and the final linq query that client runs is just pushed straight to the database (via LINQ to SQL).

    In my particular case I wanted to use MongoDB, but in order to return IQueryable for my GetItems() method, it was:
    Database.Items.Select(i => new MyEntity(i)).AsIQueryable()
    It's the same performance hit - I have to suck every item from the database.

    Any thoughts, comments?

    ReplyDelete
  6. All great comments. You are correct that IQueryable should only get within the confines of the query. If it is sucking everything back, I'm wondering if this is a limitation of MongoDB (i.e. it only provides the full list). That's the point of IQueryable, is to provide a LINQ construct that enumerates rather than reading to the end of the list.

    ToList will pull it all back, IQueryable should only iterate through the conditions you provide.

    ReplyDelete
  7. Hi Jeremy .. Nice article , but I am new in those things and would like to ask you if you have working project which inplements all you talk in article. Thanks Peter

    ReplyDelete
  8. Hi Jeremy,

    I have a question: Why do you use virtual and not a partial class with the MetadataType annotation?

    ReplyDelete
  9. Because we're not completing the class, we're deriving it to set the additional data annotations.

    ReplyDelete
  10. Thank you for your fast reply.

    ReplyDelete
  11. Hey Jeremy,
    Very good article. Can you please give me idea like if i want to use complex objects as my POCOs and use RIAServices how it will figure out which entity to update ?

    ReplyDelete