Click here to Skip to main content
15,868,016 members
Articles / Web Development / IIS

Creating a Domain Service Factory to Host POCO Entities

Rate me:
Please Sign up or sign in to vote.
4.00/5 (2 votes)
10 Dec 2010CPOL4 min read 38.9K   615   11   2
This article demonstrates how to write a domain service factory to host plain old class object (POCO) entities and use them through RIA Services in Silverlight version 4.

Introduction

If you have not, please see the associated article: POCO Entities Through RIA Service, as this article is the companion to using plain old class objects (POCO) entities through Rich Internet Application (RIA) Services. You could use this example to host anything via the Open Data Protocol (OData), really - not just POCO entities. For this example, you must have the following installed:

Background

A problem arises with using POCOs, which is that you might need to include relationships through LINQ or other data contexts. Microsoft wrote an article on sharing entities using the Include statement, but it does not work with POCOs because they should be context unaware. Also, as is often the case, you might want to join two .edmx files, or provide some highly customized business relationship between your entities, or you might even want to do user access checks before providing data, or you might want to implement the Model View View Model (MVVM) pattern, like this example.

Creating a Domain Service Factory

My WCF RIA Services Class Library project from the previous article (see Introduction) is called EntRiaServices, and it has a sub-project called EntRiaServices.Web. This is where we are going to be creating our Domain Service Classes along with our Domain Service Factory. These are two classes, so right-click on the EntRiaServices.Web sub-project and select Add, New Item, Class.

A Domain Service Factory (DomainServiceFactory.cs) is a class that will produce your available domain services for RIA Services to access:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.DomainServices.Server;

namespace EntRIAServices.Web
{
    public class DomainServiceFactory : IDomainServiceFactory
    {
        private IDomainServiceFactory _defaultFactory;
        private Entities.DBData _dataContext;

        public DomainServiceFactory(IDomainServiceFactory defaultFactory)
        {
            _defaultFactory = defaultFactory;

            // For this I copied the connection string from
            // the web.config file of the BasicDataViewer file
            // (i.e. it's where by .EDMX file is located
            // and the .EDMX generated it for me because I selected
            // Save your connection settings to your Web.config when I created it)
            // I had to modify it to point to the physical
            // location of the metadata files generated by my .EDMX
            // after selecting the Metadata Artifact property to "Copy to Output Directory"
            _dataContext = 
               new Entities.DBData("metadata=C:\\Users\\Owner\\Documents\\" + 
               "Visual Studio 2010\\Projects\\BasicDataViewer\\BasicDataViewer\\" + 
               "bin\\DBData.csdl|C:\\Users\\Owner\\Documents\\Visual Studio 2010\\" + 
               "Projects\\BasicDataViewer\\BasicDataViewer\\bin\\DBData.ssdl|C:\\" + 
               "Users\\Owner\\Documents\\Visual Studio 2010\\Projects\\" + 
               "BasicDataViewer\\BasicDataViewer\\bin\\DBData.msl;provider=" + 
               "System.Data.SqlClient;provider connection string='Data Source=" + 
               "YOUR_PC_NAME\\YOUR_SQL_SERVER_NAME;Initial Catalog=YOUR_DB_NAME;" + 
               "User ID=DB_USER_NAME;Password=YOUR_DB_PASSWORD;" + 
               "MultipleActiveResultSets=True'");
        }

        public DomainService CreateDomainService(Type domainServiceType, 
                             DomainServiceContext context)
        {

            // Here is where you would filter your entities based
            // on user information and/or load multiple entities by 
            // passing in relationship objects
            if ((domainServiceType == typeof(AccessableDomainService)))
            {
                
                DomainService ds = (DomainService)Activator.CreateInstance(
                    domainServiceType, new object[] { this._dataContext });
                
                ds.Initialize(context);
                return ds;
            }
            else
            {
                return _defaultFactory.CreateDomainService(domainServiceType, context);
            }

        }

        public void ReleaseDomainService(DomainService domainService)
        {
            domainService.Dispose();
        }
    }
}

