开发者

ASP.NET MVC and IoC - Chaining Injection

Please be gentle, I'm a newb to this IoC/MVC thing but I am trying. I understand the value of DI for testing purposes and how IoC resolves dependencies at run-time and have been through several examples t开发者_StackOverflow中文版hat make sense for your standard CRUD operations...

I'm starting a new project and cannot come up with a clean way to accomplish user permissions. My website is mostly secured with any pages with functionality (except signup, FAQ, about us, etc) behind a login. I have a custom identity that has several extra properties which control access to data... So....

Using Ninject, I've bound a concrete type* to a method (Bind<MyIdentity>().ToMethod(c => MyIdentity.GetIdentity()); so that when I add MyIdentity to a constructor, it is injected based on the results of the method call.

That all works well. Is it appropriate to (from the GetIdentity() method) directly query the request cookies object (via FormsAuthentication)? In testing the controllers, I can pass in an identity, but the GetIdentity() method will be essentially untestable...

Also, in the GetIdentity() method, I will query the database. Should I manually create a concrete instance of a repository?

Or is there a better way all together?


I think you are reasonably on the right track, since you abstracted away database communication and ASP.NET dependencies from your unit tests. Don't worry that you can't test everything in your tests. There will always be lines of code in your application that are untestable. The GetIdentity is a good example. Somewhere in your application you need to communicate with framework specific API and this code can not be covered by your unit tests.

There might still be room for improvement though. While an untested GetIdentity isn't a problem, the fact that it is actually callable by the application. It just hangs there, waiting for someone to accidentally call it. So why not abstract the creation of identities. For instance, create an abstract factory that knows how to get the right identity for the current context. You can inject this factory, instead of injecting the identity itself. This allows you to have an implementation defined near the application's composition root and outside reach of the rest of the application. Besides that, the code communicates more clearly what is happening. Nobody has to ask "which identity do I actually get?", because it will be clear by the method on the factory they call.

Here's an example:

public interface IIdentityProvider
{
    // Bit verbose, but veeeery clear,
    // but pick another name if you like,
    MyIdentity GetIdentityForCurrentUser();
}

In your composition root you can have an implementation of this:

private sealed class AspNetIdentityProvider : IIdentityProvider
{
    public MyIdentity GetIdentityForCurrentUser()
    {
       // here the code of the MyIdentity.GetIdentity() method.
    }
}

As a trick I sometimes have my test objects implement both the factory and product, just for convenience during unit tesing. For instance:

private sealed class FakeMyIdentity
    : FakeMyIdentity, IIdentityProvider
{
    public MyIdentity GetIdentityForCurrentUser()
    {
       // just returning itself.
       return this;
    }
}

This way you can just inject a FakeMyIdentity in a constructor that expects an IIdentityProvider. I found out that this doesn’t sacrifice readability of the tests (which is important). Of course you want to have as little code as possible in the AspNetIdentityProvider, because you can't test it (automatically). Also make sure that your MyIdentity class doesn't have any dependency on any framework specific parts. If so you need to abstract that as well.

I hope this makes sense.


There are two things I'd kinda do differently here...

  1. I'd use a custom IPrincipal object with all the properties required for your authentication needs. Then I'd use that in conjunction with custom cookie creation and the AuthenticateRequest event to avoid database calls on every request.

  2. If my IPrincipal / Identity was required inside another class, I'd pass it as a method parameter rather than have it as a dependency on the class it's self.
    When going down this route I use custom model binders so they are then parameters to my actions rather than magically appearing inside my action methods.
    NOTE: This is just the way I've been doing things, so take with a grain of salt.

Sorry, this probably throws up more questions than answers. Feel free to ask more questions about my approach.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