Saturday, February 12, 2011

Entity Framework CTP5 injecting with NInject

Hello, this is Nripendra Nath Newa. This is the first time I'm trying to blog some of my thoughts, and I think it's quite a difficult subject that I've choosen.

Now, that microsoft has made its move towards supporting code-first approach for its ADO.Net Entity Framework, and has released developer's preview CTP5, many developer has been using the framework and giving feedback.

I have been currently trying out to practically implement the DDD approach of software development, using the EF code-first ctp5. After studying few books and blogs on DDD and EF code-first, I'm now all set to get my hands dirty. While implementing the DDD approach, it is quite common to use various services or even repositories(though second one seems to be controversial), inside the domain object. So I was trying to find if EF CTP5 has any support for such dependency injection, but I was quite disappointed to see it did not. After a series of googling I did come across a good enough approach. Although we don't have any control over the object materialization, we do have some control as soon as the object has been materialized. And, it was just fine for me(something is better than nothing). It turned out that using NInject to inject already constructed object is a very trivial task :). Thanks a lot to Nate Kohari, the creator of wonderful NInject.

I know it is enough of ranting, and you may be more interested in looking at some code. So here we go, first a domain model called "Member"

public class Member
{
  //The dependencies
  private IMemberRepository MemberRepository { get; set; }
  private IAuthenticationService AuthenticationService { get; set; }

  public long MemberId { get; set; }
  public string UserName { get; set; }
  public string Password { get; set; }
  public string Email { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }

  //A member must be able to sign in.
  public IDictionary<string, list<string>> SignIn(bool remember)
  {
    var errors = new Dictionary<string, list<string>>();

    //Use of injected object
    var existingMember = MemberRepository.GetSingle(member => member.UserName == this.UserName && member.Password == this.Password);
    if (existingMember != null)
    {
      //Use of injected object
      AuthenticationService.SignIn(this.UserName, remember);
    }
    else
    {
       if (!errors.ContainsKey("UserName,Password"))
       {
         var errorMessages = new List<string>{WTDErrors.UsernameOrPassword};
         errors.Add("UserName,Password", errorMessages);
       }
    }
    return errors;
  }
  //end SignIn()

  //A visitor must be able to register to become a member
  public IDictionary<string, list<string>> RegisterToBecomeMember()
  {
    //even this could be injected, but I'm ok with this for now.
    var validator = new MemberValidator(MemberRepository);
    var errors = new Dictionary<string, list<string>>();

    //The validator objects will contain all the validation rules, so that the domain model doesn't get cluttered. But still the domain object enforces the business rules.
    var validationResult = validator.Validate(this);
    if (validationResult.IsValid)
    {
      MemberRepository.Add(this);
      MemberRepository.Save();
    }
    else
    {
      foreach (ValidationFailure failure in validationResult.Errors)
      {
        var errorMsg = failure.ErrorMessage;
        if (!errors.ContainsKey(failure.PropertyName))
        {
          errors.Add(failure.PropertyName, new List<string>{errorMsg});
        }
        else
        {
          errors[failure.PropertyName].Add(errorMsg);
        }
      }
    }
    return errors;
  }
  //end RegisterToBecomeMember()
}

Leaving all other details aside, the member model is dependent on two external objects, a member-repository, and an authentication service. It is always a good practice to decouple such concerns, from maintainability point of view. A standard way to decouple such external objects is to use dependency injection. The most popular and preferred approach to inject such dependencies would be to use a constructor, for example:

public Member(IMemberRepository memberRepository, IAuthenticationService authenticationService)
{
  this.MemberRepository = memberRepository;
  this.AuthenticationService = authenticationService;
}

But as, discussed earlier it is not possible to change how the objects are materialized in the Entity Framework, so we don't have an option to use the constructor to inject the dependency. Other popular techniques to do dependency injection, are to use property injection or the method injection. In this case I do find method injection more appealing, (note that it is just my personal preference, and property injection is also equally valid approach). So, I'll use a method named "InjectDependencies" as a point to inject the dependencies into the objects.
public InjectDependencies(IMemberRepository memberRepository, IAuthenticationService authenticationService)
{
  this.MemberRepository = memberRepository;
  this.AuthenticationService = authenticationService;
}

Now, NInject has a very easy way to notify that a method or property is injectable. It provides an attribute named "Inject". So, decorating the method "InjectDependencies" with an "Inject" attribute will assure that NInject knows how to inject the dependencies, for example:

[Inject]
public InjectDependencies(IMemberRepository memberRepository, IAuthenticationService authenticationService)
{
  this.MemberRepository = memberRepository;
  this.AuthenticationService = authenticationService;
}

Once we put the above method in the Member class, our domain-object is ready for the dependency injection. The final Member class will look something like this:

