Click here to Skip to main content
15,867,594 members
Articles / Web Development / ASP.NET

RESTful WCF / EF POCO / Unit of Work / Repository / MEF: 1 of 2

Rate me:
Please Sign up or sign in to vote.
4.97/5 (114 votes)
19 Apr 2012CPOL18 min read 457.2K   6.8K   331   183
A look at designing a service layer using good practices, and the MSFT technology stack.

Table of Contents

Introduction 

This article is a strange one, in that it is a bit contrived, but numerous people have asked me to create a LOB (line of business) app that shows how I would typically structure my layers between a front end and a database (this was mainly asked by users of my Cinch MVVM framework).

Now although this article uses a simple console application as its front end, my intention is to make this a two-part article, which I will revisit and expand upon when I have some more time, which may be a while as I am working on something rather large outside of this article.

That said, this article showcases quite a few things which I tend to use, and I thought it might make half a decent article so I thought why not just write up what I can right now and let the rest follow when it's ready.

So what does this demo app actually do? Put simply, it is a simple console app that retrieves and adds some data from a single database table. It uses LINQ to Entities and Domain Driven Design (DDD), as such it makes use of the Repository Pattern, and it also has some added extras like how to use some typical enterprise level things like:

  • Inversion of Control (I am using the new MEF drop)
  • log4Net
  • Unit of Work working with repositories
  • RESTful WCF using the new WCF WebApis

Note: I have specifically made this demo code pretty dumb so people can pick up the concepts, there is nothing worse than wading through tons of code when you are trying to learn something small. So please keep that in mind as you read the rest of the article.

Prerequisites

There are quite a few prerequisites, however most of them are included as part of the attached demo code. The table below shows them all and tells you whether they are included in the attached demo code, or whether you really must have them in order to run the code:

ItemIncluded
SQL ServerNo

You must have this already.

Entity Framework 4.1 POCOYes

See Lib\Entity Framework 4.1\EntityFramework41.exe

log4NetYes

See Lib\Log4Net\1.2.10.0\log4net.dll.

Lib\MEF 2Yes

See Lib\MEF 2\System.ComponentModel.Composition.CodePlex.dll

See Lib\MEF 2\System.ComponentModel.Composition.Registration.CodePlex.dll

See Lib\MEF 2\System.ComponentModel.Composition.Web.Mvc.CodePlex.dll

See Lib\MEF 2\System.Reflection.Context.CodePlex.dll

Lib\WCF Web API Preview 4Yes

See Lib\WCF Web API Preview 4

But to be honest, you would be better getting this stuff from the Nuget package

Now some of you may note that there are probably later versions of some of these, and whilst that is true, this articles code was kind of extracted as part of a bigger thing I am working on and I know these versions work together, so those are the versions I opted for.

General Design / Getting Started

The basic design is pretty easy and works like this:

We have a simple two table database (which you can setup using the attached demo code scripts) which we want to allow the user to be able to add to and query. We want to be able to do this using a WCF service and be able to do this using modern techniques such as IOC/Logging, and provide proper separation of concerns, and also maintain the ability to alter/test any part of the system either by replacing the implementation for another one or by using mocks.

This diagram illustrates the core layers of the attached demo project. As I previously stated, the attached demo project is very simple, which I did on purpose, but even with only two tables and no real business logic, we are able to fulfill all of the requirements above.

Image 1

Getting Started

To get started, you will need to do the following:

  1. Create a new SQL Server database called "TESTEntites" which you may do using the two scripts in the "getting started" solution folder.
  2. Change the Web.Config in Restful.WCFService to point to your newly created database and SQL Server installation.

RESTful API

The WCF WebApi that Glenn Block and his team have created is a pretty cool thing, it kind of brings stuff that we know and love from WCF land and allows them to be easily exposed over the web via JSON or XML serialization.

Back in .NET 3.5 SP1, we were able to do this using a limited number of attributes such as WebGet/WebInvoke on a standard WCF service which could then be hosted in a specialised web service host, namely the WebServiceHost. This was pretty cool at the time, and I wrote about this in the following links should you want to look at them:

  1. http://sachabarber.net/?p=460
  2. http://sachabarber.net/?p=475
  3. http://www.codeproject.com/KB/smart/GeoPlaces.aspx

But that was then and this is now. Now what is available is the new WCF WebApi Glenn's team has put together.

So what is so cool about this new offering? Well, here is what I consider to be the plus points:

  1. It is just a WCF service really
  2. It doesn't really need a specialized host as such
  3. It truly is RESTful
  4. Can be called using simple web client-side code, such as jQuery
  5. Supports oData (allows simple queries using a URL)

These are all pretty good reasons to consider it I feel.

So what does one of these services look like? Well, for the demo code service, the complete code is as follows (note: this makes use of MEF / Unit of Work pattern / Repository pattern / oData / EF 4.1, all of which we will talk about shortly):

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Net;
using System.Net.Http;
using Microsoft.ApplicationServer.Http.Dispatcher;
using Restful.Models;
using Restful.Models.Dtos;
using System.ComponentModel.Composition;
using Restful.Entities;
using Restful.WCFService.Services.Contracts;
using Models.Utils;

namespace Restful.WCFService.WebApi
{
    [ServiceContract]
    [Export]
    public class EFResource : IDisposable
    {
        IUnitOfWork unitOfWork;
        IRepository<Author> authorsRep;
        IRepository<Book> booksRep;
        ILoggerService loggerService;

