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

Web-Application Framework - Catharsis - part XIV. - Dependency injection (DI, IoC)

Rate me:
Please Sign up or sign in to vote.
4.41/5 (10 votes)
21 Sep 2008CPOL6 min read 47.6K   19   8
Catharsis web-app framework - dependency injection (DI, IoC)

Catharsis - Dependency Injection

This article is the next step in Catharsis documented tutorial.  Catharsis is a web-application framework gathering best-practices, using ASP.NET MVC (preview 5), NHibernate 2.0. All needed source code you can find on    

http://www.codeplex.com/Catharsis/

Catharsis-title.png

No.ChapterNo.ChapterNo.Chapter

New solution VI CatharsisArchitectureXI (Controller layer - undone)  

II Home page observationVII Entity layer XII  (UI Web layer - undone) 

III Roles and usersVIII Data layer XIII  Tracking, PAX design pattern

IV Localization, translationsIX (Business layer - undone)  XIV Catharsis Dependency Injection (DI, IoC)

Enter into Catharsis, new Entity(Model layer - undone)  XV (Code Lists - undone)     

Catharsis and Dependency Injection (DI, IoC)  

Catharsis layers are as independent as you can imagine. Upper layer does not need to reference lower layer!

For example the Data layer, which serves as the only storage handler, is used on Business tier (only on business tier). But there is NO need to put reference into business.dll to get classes from data.dll, because of Catharsis framework architecture. All layers are loosely coupled.  In this article you'll see how this works.

In fact, there are references, but for another purposes.
1) The controller project must reference the business library because of direct dependency on AOP
2) References help you with building application, all project are copied into your bin directory, and you do not have to care… This is the only reason for references.

Dependency injection (DI, IoC)  

DI Dependency injection (IoC Inversion of Control) is well described in the article by Billy McCafferty – Dependency Injection for Loose Coupling. I suggest you to read it first (because the next description is based on remarks to that solution)
There are two main approaches described. For our example, let’s talk about Controller which uses Façade. In fact it uses IFacade (IPersonFacade) - interface is the esence of DI.

C#
public interface IPersonFacade
{
    // TODO ...
}

Constructor DI

The constructor DI is forcing you to provide the IFaçade object whenever you are asking for Controller. Whenever you are creating such a controller, you have to pass existing IFacade.

C#
public class PersonController
{
    public PersonController(IPersonFacade facade)
    {
        // TODO ...
    }
}

Setter DI

You can create controller (there is default parameter-less constructor), but the controller will throw Exception if the IFaçade setter is not filled and any controller’s method or property (which uses IFaçade) is used.   

C#
public class PersonController
{
    public PersonController() { } 		// parameter-less constructor
    public IPersonFacade Facade { set; }	// public could be only setter
    // TODO ...
}   

Advantages of DI

Once you understand this pattern, you will use it. The first advantage comes from the interface oriented approach. Your controller uses only methods published in interface. Your implementing object can do more, for PersonController is important only the agreement provided by IPersonFacade interface.  

If TDD (unit test driven development) comes into play, any dummy object can be passed to simulate the IFaçade behavior. In such scenario you are concerning on testing controller behavior not the IFacade

When any changes are needed ‘or intended’ in inner implementation of façade implementing object (even in production environment) you can change only .dll with façade and the rest of application (upper layers) are still working. 

Think about it for a while, you can find out many more advantages. controller and façade are independent. That’s it. Only way they do communicate is via published IPersonFacade interface!  

DI challenges in framework architecture

As good the DI approach is, it brings few issues. There is a ‘controller factory’ in the ASP.NET MVC world which handles requests based on url-routing. If user asks for http://server/Person.mvc/ the factory will create the PersonController object and let him to handle user’s request.   

The built-in (MVC) solution won’t work with both above described scenarios. PersonController needs IPersonFacade (via constructor or setter) and the default factory is not able to do that job! So how to solve it…? 

I was walking in Billy McCafferty footprints. Firstly there was the NHibernate Best Practices with ASP.NET, 1.2nd Ed.

The SharpArchitecture 0.6.3 brought DI based on Spring.NET and xml files, which were describing which 'controller' needs which 'facade' or 'dao'. 

Few months ago Billy introduced the SharpArchitecture 0.7.3 which totally changes the web-application design. It’s based only on NHibernate and ASP.NET MVC, almost nothing else is needed (Spring.NET support was removed but DI remains!). As the IoC container now serves the MVC itself.   