The second class (AccessableDomainService.cs), or however many you want, is your accessible Domain Service(s). All of your Domain Services should expose subroutines marked with [Query], [Update], [Insert], and [Delete] attributes:

C#
using System.Data.Objects;

namespace EntRIAServices.Web
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.Linq;
    using System.ServiceModel.DomainServices.Hosting;
    using System.ServiceModel.DomainServices.Server;

    // TODO: Create methods containing your application logic.
    [EnableClientAccess()]
    public class AccessableDomainService : DomainService
    {

        private Entities.DBData _context;
        protected string EntitySetName { get; private set; }

        public AccessableDomainService(Entities.DBData dataContext)
        {
            this._context = dataContext;
            fFetchEntitySetName();
        }

        //  This gets the entity set name "Zip"
        private void fFetchEntitySetName()
        {
            var entitySetProperty =
              this._context.GetType().GetProperties().Single(
              p => p.PropertyType.IsGenericType && 
              typeof(IQueryable<>).MakeGenericType(
              typeof(Entities.Zip)).IsAssignableFrom(p.PropertyType));

            this.EntitySetName = "DBData." + entitySetProperty.Name;
        }

        [Query(IsDefault = true)]
        public IQueryable<entities.zip> GetByDallas()
        {
            return (IQueryable<entities.zip>) this._context.Zips.Where(
                            x => x.City == "Irving");
        }

        [Update]
        public virtual void SaveCommand(Entities.Zip oZipToSave)
        {
            object oOriginalItem;

            if (this._context.TryGetObjectByKey(new System.Data.EntityKey(
                this.EntitySetName, "ZipCode", oZipToSave.ZipCode), 
                out oOriginalItem))
            {
                this._context.ApplyCurrentValues(this.EntitySetName, oZipToSave);
            }
            else
            {
                this._context.AddObject(this.EntitySetName, oZipToSave);
            }

            this._context.SaveChanges();
        }

        [Insert]
        public virtual void Add(Entities.Zip oNewZip)
        {
            this._context.AddObject(this.EntitySetName, oNewZip);
            this._context.SaveChanges();
        }

        [Delete]
        public virtual void Delete(Entities.Zip oZip)
        {
            this._context.DeleteObject(oZip);
            this._context.SaveChanges();
        }


    }
}

As you can see from these two classes, the factory overrides the CreateInstance of the particular class and passes in the data context. You could pass in anything at this point, including relationship objects. Another thing you could do is if all of your Domain Services for a particular POCO have the same interface, then you could use polymorphism while associating your Silverlight .xaml with a "UI approved" Domain Service from that set.

Connecting Silverlight to Your Domain Service Factory

My SilverLight Application project from the previous article (see Introduction) is called BusinessApplication1 or BusinessApp. I had to drag a Global.asax in from another project, but if you know how to create one, go for it. The domain service factory can be changed from anywhere in your code, but I like it here:

C#
using System;
using System.Web.DynamicData;
using System.Web.Routing;
using System.ServiceModel.DomainServices.Server;

namespace BusinessApp.Web
{
    public class Global : System.Web.HttpApplication
    {

        void Application_Start(object sender, EventArgs e)
        {
            if (!(DomainService.Factory is 
                    EntRIAServices.Web.DomainServiceFactory))
            {
                DomainService.Factory = 
                  new EntRIAServices.Web.DomainServiceFactory(DomainService.Factory);
            }
        }

    }
}

Next, you are going to update your .XAML file to include a button. If you were following the previous article, you should already have data on your .xaml and the riaControls:DomainDataSource.DomainContext reference setup. The data context in my example is named zipDomainDataSource. I have a demo Save button called Button1. Here is a quick test for the code-behind to perform an update; you will need to press F5 after entering this into your .xaml.cs for your button:

XAML:
XML
<navigation:Page x:Class="BusinessApp.Page1" 
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   mc:Ignorable="d"
   xmlns:navigation="clr-namespace:System.Windows.Controls;
                     assembly=System.Windows.Controls.Navigation"
   d:DesignWidth="640" d:DesignHeight="480"
   Title="Page1 Page" 
   xmlns:riaControls="clr-namespace:System.Windows.Controls;
                     assembly=System.Windows.Controls.DomainServices" 
   xmlns:my="clr-namespace:EntRIAServices.Web;assembly=EntRIAServices" 
   xmlns:my1="clr-namespace:Entities;assembly=EntRIAServices"
   xmlns:myApp="clr-namespace:BusinessApp"
   xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">
        <riaControls:DomainDataSource AutoLoad="True" 
   d:DesignData="{d:DesignInstance my1:Zip, CreateList=true}" 
   Height="0" LoadedData="zipDomainDataSource_LoadedData" 
   Name="zipDomainDataSource" QueryName="GetZipsQuery" Width="0">
            <riaControls:DomainDataSource.DomainContext>
                <my:EntityDomainContext />
            </riaControls:DomainDataSource.DomainContext>
        </riaControls:DomainDataSource>
    <UserControl.Resources>
        <myApp:ZipViewModel x:Key="zipDomainDataSource"></myApp:ZipViewModel>
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot">
        <Grid DataContext="{Binding ElementName=zipDomainDataSource, Path=Data}" 
          HorizontalAlignment="Left" Margin="319,0,0,311" 
          Name="grid1" VerticalAlignment="Bottom">
        <Grid HorizontalAlignment="Left" 
               Margin="114,166,0,0" Name="grid3" 
               VerticalAlignment="Top">
            <Grid.DataContext>
                <Binding Source="{StaticResource zipDomainDataSource}" Path="Data" />
            </Grid.DataContext>            
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <sdk:Label Content="Tax Rate:" Grid.Column="0" Grid.Row="0" 
              HorizontalAlignment="Left" Margin="3" 
              VerticalAlignment="Center" />
            <TextBox Grid.Column="1" Grid.Row="0" Height="23" 
               HorizontalAlignment="Left" Margin="3" 
               Name="taxRateTextBox" 
               Text="{Binding Path=TaxRate, Mode=TwoWay, 
                     NotifyOnValidationError=true, ValidatesOnExceptions=true}" 
               VerticalAlignment="Center" Width="120" />
        </Grid>
        <Button Click="button1_Click" Content="Button" 
           Height="40" HorizontalAlignment="Left" 
           Margin="116,262,0,0" Name="button1" 
           VerticalAlignment="Top" Width="132" >
        </Button>
        <Button Command="{Binding SaveMe}" 
                Content="Button" Height="40" 
                HorizontalAlignment="Left" Margin="116,262,0,0" 
                Name="button1" VerticalAlignment="Top" Width="132" >
            <Button.DataContext>
                <Binding Source="{StaticResource zipDomainDataSource}" />
           </Button.DataContext>
        </Button>
        
    </Grid>
</navigation:Page>
.xaml.cs
C#
private void button1_Click(object sender, RoutedEventArgs e)
{
    zipDomainDataSource.SubmitChanges();
}

Abstracting the Interface

This is included in the downloadable project, so if you are after the MVVM pattern using POCOs, here is my View Model and associated classes. These classes are in my Silverlight Application project.

The View Model class provides access to the data, and exposes commands that can be called from the UI using delegates (ZipViewModel.cs):

C#
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.ServiceModel.DomainServices.Client;
using Entities;
 
namespace BusinessApp
{
    public class ZipViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
 
        public ICommand SaveMe { get; set; }
 
        private System.Collections.IEnumerable _myData;
        private ICollectionView _myDataView;
        private DomainDataSource _myDomainDataSource;
 
        public ZipViewModel()
        {
 
            // Initialize the object and set the domain context
            this._myDomainDataSource = new DomainDataSource();
            this._myDomainDataSource.AutoLoad = true;
            this._myDomainDataSource.Name = "zipDomainDataSource";
            this._myDomainDataSource.LoadingData += 
              new EventHandler<loadingdataeventargs>(_zipDomainDataSource_LoadingData);
            this._myDomainDataSource.LoadedData += 
              new EventHandler<loadeddataeventargs>(_zipDomainDataSource_LoadedData);
            this._myDomainDataSource.DomainContext = 
              new EntRIAServices.Web.AccessableDomainContext();
 
            // Load our data source
            this._myDomainDataSource.QueryName = "GetDefaultQuery";
            this._myDomainDataSource.Load();
 
            // Link commands
            this.SaveMe = new DelegateCommand(SaveCommand, SaveCommand_CanExecute);
 
            // Set vars
            this._myData = this._myDomainDataSource.Data;
            this._myDataView = this._myDomainDataSource.DataView;
        }
 