        [ImportingConstructor]
        public EFResource(
            IUnitOfWork unitOfWork,
            IRepository<Author> authorsRep,
            IRepository<Book> booksRep,
            ILoggerService loggerService)
        {
            this.unitOfWork = unitOfWork;
            this.authorsRep = authorsRep;
            this.booksRep = booksRep;
            this.loggerService = loggerService;
        }

        [WebGet(UriTemplate = "Authors")]
        public IQueryable<DtoAuthor> GetAuthors()
        {

            loggerService.Info("Calling IQueryable<DtoAuthor> GetAuthors()");

            
            authorsRep.EnrolInUnitOfWork(unitOfWork);
            List<Author> authors =
                     authorsRep.FindAll("Books").ToList();

            IQueryable<DtoAuthor> dtosAuthors = authors
                     .Select(a => DtoTranslator.TranslateToDtoAuthor(a))
                     .ToList().AsQueryable<DtoAuthor>();

            return dtosAuthors;
            
        }

        [WebGet(UriTemplate = "Books")]
        public IQueryable<DtoBook> GetBooks()
        {
            loggerService.Info("Calling IQueryable<DtoBook> GetBooks()");

            
            booksRep.EnrolInUnitOfWork(unitOfWork);
            List<Book> books = 
                    booksRep.FindAll().ToList();

            IQueryable<DtoBook> dtosBooks = books
                     .Select(b => DtoTranslator.TranslateToDtoBook(b))
                     .ToList().AsQueryable<DtoBook>();

            return dtosBooks;
            
        }

        [WebInvoke(UriTemplate = "AddAuthor", Method = "POST")]
        public Restful.Models.Dtos.DtoAuthor AddAuthor(DtoAuthor dtoAuthor)
        {

            loggerService.Info("Restful.Models.Author AddAuthor(DtoAuthor dtoAuthor)");

            if (dtoAuthor == null)
            {
                loggerService.Error("author parameter is null");
                throw new HttpResponseException(HttpStatusCode.BadRequest);
            }

            
            authorsRep.EnrolInUnitOfWork(unitOfWork);
            Author author = EfTranslator.TranslateToEfAuthor(dtoAuthor);
            authorsRep.Add(author);
            unitOfWork.Commit();
            dtoAuthor.Id = author.Id;
            return dtoAuthor;
        }

        [WebInvoke(UriTemplate = "AddBook", Method = "POST")]
        public Restful.Models.Dtos.DtoBook AddBook(DtoBook dtoBook)
        {
            loggerService.Info("Restful.Models.Book AddBook(DtoBook dtoBook)");

            if (dtoBook == null)
            {
                loggerService.Error("book parameter is null");
                throw new HttpResponseException(HttpStatusCode.BadRequest);
            }

            
            booksRep.EnrolInUnitOfWork(unitOfWork);
            Book book = EfTranslator.TranslateToEfBook(dtoBook);
            booksRep.Add(book);
            unitOfWork.Commit();
            dtoBook.Id = book.Id;
            
            return dtoBook;
        }


        public void Dispose()
        {
            unitOfWork.Dispose(); 
        }     
    }
}

It can be seen that if we took the actual code in the methods away, we would simply be left with a couple of methods that could be invoked by calling a URL. For example, to add a new EF 4.1 Book, I could simply do this:

http://localhost:8300/ef/AddBook

Where the POST request would contain a new Book object.

And how about a GET request that returns all Book objects? That is just this sort of GET request:

http://localhost:8300/ef/Books

Here is screenshot from a browser where I have simply navigated to that URL. See how it shows all the books I have in my database installation (you will have to set your own database up):

Image 2

You may notice that this is in XML format. That can easily be changed by changing RequestFormat and ResponseFormat and BodyFormat of the WebGet and WebInvoke attributes used on the service.

So what about oData support? In fact, if you do not know what oData is, here is what www.oData.org says about it:

There is a vast amount of data available today and data is now being collected and stored at a rate never seen before. Much, if not most, of this data however is locked into specific applications or formats and difficult to access or to integrate into new uses.

The Open Data Protocol (OData) is a Web protocol for querying and updating data that provides a way to unlock your data and free it from silos that exist in applications today. OData does this by applying and building upon Web technologies such as HTTP, Atom Publishing Protocol (AtomPub), and JSON to provide access to information from a variety of applications, services, and stores. The protocol emerged from experiences implementing AtomPub clients and servers in a variety of products over the past several years. OData is being used to expose and access information from a variety of sources including, but not limited to, relational databases, file systems, content management systems, and traditional Web sites.

OData is consistent with the way the Web works - it makes a deep commitment to URIs for resource identification and commits to an HTTP-based, uniform interface for interacting with those resources (just like the Web). This commitment to core Web principles allows OData to enable a new level of data integration and interoperability across a broad range of clients, servers, services, and tools.

Well, that too is built in, all we need to do is supply some extra parts. For example, here is how I would get the book on the top:

http://localhost:8300/ef/Books?$top=1

Which yeilds these results:

Image 3

As far as hosting one of these types of services, there is very little to it. There is this bit of Web.Config code:

XML
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.serviceModel>
    <serviceHostingEnvironment 
    aspNetCompatibilityEnabled="true" />
  </system.serviceModel>
</configuration>

