Injecting Dependencies into Domain Model classes with Nhibernate (ASP.NET MVC + IOC)
I'm building an ASP.NET MVC application that uses a DDD (Domain Driven Design) approach with database access handled by NHibernate. I have domain model class (Administrator) that I want to inject a dependency into via an IOC Container such as Castle Windsor, something like this:
public class Administrator
{
public virtual int Id { get; set; }
//.. snip ..//
public virtual string HashedPassword { get; protected set; }
public void SetPassword(string plainTextPassword)
{
IHashingService hasher = IocContainer.Resolve<IHashingService>();
this.HashedPassword = hasher.Hash(plainTextPassword);
}
}
I basically want to inject IHashingService for the SetPassword method without calling the IOC Container directly (because this is suppose to be an IOC Anti-pattern). But I'm not sure how to go about doing it. My Administrator object either gets instantiated via new Administrator();
or it gets loaded via NHibernate, so how would I inject the IHashingService into the Administrator class?
On second thoughts, am I going about this the right way? I was hoping to avoid having my codebase littered with...
currentAdmin.Password = HashUtils.Hash(password, Algorithm.Sha512);
...and instead get the domain model itself to take care o开发者_StackOverflow中文版f hashing and neatly encapsulate it away. I can envisage another developer accidently choosing the wrong algorithm and having some passwords as Sha512, and some as MD5, some with one salt, and some with a different salt etc. etc. Instead if developers are writing...
currentAdmin.SetPassword(password);
...then that would hide those details away and take care of those problems listed above would it not?
There are already similar questions here on SO:
Dependency injection with NHibernate objects
DI/IoC, NHibernate and help in getting them to work together
You'll need to use Interceptors. Take a look at Fabio Maulo's post for implementation:
https://nhibernate.info/blog/2008/12/12/entities-behavior-injection.html
You need to remember how it was hashed. This is so that you can hash a string in the future to check and see if it's their password, comparing that with the hashed value. That means that you need to store an enum or some other field in your object that indicates the hashing mechanism that was used in your database.
Otherwise, if you change your default hashing implementation, all of your old hashed passwords are no longer good, and your users will be left scratching their heads as to why their passwords no longer work--and you'll end up an IHashingService
interface that provides no flexibility (since the hashing implementation cannot be changed without adding weird rules like "use this hash for Administrators created before 2010-01-12"), existing for no real good reason.
To that end, I would add the appropriate field (an enum, a string returned by the IHashingService
interface, something) and either have NHibernate instantiate the hashing service for me via an IUserType
implementation, or I'd use a factory pattern where the concrete instances were provided to the factory by the IoC container. This would be combining Jarrett's method-level injection with a solution that allows re-hydrated objects to find their hashing implementations without being dependent on the IoC container.
Good luck!
Is there a reason that you can't pass the IHashingService
in the constructor for the Administrator
class? That's how I would resolve the dependency.
public class Administrator
{
private readonly IHashingService _hashingService;
public Administrator(IHashingService hashingService)
{
_hashingService = hashingService;
}
// <snip>
public void SetPassword(string plainTextPassword)
{
this.HashedPassword = _hashingService.Hash(plainTextPassword);
}
}
Edit #1
If pulling from a model, try using method-level injection.
public void SetPassword(string plainText, IHashingService hasher)
{
if (hasher == null) throw new ArgumentNullException("hasher");
this.HashedPassword = hasher.Hash(plainText);
}
Edit #2
Also, why not make it easy on yourself and just make an extension on string?
public static class ExtensionsOfString
{
public static string Hash(this string s)
{
// hash with SHA256
return hashedString;
}
}
While I do realize that there's a "replaceable" code aspect of using dependency injection, this isn't exactly a big deal for this example. You don't really need a IPasswordEncryptionService the same way you'd need a, say, ICreditCardAuthorizationService. If, someday, you change your hashing algorithm from SHA256 to SHA512, you will now have invalidated every password in your database.
Either hash the password in an application façade (if you are using any) or supply the IHashingService
implementation on every call to Administrator.SetPassword(..)
. I think it was called double dispatch?!
If you insist on DI-in-entity solution, I have done something like that with PostSharp AOP and PostSharp4Spring by declaring [Configurable]
attribute on the entity, but the solution is for Spring.Net. You can look here for more info. Also, if you are configuring NHibernate from the DI container, you can fall in a recursion trying to DI an entity before the container has finished configuring. You need a simple static class with method to suppress DI on entity construction during container initialization. Cannot provide an example a the moment though :(
精彩评论