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






4.41/5 (10 votes)
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/No. | Chapter | No. | Chapter | No. | Chapter | |
I | New solution | VI | CatharsisArchitecture | XI | (Controller layer - undone) | |
II | Home page observation | VII | Entity layer | XII | (UI Web layer - undone) | |
III | Roles and users | VIII | Data layer | XIII | Tracking, PAX design pattern | |
IV | Localization, translations | IX | (Business layer - undone) | XIV | Catharsis Dependency Injection (DI, IoC) |
|
V | Enter into Catharsis, new Entity | X | (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.
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
.
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.
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:
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:
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:
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:
[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:
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:
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