转:IOC Container for Web App, Castle Windsor

I recently wrote a post about how to set up CW’s IOC container for applications. This post covered the most basic implementation to get an IOC container up and running. It is now time to cover a proper architecture for using IOC specifically for web applications / sites.

The main problem with the previous implementation is that each time an object was requested a new container would be created and then destroyed when the object was no longer needed. This is expensive to create and offers no benefits of the IOC framework such as scoping of the object or re-use. It also meant that the configuration was kept in a configuration file which very quickly gets out of hand with just a few classes. Another problem is that the coding of the container creation and object creation is littered throughout the client code with no single place to create and re-use objects.

I have read many posts and the IOC documentation about the subject of how to use containers for web applications and although nothing I am writing here is new, I have not found anywhere else that details how to set up the whole architecture. Most posts seem to cover small pieces of the jigsaw.

I’m not going to start with the basic implementation I had last time but start from scratch to define how I set out my web architecture.

The first thing as with all IOC set ups is to make sure you have coded your classes correctly, and this means at the very least following these principles:

Write your unit tests first (TDD) with something like NUnit or MS Test
Use the single responsibility principle with your methods
Use simple domain objects (POCOs).
Code your data access layer to an interface, preferably using a repository pattern and something like NHibernate as an ORM (see NHibernate FAQ on repository pattern )
Once you’ve started writing code that is testable and follows the above patterns etc, then you are ready to look at IOC.

The first thing is to structure the classes in such a way as to use convention over configuration. We want to put all of our service classes into the same project and make sure they all have the same namespace. We also want all of the interfaces to have the same beginning part of the namespace and the data access classes that implement those interfaces to have a common root namespace. This is so we can do away with implementing the objects the container will create manually and move to a system that automatically registers them for us!

Example:

Put all services in a separate project and call the classes something like MyProject.Services.Classname
Put all data access classes in a separate project and call them something like MyProject.DataAccessLayer.ClassName
Put all Interfaces into the domain layer and call them MyProject.Domain.Interfaces.InterfaceName

To code my classes I typically write my Interface first. Then I write my service by first inheriting my service from the interface to automatically implement the methods. I find the service usually has more or less the same methods as the DAL. Once they are implemented, I remove the inheritance as the service is not of that type. This is just a trick to save typing. I then implement my tests around the service to describe what logic I want the service to provide. Then I complete my service class logic to pass the tests.
I then implement the DAL concrete class by inheriting from the interface.
If I am using NHibernate I will first create my mapping files and use an in memory SQL Server to write tests for my DAL. If I am using stored procedures and not using an ORM, then I generally don’t have tests for this. (If anyone has any ideas around testing procs, please let me know)

The Interface is something like this:

namespace MyProject.Domain.Interfaces
{

public Interface IMyDal
{

void GetSomeObjects();

}
}

The service is something like this:

namespace MyProject.Services
{

public class MyService
{

private readonly IMyDal _myDal;

public MyService(IMyDal MyDal)
{

_myDal = MyDal;

}

public void GetSomeObjects()
{

_myDal. GetSomeObjects();

}

}
}

The tests are something like this:
(Using NUnit and Rhino Mocks)

namespace ServiceTests

{

[TestFixture]
public class MyServiceTests
{

[Test]
public void SomeTest ()
{

//some expected outcomes here
IMyDal dal = MockRepository.GenerateMock<IMyDal>();

MyService service = new MyService(dal);

service.GetSomeObjects();

dal.AssetWasCalled(d=>d.GetSomeObjects());

}
}
}

The DAL is something like this:

namespace MyProject.Dal
{

public class MyConcreteDal : IMyDal
{

public void GetSomeObjects()
{

//implement dal code here to access db
}
}
}

Once I have a service class with passing tests that implements and calls the data access layer, then I am ready to write the client which will use the IOC to inject the DAL into the service.