        void _zipDomainDataSource_LoadingData(object sender, LoadingDataEventArgs e)
        {
            
        }
 
        void _zipDomainDataSource_LoadedData(object sender, LoadedDataEventArgs e)
        {
        }
 
        public System.Collections.IEnumerable Data
        {
            get { return this._myData; }
            set { this._myData = value;  }
        }
 
        public ICollectionView DataView
        {
            get { return this._myDataView; }
        }
 
        private void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
 
        private void SaveCommand(object parameter) {
            this._myDomainDataSource.DomainContext.SubmitChanges();
        }
 
        public bool SaveCommand_CanExecute(object parameter)
        {
            return true;
        }
     }
}

The View Model class calls this custom handler class to wrap the common command interface:

C#
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Input;

namespace BusinessApp
{

     public class DelegateCommand : ICommand   {   
         Func<object, bool> canExecute;   
         Action<object> executeAction;   
         bool canExecuteCache;   
         
         public DelegateCommand(Action<object> 
                executeAction, Func<object, bool> canExecute)   
         {   
             this.executeAction = executeAction;  
             this.canExecute = canExecute;  
         }  
         
         #region ICommand Members  
         
         public bool CanExecute(object parameter)  
         {  
             bool temp = canExecute(parameter);  
             
             if (canExecuteCache != temp)  
             {  
                 canExecuteCache = temp;  
                 if (CanExecuteChanged != null)  
                 {  
                     CanExecuteChanged(this, new EventArgs());  
                 }  
             }  
             
             return canExecuteCache;  
         }  
         
         public event EventHandler CanExecuteChanged;  
         
         public void Execute(object parameter)  
         {  
             executeAction(parameter);  
         }  
         
         #endregion  
     }
}

Lastly, I want to access the View Model class directly from my .xaml file; you can remove all references to RIA Services and do late binding like this if you want:

C#
namespace BusinessApp
{
    public partial class Page1 : Page
    {
        public Page1()
        {
            DataContext = new ZipViewModel();
            InitializeComponent();
        }

        // Executes when the user navigates to this page.
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
        }
    }
}

However, it is my preference to reference the View Model directly from the .xaml file because then we can see the object in our data sources. All you have to do is create a reference to your namespace in the page definition: xmlns:myApp="clr-namespace:BusinessApp". Then just reference that resource:

XML
<UserControl.Resources>
    <myApp:ZipViewModel x:Key="zipDomainDataSources"></myApp:ZipViewModel>
</UserControl.Resources>

Using the context in a control is done by setting the explicit data context:

XML
<Button.DataContext>
    <Bind Source="{StaticResource zipDomainDataSource}" Path="Data" />
</Button.DataContext>

Command callbacks can then be done by calling the iCommand on your View Model (see the stricken portion from the above .xaml):

Command = "{Binding SaveMe}"

Conclusion

Here is a sample of this solution; you will need to use 7Zip to extract these files because of the high rate of compression:

As you can see, having a domain service factory adds a tremendous amount of flexibility to how domain services are provided, and keeps our POCO DLLs context unaware. Concluding this article, the attached solution demonstrates a Silverlight 4 solution using the Model View View Model (MVVM) pattern and POCO entities. I hope this helps all of you C# business application developers out there, because this technology is still fairly new, and it is hard to find good resources.

License

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


Written By
Unknown
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralPOCO - plain old class objects Pin
JohanJvR13-Dec-10 4:13
JohanJvR13-Dec-10 4:13 
GeneralRe: POCO - plain old class objects Pin
Beavis Killer13-Dec-10 6:47
Beavis Killer13-Dec-10 6:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.