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:
Item | Included |
SQL Server | No You must have this already. |
Entity Framework 4.1 POCO | Yes See Lib\Entity Framework 4.1\EntityFramework41.exe |
log4Net | Yes See Lib\Log4Net\1.2.10.0\log4net.dll. |
Lib\MEF 2 | Yes
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 4 | Yes
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.
Getting Started
To get started, you will need to do the following:
- Create a new SQL Server database called "TESTEntites" which you may do using the two scripts in the "getting started" solution folder.
- Change the Web.Config in Restful.WCFService to point to your newly created database and SQL Server installation.
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:
- http://sachabarber.net/?p=460
- http://sachabarber.net/?p=475
- 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:
- It is just a WCF service really
- It doesn't really need a specialized host as such
- It truly is RESTful
- Can be called using simple web client-side code, such as jQuery
- 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):
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):
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:
As far as hosting one of these types of services, there is very little to it. There is this bit of Web.Config code:
="1.0"="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:
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)
{
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
/Export
s
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 Import
s, you may also note that the entire
WCF WebApi service is also being Export
ed. 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.
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):
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:
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:
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);
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 Export
ed 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:
[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:
private void Application_Start(object sender, EventArgs e)
{
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.
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:
- Use a very simple data context based object, which simply held sets of data objects.
- Not use any form of code generation (where everything under the sun is chucked onto the generated objects).
- 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:
?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:
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:
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.
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
:
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:
?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:
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):
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:
[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:
- Referencing the log4Net DLL
- Adding some config settings for log4Net
- Add a logger object
- 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:
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.
="1.0"="utf-8"
<configuration>
<configSections>
<section name="log4net"
type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
</configSections>
<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:
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):
[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:
- Gets an
HttpClient
which can be used with the demo WCF WebApi service - POSTs a new
Author
- Gets all
Author
objects - Does an oData query against
Author
- 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.
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";
HttpClient client = GetClient(MimeFormat.Xml);
string uri = "";
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);
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);
}
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.