And this bit of Global.asax.cs (if you use C#), and that's it:

C#
using Microsoft.ApplicationServer.Http.Activation;
using System;
using System.ServiceModel.Activation;
using System.Web.Routing;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Registration;
using System.ComponentModel.Composition;
using Restful.Entities;
using Restful.WCFService.Services.Implementation;
using Restful.WCFService.Services.Contracts;
using log4net.Config;
using Restful.WCFService.Mef;
using Restful.WCFService.WebApi;

namespace Restful.WCFService
{
    public class Global : System.Web.HttpApplication
    {
        private void Application_Start(object sender, EventArgs e)
        {
            // setting up Web Api WCF Service route
            RouteTable.Routes.MapServiceRoute<EFResource>("ef");
        }
    }
}

The other point of interest is that we never actually serialize and send the raw Entity Framework POCO classes, what we do instead is use "Data Transfer Objects" DTOS, which we translate to and from Entity Framework POCO classes. The general rule of thumb being:

  • Entity Framework POCO class: Used for server side persistence
  • DTO class: Sent from/to the WCF WebApi service

How to Secure the REST Service

One of the readers of this article actually noted that I had nothing mentioned about security, which is a valid point, I did not mention that at all. The main reason being that for my OSS project, we are not actually using the WCF WebApis but use ASP MVC, where I hook into the standard ASP authorization methods.

That said, the same reader (RAbbit12, thank you) also gave a nice link to an article which discusses how to use attributes (similar to what ASP MVC actually does) to apply Authorization to a WCF WebApi service. Here is a link to that article: http://haacked.com/archive/2011/10/19/implementing-an-authorization-attribute-for-wcf-web-api.aspx.

MEF

When designing our systems at work, we typically choose to abstract away any system boundaries, such that these can easily be replaced or mocked if a system is down, or we wish to use a test version of something. We would typically expect to be able to do this at the highest level and have all our dependencies resolved from an IOC container. Which for us means that we would expect our entire WCF service to be part of the IOC container, which would take dependencies on other services/repositories or helpers.

We find that abstracting these services/repositories/helpers behind interfaces greatly increases our ability to swap out any part of the system, and also to create Mocked versions of them. MEF is my IOC container of choice these days, as I like the metadata, so that is what this article focuses on.

The following sections talk about the new MEF APIs that we will one day get within .NET.

Imports

When using MEF, one of the first things that needs to be done is to define what Import/Exports we wish to use. MEF V2 has changed a bit from V1, in that we no longer need to supply ImportAttribute and ExportAttribute markets to our classes, this should all be handled by the new and improved registration process, but I still prefer to use them, as it shows me clearly where things are coming from and where I can expect to fault find if they are not working.

So here is the demo WCF WebApi with all its expected Imports, you may also note that the entire WCF WebApi service is also being Exported. This allows the entire graph dependency to be satisfied when you obtain an instance of this service. Believe me, that is what you want, manually obtained service location objects can be a bugger to find if you miss one in a test.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Net;
using System.Net.Http;
using Microsoft.ApplicationServer.Http.Dispatcher;
using Restful.Models;
using Restful.Models.Dtos;
using System.ComponentModel.Composition;
using Restful.Entities;
using Restful.WCFService.Services.Contracts;
using Models.Utils;

namespace Restful.WCFService.WebApi
{
    [ServiceContract]
    [Export]
    public class EFResource : IDisposable
    {
        IUnitOfWork unitOfWork;
        IRepository<Author> authorsRep;
        IRepository<Book> booksRep;
        ILoggerService loggerService;

        [ImportingConstructor]
        public EFResource(
            IUnitOfWork unitOfWork,
            IRepository<Author> authorsRep,
            IRepository<Book> booksRep,
            ILoggerService loggerService)
        {
            this.unitOfWork = unitOfWork;
            this.authorsRep = authorsRep;
            this.booksRep = booksRep;
            this.loggerService = loggerService;
        }

        [WebGet(UriTemplate = "Authors")]
        public IQueryable<DtoAuthor> GetAuthors()
        {

            loggerService.Info("Calling IQueryable<DtoAuthor> GetAuthors()");

            
            authorsRep.EnrolInUnitOfWork(unitOfWork);
            List<Author> authors =
                     authorsRep.FindAll("Books").ToList();

            IQueryable<DtoAuthor> dtosAuthors = authors
                     .Select(a => DtoTranslator.TranslateToDtoAuthor(a))
                     .ToList().AsQueryable<DtoAuthor>();

            return dtosAuthors;
            
        }

        [WebGet(UriTemplate = "Books")]
        public IQueryable<DtoBook> GetBooks()
        {
            loggerService.Info("Calling IQueryable<DtoBook> GetBooks()");

            
            booksRep.EnrolInUnitOfWork(unitOfWork);
            List<Book> books = 
                    booksRep.FindAll().ToList();

            IQueryable<DtoBook> dtosBooks = books
                     .Select(b => DtoTranslator.TranslateToDtoBook(b))
                     .ToList().AsQueryable<DtoBook>();

            return dtosBooks;
            
        }

        [WebInvoke(UriTemplate = "AddAuthor", Method = "POST")]
        public Restful.Models.Dtos.DtoAuthor AddAuthor(DtoAuthor dtoAuthor)
        {

            loggerService.Info("Restful.Models.Author AddAuthor(DtoAuthor dtoAuthor)");

            if (dtoAuthor == null)
            {
                loggerService.Error("author parameter is null");
                throw new HttpResponseException(HttpStatusCode.BadRequest);
            }

            
            authorsRep.EnrolInUnitOfWork(unitOfWork);
            Author author = EfTranslator.TranslateToEfAuthor(dtoAuthor);
            authorsRep.Add(author);
            unitOfWork.Commit();
            dtoAuthor.Id = author.Id;
            return dtoAuthor;
        }

        [WebInvoke(UriTemplate = "AddBook", Method = "POST")]
        public Restful.Models.Dtos.DtoBook AddBook(DtoBook dtoBook)
        {
            loggerService.Info("Restful.Models.Book AddBook(DtoBook dtoBook)");

            if (dtoBook == null)
            {
                loggerService.Error("book parameter is null");
                throw new HttpResponseException(HttpStatusCode.BadRequest);
            }

            
            booksRep.EnrolInUnitOfWork(unitOfWork);
            Book book = EfTranslator.TranslateToEfBook(dtoBook);
            booksRep.Add(book);
            unitOfWork.Commit();
            dtoBook.Id = book.Id;
            
            return dtoBook;
        }


        public void Dispose()
        {
            unitOfWork.Dispose(); 
        }     
    }
}

Configuration

As far satisfying the object graph and hosting of this service goes, that is pretty easy, thanks to the new WCF WebApi, all we need to do is have this one line in the Global.asax.cs (if you are using C# that is):

C#
RouteTable.Routes.MapServiceRoute<EFResource>("ef");

Which simply exposes the WCF WebApi service as an ASP MVC route.

So that is pretty much all we need to expose/host the WCF WebApi service and make it callable. But what about satisfying that dependency tree from the top down? Well, that is done by using the following WCF WebApi code in Global.asax.cs:

C#
var config = new MefConfiguration(container);
config.EnableTestClient = true;
RouteTable.Routes.SetDefaultHttpConfiguration(config);

Note the use of the MefConfiguration class there. Well, that is what we use to ensure that MEF will satisfy the Import/Export requirements of not only the WCF WebApi service, but all the required MEF parts.

So let's have a look at this class, shall we? Here it is in its entirety:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.ApplicationServer.Http;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Primitives;

namespace Restful.WCFService.Mef
{
    public class MefConfiguration : WebApiConfiguration
    {
        public MefConfiguration(CompositionContainer container)
        {
            CreateInstance = (t, i, m) =>
            {
                var contract = AttributedModelServices.GetContractName(t);
                var identity = AttributedModelServices.GetTypeIdentity(t);

                // force non-shared so that every service doesn't need to
                // have a [PartCreationPolicy] attribute.
                var definition = new ContractBasedImportDefinition(contract, 
                    identity, null, ImportCardinality.ExactlyOne, false, false, 
                    CreationPolicy.NonShared);
                return container.GetExports(definition).First().Value;
            };

            
            ReleaseInstance = (i, o) =>
                {
                    (o as IDisposable).Dispose();

                    Lazy<EFResource> service = new Lazy<EFResource>(() =>
                    {
                        return (EFResource)o;
                    });

                    container.ReleaseExport<EFResource>(service);
                };
        }
    }
}

It can be seen that this code is responsible for creating the MEF ContractBasedImportDefinition and the actual Exported values.

Registration

MEF used to auto wire up all its Import/Exports using whatever attributes the developer chose to use. These attributes are still available but the registration process has changed a lot, it is now more akin to other popular IOC providers such as Castle/AutoFac etc.

Here is how we provide what components the MEF container will actually use when resolving types from the container.

Let's examine the demo code WCF WebApi service again, where we have this:

C#
[ServiceContract]
[Export]
public class EFResource  : IDisposable
{
    IUnitOfWork unitOfWork;
    IRepository<Author> authorsRep;
    IRepository<Book> booksRep;
    ILoggerService loggerService;

    [ImportingConstructor]
    public EFResource(
        IUnitOfWork unitOfWork, 
        IRepository<Author> authorsRep,
        IRepository<Book> booksRep,
        ILoggerService loggerService)
    {
        this.unitOfWork = unitOfWork;
        this.authorsRep = authorsRep;
        this.booksRep = booksRep;
        this.loggerService = loggerService;
    }

    ....
    ....
    ....
    ....
}

How do we ensure MEF 2 can satisfy all that? It's pretty simple, we just need to register the stuff we want in the MEF container, as follows:

C#
private void Application_Start(object sender, EventArgs e)
{
    // use MEF for providing instances
    RegistrationBuilder context = new RegistrationBuilder();

    XmlConfigurator.Configure();

    context.ForType(typeof(Log4NetLoggerService)).
        Export(builder => builder.AsContractType(typeof(ILoggerService)))
        .SetCreationPolicy(CreationPolicy.Shared);

    context.ForType(typeof(Repository<>))
        .Export(builder => builder.AsContractType(typeof(IRepository<>)))
        .SetCreationPolicy(CreationPolicy.NonShared);

    context.ForType(typeof(TESTEntities))
        .Export(builder => builder.AsContractType(typeof(IUnitOfWork)))
        .SetCreationPolicy(CreationPolicy.NonShared);

    AggregateCatalog catalog = new AggregateCatalog(
        new AssemblyCatalog(typeof(Global).Assembly, context),
        new AssemblyCatalog(typeof(Repository<>).Assembly, context));

    var container = new CompositionContainer(catalog, 
        CompositionOptions.DisableSilentRejection | CompositionOptions.IsThreadSafe);
                        
    var config = new MefConfiguration(container);
    config.EnableTestClient = true;
    RouteTable.Routes.SetDefaultHttpConfiguration(config);
}

Note the use of the new registration syntax, also note that we are able to deal open Generics support for the IRepository type where the generic type is actually specified when we use the MEF 2 container registered IRepository type. This feature alone is worth moving to MEF 2, in my opinion.

It is very handy, as you can see when you look at the demo code WCF WebApi service, where it has two different IRepository types, each using a different generic type. Neato.

We now have all the pieces of the puzzle such that when we run our WCF WebApi service, we see it fully loaded with all its wants and needs.

Image 4

EF 4.1 POCO

LINQ to Entities has been around quite a while, and has gone through many different flavours (at least I think so). Truth is none of it has really appealed to me until this new LINQ to EF 4.1 POCO version came out. What this allowed us to do was:

  1. Use a very simple data context based object, which simply held sets of data objects.
  2. Not use any form of code generation (where everything under the sun is chucked onto the generated objects).
  3. Use simple hand rolled code classes which have no database concerns in them.

Using this POCO code first approach, we are able to create a simple data context class such as this one (which we will read more about in a minute).

Let us start by examining the actual LINQ to Entities 4.1 POCO context object. That for us starts life as a class that extends DbContext.

Here is the demo one:

C#
?using System;;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;

namespace Restful.Entities
{
    public abstract class EfDataContextBase : DbContext, IUnitOfWork
    {
        public EfDataContextBase(string nameOrConnectionString)
            : base(nameOrConnectionString)
        {
        }

        public IQueryable<T> Get<T>() where T : class
        {
            return Set<T>();
        }

        public bool Remove<T>(T item) where T : class
        {
            try
            {
                Set<T>().Remove(item);
            }
            catch (Exception)
            {
                return false;
            }
            return true;
        }

        public void Commit()
        {
            base.SaveChanges();
        }

        public void Attach<T>(T obj) where T : class
        {
            Set<T>().Attach(obj);
        }

        public void Add<T>(T obj) where T : class
        {
            Set<T>().Add(obj);
        }
    }
}

This provides a basic Entity Framework base class. But we need to extend that further to make a specific implementation for our EF 4.1 POCO objects. So we then end up with this:

C#
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.ComponentModel.Composition;
using Restful.Models;

namespace Restful.Entities
{
    public class ApplicationSettings
    {
        [Export("EFConnectionString")]
        public string ConnectionString
        {
            get { return "name=TESTEntities"; }
        }
    }

    public partial class TESTEntities : EfDataContextBase, IUnitOfWork
    {

        [ImportingConstructor()]
        public TESTEntities(
            [Import("EFConnectionString")]
            string connectionString)
            : base(connectionString)
        {
            this.Configuration.ProxyCreationEnabled = false;
            this.Configuration.LazyLoadingEnabled = true;
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            throw new UnintentionalCodeFirstException();
        }

        public DbSet<Author> Authors { get; set; }
        public DbSet<Book> Books { get; set; }
    }
}

If we then examine the actual EDMX file, we can see that code generation is turned off:

Image 5

What that means is that we must develop our own classes that may be used with this LINQ to Entities EDMX model file. It can be seen that this model consists of two classes: Author and Book, these are both shown below.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;

namespace Restful.Models
{
    public partial class Author
    {
        public Author()
        {
            this.Books = new List<Book>();
        }

        public int Id { get; set; }
        public string Name { get; set; }
        public List<Book> Books { get; set; }

        public override string ToString()
        {
            return string.Format(CultureInfo.InvariantCulture,
                "Author Name: {0}, Author Id: {1}", Name, Id);
        }
    }
}

And this is the Book:

C#
using System;
using System.Collections.Generic;
using System.Globalization;

namespace Restful.Models
{
    public partial class Book
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public int AuthorId { get; set; }
        public Author Author { get; set; }

        public override string ToString()
        {
            return string.Format(CultureInfo.InvariantCulture,
                "Title: {0}, Book Id: {1}", Title, Id);
        }
    }
}

Unit of Work

This pattern keeps track of everything that happens during a business transaction that affects the database. At the conclusion of the transaction, it determines how to update the database to conform to the changes.

Martin Fowler has an excellent article on this: http://www.martinfowler.com/eaaCatalog/unitOfWork.html.

Now since we are using LINQ to Entities 4.1 POCO, we already have some of the necessary bits to go about creating a nice Unit Of Work pattern implementation.

As before, we start by examining the actual LINQ to Entities 4.1 POCO context object. That for us starts life as a class that extends DbContext.

Here is the demo one:

C#
?using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;

namspace Restful.Entities
{
    public abstract class EfDataContextBase : DbContext, IUnitOfWork
    {
        public EfDataContextBase(string nameOrConnectionString)
            : base(nameOrConnectionString)
        {
        }

        public IQueryable<T> Get<T>() where T : class
        {
            return Set<T>();
        }

        public bool Remove<T>(T item) where T : class
        {
            try
            {
                Set<T>().Remove(item);
            }
            catch (Exception)
            {
                return false;
            }
            return true;
        }

        public void Commit()
        {
            base.SaveChanges();
        }

        public void Attach<T>(T obj) where T : class
        {
            Set<T>().Attach(obj);
        }

        public void Add<T>(T obj) where T : class
        {
            Set<T>().Add(obj);
        }
    }
}

Which as you now know provides a basic Entity Framework Unit of Work base class. But we need to extend that further to make a specific implementation for our EF 4.1 POCO objects. So we then end up with this:

C#
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.ComponentModel.Composition;
using Restful.Models;

namespace Restful.Entities
{

    public class ApplicationSettings
    {
        [Export("EFConnectionString")]
        public string ConnectionString
        {
            get { return "name=TESTEntities"; }
        }
    }

    public partial class TESTEntities : EfDataContextBase, IUnitOfWork
    {
        [ImportingConstructor()]
        public TESTEntities(
            [Import("EFConnectionString")]
            string connectionString)
            : base(connectionString)
        {
            this.Configuration.ProxyCreationEnabled = false;
            this.Configuration.LazyLoadingEnabled = true;
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            throw new UnintentionalCodeFirstException();
        }

        public DbSet<Author> Authors { get; set; }
        public DbSet<Book> Books { get; set; }
    }
}

The basic idea is that we simply expose DbSet properties and also methods to Add/Remove and Save changes. The interaction with these methods will be done by enrolling repositories with the Unit of Work, which we shall see in just a minute.

Repository

The Repository pattern has been around for a very long time, and comes from Domain Driven Design. MSDN has this to say about the objectives of the Repository pattern:

Use the Repository pattern to achieve one or more of the following objectives:

  • You want to maximize the amount of code that can be tested with automation and to isolate the data layer to support unit testing.
  • You access the data source from many locations and want to apply centrally managed, consistent access rules and logic.
  • You want to implement and centralize a caching strategy for the data source.
  • You want to improve the code's maintainability and readability by separating business logic from data or service access logic.
  • You want to use business entities that are strongly typed so that you can identify problems at compile time instead of at run time.
  • You want to associate a behavior with the related data. For example, you want to calculate fields or enforce complex relationships or business rules between the data elements within an entity.
  • You want to apply a domain model to simplify complex business logic.

Sounds cool, doesn't it? But how does that translate into code? Well, here is my take on it (again, I abstract this behind interfaces to allow alternative implementations, or mocking):

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using System.Linq.Expressions;
using System.ComponentModel.Composition;

namespace Restful.Entities
{
    public class Repository<T> : IRepository<T> where T : class
    {
        protected IUnitOfWork context;

        public void EnrolInUnitOfWork(IUnitOfWork unitOfWork)
        {
            this.context = unitOfWork;
        }

        public int Count
        {
            get { return context.Get<T>().Count(); }
        }
        
        public void Add(T item)
        {
            context.Add(item);
        }
        
        public bool Contains(T item)
        {
            return context.Get<T>().FirstOrDefault(t => t == item) != null;
        }
        
        public void Remove(T item)
        {
            context.Remove(item);
        }

        public IQueryable<T> FindAll()
        {
            return context.Get<T>();
        }

        public IQueryable<T> FindAll(Func<DbSet<T>, IQueryable<T>> lazySetupAction)
        {
            DbSet<T> set = ((DbSet<T>)context.Get<T>());
            return lazySetupAction(set);
        }

        public IQueryable<T> FindAll(string lazyIncludeStrings)
        {
            DbSet<T> set = ((DbSet<T>)context.Get<T>());
            return set.Include(lazyIncludeStrings).AsQueryable<T>();
        }

        public IQueryable<T> FindBy(Func<T, bool> predicate)
        {
            return context.Get<T>().Where(predicate).AsQueryable<T>();
        }

        public IQueryable<T> FindByExp(Expression<Func<T, bool>> predicate)
        {
            return context.Get<T>().Where(predicate).AsQueryable<T>();
        }

        public IQueryable<T> FindBy(Func<T, bool> predicate, string lazyIncludeString)
        {
            DbSet<T> set = (DbSet<T>)context.Get<T>();
            return set.Include(lazyIncludeString).Where(predicate).AsQueryable<T>();
        }

        public IQueryable<T> FindByExp(Expression<Func<T, 
               bool>> predicate, string lazyIncludeString)
        {
            return context.Get<T>().Where(predicate).AsQueryable<T>();
        }
    }
}

At first glance, this may seem confusing, but all the repository does is abstract away dealing with the raw data from the database. It can also be seen above that my implementation relies on a IUnitOfWork object. In my case, that is the actual LINQ to Entities 4.1 DbContext class that we use to talk to the database.

So by allowing your repositories to interact with the IUnitOfWork (LINQ to Entities 4.1 DbContext) code, we can get the repositories to enroll in a simple transactional piece of work (Unit of Work, basically).

Shown below is an example of how you might use my Unit of Work / Repository implementations together:

C#
[WebGet(UriTemplate = "Books")]
public IQueryable<DtoBook> GetBooks()
{
    loggerService.Info("Calling IQueryable<DtoBook> GetBooks()");

    using (unitOfWork)
    {
        booksRep.EnrolInUnitOfWork(unitOfWork);
        List<Book> books = 
            booksRep.FindAll().ToList();

        IQueryable<DtoBook> dtosBooks = books
                .Select(b => DtoTranslator.TranslateToDtoBook(b))
                .ToList().AsQueryable<DtoBook>();

        return dtosBooks;
    }
}

Obviously this is a very simple example, but I hope you get the idea.

log4Net

Logging is of upmost importance in any app, and we should all do it. Luckily, we do not have to do all the hard work ourselves, there are some truly excellent logging frameworks out there. log4Net being the pick of the crop in my opinion.

Thankfully, integrating log4Net is very simple. It is just a matter of:

  1. Referencing the log4Net DLL
  2. Adding some config settings for log4Net
  3. Add a logger object
  4. Get log4Net to configure based on your config settings

Assuming you have added log4Net reference, what I normally do is start by abstracting (you know so we can substitute for a different logging library or mock this one) the logging behind a logging service, like so:

C#
using System;
using log4net;
using Restful.WCFService.Services.Contracts;

namespace Restful.WCFService.Services.Implementation
{
    public class Log4NetLoggerService : ILoggerService
    {
        private ILog logger;
        private bool isConfigured = false;

        public Log4NetLoggerService()
        {
            if (!isConfigured)
            {
                logger = LogManager.GetLogger(typeof(Log4NetLoggerService));
                log4net.Config.XmlConfigurator.Configure();
            }
        }

        public void Info(string message)
        {
            logger.Info(message);
        }
        public void Warn(string message)
        {
            logger.Warn(message);
        }
        public void Debug(string message)
        {
            logger.Debug(message);
        }
        public void Error(string message)
        {
            logger.Error(message);
        }
        public void Error(Exception ex)
        {
            logger.Error(ex.Message, ex);
        }
        public void Fatal(string message)
        {
            logger.Fatal(message);
        }
        public void Fatal(Exception ex)
        {
            logger.Fatal(ex.Message, ex);
        }
    }
}

Then with that in place, I take a dependency on the logging service (this can be seen in the MEF section we discussed earlier).

Next, I add the following log4Net configuration (you may want to change this or add new appenders (one of the best log4Net features is the ability to add new appenders, and log4Net has many appenders, and makes the process of creating new ones very straightforward)).

In my case, this is in the Restful.WCFService Web.Config.

XML
<?xml version="1.0" encoding="utf-8"?>
<configuration>

  <configSections>
    <section name="log4net" 
    type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
  </configSections>

  <!-- Log4Net settings -->
  <log4net>
    <root>
      <priority value="Info"/>
      <appender-ref ref="RollingFileAppender"/>
    </root>
    <appender name="RollingFileAppender" 
    type="log4net.Appender.RollingFileAppender">
      <file value="C:\Temp\Restful.WCFService.log" />
      <appendToFile value="true" />
      <rollingStyle value="Composite" />
      <maxSizeRollBackups value="14" />
      <maximumFileSize value="15000KB" />
      <datePattern value="yyyyMMdd" />
      <staticLogFileName value="true" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern 
        value="{%level}%date{dd/MM/yyyy HH:mm:ss} - %message%newline"/>
      </layout>
    </appender>
  </log4net>  
</configuration>

The next thing is to ensure that you make sure that log4Net picks up these settings, which you do by running the following line of code:

C#
XmlConfigurator.Configure();

Then all that remains is to use the logging service (the code below shows this, where the logging service was provided by MEF):

C#
[WebGet(UriTemplate = "Books")]
public IQueryable<DtoBook> GetBooks()
{
    loggerService.Info("Calling IQueryable<DtoBook> GetBooks()");
    ...
    ...
    ...
}

Client App

As I say, for this demo solution, the client side code is pretty simple, it is just a simple Console app. At some stage further down the line, when I do part II of this (which may never happen), I will make a nice WPF UI over the top of it all. However, for now, it is a simple console app, which basically does the following:

  1. Gets an HttpClient which can be used with the demo WCF WebApi service
  2. POSTs a new Author
  3. Gets all Author objects
  4. Does an oData query against Author
  5. POSTs a new Book

Here is the complete client code, pretty simple I think. The best part is there is no App.Config specific stuff for the WCF WebApi service at all, we simply make a request and deal with the results.

C#
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Xml.Serialization;
using Microsoft.ApplicationServer.Http;

using Restful.Models;
using System.Net.Http.Formatting;
using Restful.Models.Dtos;

namespace Restful.Client
{
    class Program
    {
        private enum MimeFormat { JSON, Xml };
        string[] randomAuthors = new string[] {
            "Jonnie Random",
            "Philip Morose",
            "Damien Olive",
            "Santana Miles",
            "Mike Hunt",
            "Missy Elliot"
        };

        string[] randomBooks = new string[] {
            "Title1",
            "Title2",
            "Title3",
            "Title4",
            "Title5",
            "Title6"
        };
        
        private Random rand = new Random();

        public Program()
        {

        }

        public void SimpleSimulation()
        {
            string baseUrl = "http://localhost:8300/ef";

            //GET Using XML
            HttpClient client = GetClient(MimeFormat.Xml);
            string uri = "";

            //POST Author using XML
            DtoAuthor newAuthor = 
              new DtoAuthor { Name = randomAuthors[rand.Next(randomAuthors.Length)] };
            uri = string.Format("{0}/AddAuthor", baseUrl);
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, new Uri(uri));
            request.Content = new ObjectContent<DtoAuthor>(newAuthor, "application/xml");
            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));

            HttpResponseMessage response = client.Send(request);
            DtoAuthor receivedAuthor = response.Content.ReadAs<DtoAuthor>();
            LogAuthor(uri, receivedAuthor);


            //GET All Authors
            client = GetClient(MimeFormat.Xml);
            uri = string.Format("{0}/Authors", baseUrl);
            response = client.Get(uri);
            foreach (DtoAuthor author in response.Content.ReadAs<List<DtoAuthor>>())
            {
                LogAuthor(uri, author);
            }


            //POST Book using XML
            client = GetClient(MimeFormat.Xml);
            uri = string.Format("{0}/Authors?$top=1", baseUrl);
            response = client.Get(uri);
            DtoAuthor authorToUse = 
              response.Content.ReadAs<List<DtoAuthor>>().FirstOrDefault();
            if (authorToUse != null)
            {
                LogAuthor(uri, authorToUse);

                DtoBook newBook = new DtoBook
                {
                    Title = randomBooks[rand.Next(randomBooks.Length)],
                    AuthorId = authorToUse.Id
                };
                client = GetClient(MimeFormat.Xml);
                uri = string.Format("{0}/AddBook", baseUrl);
                request = new HttpRequestMessage(HttpMethod.Post, new Uri(uri));
                request.Content = new ObjectContent<DtoBook>(newBook, "application/xml");
                request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
                response = client.Send(request);
                DtoBook receivedBook = response.Content.ReadAs<DtoBook>();
                LogBook(uri, receivedBook);

                client = GetClient(MimeFormat.Xml);
                uri = string.Format("{0}/Books?$top=1", baseUrl);
                response = client.Get(uri);
                receivedBook = response.Content.ReadAs<List<DtoBook>>().FirstOrDefault();
                LogBook(uri, receivedBook);
            }
            Console.ReadLine();
        }

        private void LogAuthor(string action, DtoAuthor author)
        {
            Console.WriteLine(string.Format("{0}:{1}", action, author));
        }

        private void LogBook(string action, DtoBook book)
        {
            Console.WriteLine(string.Format("{0}:{1}", action, book));
        }

        [STAThread]
        static void Main(string[] args)
        {
            Program p = new Program();
            p.SimpleSimulation();
        }

        private static HttpClient GetClient(MimeFormat format)
        {
            var client = new HttpClient();
            switch (format)
            {
                case MimeFormat.Xml:
                    client.DefaultRequestHeaders.Accept.Add(
                        new MediaTypeWithQualityHeaderValue("application/xml"));
                    break;
                case MimeFormat.JSON:
                    client.DefaultRequestHeaders.Accept.Add(
                        new MediaTypeWithQualityHeaderValue("application/json"));
                    break;
            }
            return client;
        }
    }
}