public class Member
{
  [Inject]
  public InjectDependencies(IMemberRepository memberRepository, IAuthenticationService authenticationService)
  {
    this.MemberRepository = memberRepository;
    this.AuthenticationService = authenticationService;
  }

  //The dependencies
  private IMemberRepository MemberRepository { get; set; }
  private IAuthenticationService AuthenticationService { get; set; }

  public long MemberId { get; set; }
  public string UserName { get; set; }
  public string Password { get; set; }
  public string Email { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }

  //A member must be able to sign in.
  public IDictionary<string, list<string>> SignIn(bool remember)
  {
    var errors = new Dictionary<string, list<string>>();

    //Use of injected object
    var existingMember = MemberRepository.GetSingle(member => member.UserName == this.UserName && member.Password == this.Password);
    if (existingMember != null)
    {
      //Use of injected object
      AuthenticationService.SignIn(this.UserName, remember);
    }
    else
    {
       if (!errors.ContainsKey("UserName,Password"))
       {
         var errorMessages = new List<string>{WTDErrors.UsernameOrPassword};
         errors.Add("UserName,Password", errorMessages);
       }
    }
    return errors;
  }
  //end SignIn()

  //A visitor must be able to register to become a member
  public IDictionary<string, list<string>> RegisterToBecomeMember()
  {
    //even this could be injected, but I'm ok with this for now.
    var validator = new MemberValidator(MemberRepository);
    var errors = new Dictionary<string, list<string>>();

    //The validator objects will contain all the validation rules, so that the domain model doesn't get cluttered. But still the domain object enforces the business rules.
    var validationResult = validator.Validate(this);
    if (validationResult.IsValid)
    {
      MemberRepository.Add(this);
      MemberRepository.Save();
    }
    else
    {
      foreach (ValidationFailure failure in validationResult.Errors)
      {
        var errorMsg = failure.ErrorMessage;
        if (!errors.ContainsKey(failure.PropertyName))
        {
          errors.Add(failure.PropertyName, new List<string>{errorMsg});
        }
        else
        {
          errors[failure.PropertyName].Add(errorMsg);
        }
      }
    }
    return errors;
  }
  //end RegisterToBecomeMember()
}

Now, let's move forward and make our DbContext ready to inject the dependencies.

public class NInjectDataContext : DbContext
{
  public NInjectDataContext(DbConnection dbconnection,  IKernel kernel): base(dbconnection)
  {
    this.Kernel = kernel;
    (this as IObjectContextAdapter).ObjectContext.ObjectStateManager.ObjectStateManagerChanged += ObjectStateManagerChanged;
  }
  //end constructor

  public DbSet<member> Members { get; set; }

  private void ObjectStateManagerChanged(object sender, CollectionChangeEventArgs e)
  {
    if (e.Action != CollectionChangeAction.Add)
      return;
    var state = (this as IObjectContextAdapter).ObjectContext.ObjectStateManager.GetObjectStateEntry(e.Element).State;
    
    //discard all entities that are not loaded from database.
    if (state != System.Data.EntityState.Unchanged)
      return;
    ObjectContext_ObjectMaterialized(e.Element);
  }
  //end ObjectStateManagerChanged()

  private void ObjectContext_ObjectMaterialized(object element)
  {
    if (Kernel != null)
    {
      //This is the place where injection takes place.
      Kernel.Inject(element);
    }
  }
  //end ObjectContext_ObjectMaterialized()

  public IKernel Kernel
  {
    get;
    private set;
  }
}

This is it, now we have prepared a model that is dependency ready, and a DbContext that can inject dependencies after the objects are materialized. As of now following code will return a member with the dependencies injected:

var kernel = new StandardKernel(); var context = new NInjectDataContext(new SqlConnection(ConnectionStringProvider.ProductionConnection), kernel);
var queriedMember = context.Members.Where(member => member.Id ==1).SingleOrDefault();

Now, I just have a single concern that our Domain-model and the DbContext has yet another dependency upon, the NInject framework. But NInject is a very fantastic and power-full framework which has many extension points, so turning off the dependencies is quite a breeze.

This post has already become very long, so we will try to look at how the dependencies can be removed from NInject in some later posts. Any thoughts are very welcome.

References:
The code for DbContext, has been taken from (with some modifications).
http://rogeralsing.com/2009/05/30/entity-framework-4-entity-dependency-injection/

I was motivated to write this code, and in turn this blog, after reading this stack-overflow question:
http://stackoverflow.com/questions/4562276/entity-framework-ctp5-and-ninject-as-my-ioc

N.B.
I'm not a native English speaker, so I'd like to apologize if the above contents aren't very readable. Also, since this is my first blogging experience I'd be very thankful to anyone who points me towards the right direction.

No comments:

Post a Comment