The first thing we need to use IOC frame work is the container. As this is a web application we need a place to create the container so that it doesn’t have to be called on each object request or even each web request. The obvious place for this is in the application onStart method in the globa.asax.

The actual global.asax file should only have one line in it:

<%@ application language=”c#” inherits=”Global” %>

We then create a class in the appCode folder called Global with the file name of Global.asax.cs

public class Global : HttpApplication , IContainerAccessor
{

private static WebAppContainer _webAppContainer;

public WebAppContainer Container
{
get{return _ webAppContainer;}
}

IWindsorContainer.IContainerAccessor.Container
{
get{return Container;
}

protected void Application_Start(Object sender, EventArgs e)
{
_webAppContainer = new WebAppContainer();
}

protected void Application_End(Object sender, EventArgs e)
{
_webAppContainer.Dispose();
}
}

This code creates a single container that we will use to create all of the IOC objects for our application. The container is stored in a static variable so that it is only created once and used by the whole application. The class WebAppContainer is our container we will use to create our objects. It is derived from the standard Windsor container and defined below.

As the object is created in the global.asax file we cant create an instance of this class every time we want to get access to the container. This is why we use the interface IContainerAccessor. We can instantiate a container from the global.asax using this interface. First we define a static class in the services project (so we can call it from our tests if needed) with just a single getter.

public static class Ioc
{
public static WebAppContainer Container
{
get
{
IContainerAccessor containerAccessor = HttpContext.Current.ApplicationInstance as IContainerAccessor;

if(containerAccessor != null){return (WebAppContainer) containerAccessor.Container;}

throw new Exception(“error message”);
}
}

}

Using this class we can now get our container from anywhere in our website.

The next thing is to tell the container about our service classes, data access layer and interfaces. This is where making sure they are all in the same namespaces comes in. We wont use an xml configuration file but we will use fluent and reflection to load them all in in one go.

This is where we define our container. We will inherit from the base Windsor container so we can include the configuration in the constructor. We will even include a config file in the constructor should we wish to override the basic settings using a config. That way if we want to write some test Dals (see previous post on testing web app ui code) we can without having to recompile the application.

public class WebAppContainer : WindsorContainer
{
public WebAppContainer : base(new XmlInterpreter(“castle.config”))
{
Register(AllTypes.FromAssembly(Assembly.GetAssembly(typeof(MyService)))
.Where(type=>type.Namespace.Contains(MyProject.Services)).WithService.FirstInterface());

Register(AllTypes.FromAssembly(Assembly.GetAssembly(typeof(MyDataAccessLayerClass)))
.Where(type => type.Namespace.Contains(MyProject.DataAccessLayer.Ioc) && type.IsClass)
.Configure(x =>x.LifeStyle.PerWebRequest)
.WithService.FirstInterface());
}
}

This code will register the services and then register all the data access classes in the assembly with the class "MyDataAccessLayerClass" in it. It will look for all classes in the namespace "MyProject.DataAccessLayer.Ioc" and find the first interface declared on those classes. It will then configure the container to use the dal class when the interface is called.

To get this to work using the PerWebRequest scope, you will also need to add the following line to the web config file in the httpModules section:


<add name="PerRequestLifestyle" type="Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule, Castle.MicroKernel"/>

This uses reflection so that you can now add as many services, dals and interfaces and never have to wire them up explicitly. This removes the configuration nightmare that can quickly appear otherwise. With regards to performance using reflection, that is something that you will need to decide if it is OK for your application. You can read about it in this good guide on IOC Performance.

The final thing to do is to call the container from the client code that needs to use the service. This is best done in a single method per page such as:

private static MyService getMyService()
{
WebAppContainer myContainer = Ioc.Container;
MyService service = myContainer.Resolve<MyService>();
return service;
}

We can now access the service from any method in the page and be sure we are using the same container and the object will last the length of the web request.

原文地址:https://www.cnblogs.com/wucg/p/2391567.html