That's It

Anyway, that is all I really wanted to say this time. I am now going to get back to the final 5% of this OSS project that Pete O'Hanlon and I are working on. Thing is that, the final 5% is the hardest part, but we are both into it, so expect to see it appearing here some time soon. It's been a while coming but we both like it, and feel it will be of use. So until then.....Dum dum dum.

However, if you liked this article, and can be bothered to give a comment/vote, they are always appreciated. Thanks for reading. Cheerio.

License

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


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
QuestionSuggestions for a newbie Pin
Leonardo Victor24-Mar-16 7:17
Leonardo Victor24-Mar-16 7:17 
AnswerRe: Suggestions for a newbie Pin
Sacha Barber24-Mar-16 7:33
Sacha Barber24-Mar-16 7:33 
GeneralRe: Suggestions for a newbie Pin
Leonardo Victor24-Mar-16 8:59
Leonardo Victor24-Mar-16 8:59 
GeneralRe: Suggestions for a newbie Pin
Sacha Barber24-Mar-16 9:47
Sacha Barber24-Mar-16 9:47 
QuestionAgain Pin
lmcgs12-Nov-15 2:54
lmcgs12-Nov-15 2:54 
AnswerRe: Again Pin
Sacha Barber12-Nov-15 3:14
Sacha Barber12-Nov-15 3:14 
Questionok Pin
pop ioana30-Jan-15 0:08
pop ioana30-Jan-15 0:08 
AnswerRe: ok Pin
Sacha Barber30-Jan-15 0:09
Sacha Barber30-Jan-15 0:09 
Questionrequest Pin
pop ioana29-Jan-15 23:57
pop ioana29-Jan-15 23:57 
AnswerRe: request Pin
Sacha Barber30-Jan-15 0:03
Sacha Barber30-Jan-15 0:03 
GeneralMy vote of 4 Pin
Phil_Murray4-Sep-13 3:07
Phil_Murray4-Sep-13 3:07 
QuestionI dont like the new Webapi Pin
commerce@jant.de4-Jun-13 0:30
commerce@jant.de4-Jun-13 0:30 
QuestionAbout Modifying EF POCO Entities Pin
Emiliano Sandler16-Mar-13 18:01
Emiliano Sandler16-Mar-13 18:01 
AnswerRe: About Modifying EF POCO Entities Pin
Sacha Barber16-Mar-13 22:05
Sacha Barber16-Mar-13 22:05 
QuestionImplementation Detail in the FindByExp-Methods of the Repository<T> class Pin
Felix Liebrecht7-Nov-12 0:05
Felix Liebrecht7-Nov-12 0:05 
QuestionMapServiceRoute disappears in RC Pin
deBaires31-Jul-12 11:10
deBaires31-Jul-12 11:10 
AnswerRe: MapServiceRoute disappears in RC Pin
Sacha Barber31-Jul-12 11:25
Sacha Barber31-Jul-12 11:25 
GeneralRe: MapServiceRoute disappears in RC Pin
deBaires31-Jul-12 12:23
deBaires31-Jul-12 12:23 
GeneralMy vote of 5 Pin
Monjurul Habib31-Jul-12 2:22
professionalMonjurul Habib31-Jul-12 2:22 
QuestionWhat about ASP.NET Web API? Pin
g5temp24-Jul-12 13:16
g5temp24-Jul-12 13:16 
AnswerRe: What about ASP.NET Web API? Pin
Sacha Barber24-Jul-12 22:30
Sacha Barber24-Jul-12 22:30 
AnswerRe: What about ASP.NET Web API? Pin
Sacha Barber25-Jul-12 22:09
Sacha Barber25-Jul-12 22:09 
GeneralRe: What about ASP.NET Web API? Pin
g5temp30-Jul-12 17:14
g5temp30-Jul-12 17:14 
GeneralRe: What about ASP.NET Web API? Pin
Sacha Barber31-Jul-12 0:06
Sacha Barber31-Jul-12 0:06 
QuestionGreat! Good work guy.. Pin
neriacompany1-Jun-12 3:56
neriacompany1-Jun-12 3:56 

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.