The only think needed is to provide your own ‘ControllerFactory’ which knows how to inject the right objects to controllers (In the SharpArchtecture it is IDao what's used in controller, separated Business tier is not used).  

SharpArchitecture will inspire a lot, and you can see how this pattern should be applied in practice.  

Weak points of constructor or setter DI  

Catharsis controller needs IFacade and IModel to exist. When you are asking factory to give you IController, 3 objects must be created (IModel, IFacade and IController). Due Catharsis architecture even 4 objects, because facade needs IDao

The factory for that reason must be very very smart. It must be able to create all these objects (somehow) to return the single controller object.

When I was working on Catharsis that kind of Dependency Injection approach started to be crucial restriction. And even if successfully implemented it was so complicated that there was frustration instead of catharsis.

I've rolled back and changed my focus on separating of concern again. There must be the way how all need objects (IFacade, IModel and even IDao) will be injected but responsibility for doing that must remain on caller! 

Controller needs IFacade. There is (must be) FacadeFactory wich can produce any IFacade object. So let the controller itself ask that factory for injection in runtime. And also it must ask ModelFacotry for IModel. And facade itself will call DaoFactory for IDao object to be injected.

The difference, the clearness, the separation of concern – enter into catharsis again...

Catharsis DI  

Next example will show you what we are talking about:

C#
public class PersonController
{
    public PersonController()
    {
        IPersonFacade Facade = FacadeFactory.CreateFacade<IPersonFacade>();
    }
    IPersonFacade Facade { get; set; }
} 

PersonController needs the façade, it is still true. And because the controller knows which object needs it calls FacadFactory for such an object. 

There is still controller totally independent on business layer; working with IPersonFacade instead of PersonFacade

Our gain is that we have the controller which could be created by default MVC  ControllerFactory, because it does not need any type of outer Injection (nor constructor neither setter). 

But what we have gained more? We can even provide Model the same way: 

C#
public class PersonController
{
    public PersonController()
    {
        IPersonFacade Facade = FacadeFactory.CreateFacade<IPersonFacade>();
        IPersonModel Model = ModelFactory.CreateModel<IPersonModel>();
    }
    IPersonFacade Facade { get; set; }
    IPersonModel Model { get; set; }
}

Factory pattern 

And it is still not the end! How it is possible, that we did created IFaçade in the controller, if we know it needs the IDao object to persist objects? As you expect:

C#
public class PersonFacade
{
    public PersonFacade()
    {
        IPersonDao Dao = DaoFactory.CreateDao<IPersonDao>();
    }
    IPersonDao Dao { get; set; }
}

Yes, this approach is breaking (despite of the fact, that is years old). Any object in any layer can ask for an interface via factory. And it could be done in a chain, because we are asking for parameter-less, independent objects (no constructor, no setter)

How factory works

Every interface defined in Common layer must be provided with ConcreteTypeAttribute, which needs the implementing object full-name and .dll in the constructor:

C#
[ConcreteType("Firm.Product.Business.People.PersonFacade, Firm.Product.Business")]
public interface IPersonFacade : IFacade<Person> { }

This attribute was introduced in Billy McCafferty - SharpArchitecture 0.7.2. Wonderful solution, thanks a lot. This information is used by factory: 

C#
public static class FacadeFactory
   {
       public static T CreateFacade<T>()
       {
           var attributes = typeof(T).GetCustomAttributes(typeof(ConcreteTypeAttribute), true);
           if (attributes.Length > 0 && attributes[0] is ConcreteTypeAttribute)
           {
               var facadeName = ((ConcreteTypeAttribute)attributes[0]).TypeName;
               var targetType = Type.GetType(facadeName);
               return (T)Activator.CreateInstance(targetType);
           }
           return default(T);
       }
   }

Simple. If you would like to improve performance, reflection could be reduced and type stored for the next reuse: 

C#
public static class FacadeFactory
    {
        static readonly SortedList<string, Type> _facades = new SortedList<string, Type>();
        public static T CreateFacade<T>()
        {
            if (_facades.ContainsKey(typeof(T).FullName))
            {
                return (T)Activator.CreateInstance(_facades[typeof(T).FullName]);
            }
            var attributes = typeof(T).GetCustomAttributes(typeof(ConcreteTypeAttribute), true);
            if (attributes.Length > 0 && attributes[0] is ConcreteTypeAttribute)
            {
                var facadeName = ((ConcreteTypeAttribute)attributes[0]).TypeName;
                var targetType = Type.GetType(facadeName);
                _facades[typeof(T).FullName] = targetType;
                return (T)Activator.CreateInstance(targetType);
            }
            return default(T);
        }
    }

Final security notes

Take care about who is using your Factories. They must be provided wisely, to avoid creating very low level object on upper ones; let’s say Dao on UI – Web layer. Catharsis therefore provides the DaoFactory only on Business layer, what means that Dao objects cannot be created in controllers! They have to use the Façades instead, which will take care about business rules. 

Source code

http://www.codeplex.com/Catharsis/

Enjoy Catharsis 

History   

License

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


Written By
Web Developer
Czech Republic Czech Republic
Web developer since 2002, .NET since 2005

Competition is as important as Cooperation.

___

Comments and Discussions

 
GeneralSharpArchitecture vs. Catharsis Pin
Eng. Jalal26-Sep-08 20:31
Eng. Jalal26-Sep-08 20:31 
GeneralRe: SharpArchitecture vs. Catharsis Pin
Radim Köhler26-Sep-08 21:40
Radim Köhler26-Sep-08 21:40 
GeneralRe: SharpArchitecture vs. Catharsis Pin
Eng. Jalal27-Sep-08 1:23
Eng. Jalal27-Sep-08 1:23 
GeneralRe: SharpArchitecture vs. Catharsis Pin
Radim Köhler27-Sep-08 1:36
Radim Köhler27-Sep-08 1:36 
GeneralDI != ServiceLocator Pin
Roger Alsing22-Sep-08 21:37
Roger Alsing22-Sep-08 21:37 
GeneralRe: DI != ServiceLocator Pin
Radim Köhler22-Sep-08 21:55
Radim Köhler22-Sep-08 21:55 
GeneralGood stuff/ Pin
Pete O'Hanlon22-Sep-08 4:13
subeditorPete O'Hanlon22-Sep-08 4:13 
GeneralRe: Good stuff/ Pin
Radim Köhler22-Sep-08 4:44
Radim Köhler22-Sep-08 4:44 

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.