Injecting Dependencies into Domain Model classes with Nhibernate (ASP.NET MVC + IOC)
Asked Answered
W

4

7

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 of 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?

Wormhole answered 13/1, 2010 at 0:32 Comment(0)
W
5

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

Waitabit answered 13/1, 2010 at 0:44 Comment(1)
Interceptors look interesting, I'll give those a try, thanks!Wormhole
A
2

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!

Apollo answered 13/1, 2010 at 0:52 Comment(0)
U
1

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.

Uteutensil answered 13/1, 2010 at 0:36 Comment(3)
Because Administrator can get instantiated by Nhibernate (ie. if it's loading an Administrator object from the database). So how would I get NHibernate to inject the service? That's the bit I'm stuck on.Wormhole
I agree with the Jarretts 2nd edit. In this case you injecting a dependency that shouldn't be there.Linkous
I agree in this instance, maybe dependency injection isn't the best approach. But I may potentially have other cases where it's needed such as an ITaxCalculatorService, so it would be useful to know how to do this for future reference.Wormhole
L
1

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 :(

Lowtension answered 13/1, 2010 at 15:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.