6

I am in the process of changing my Asp.Net MVC3 project to use Autofac for service injection into my controllers. So far this has been pretty straightforward. My services all have a Telerik OpenAccess db property which I inject through the constructors (In a service base class). And my controllers all have constructor properties for services which also get injected.

I have a class called AuditInfo which encapsulates auditable properties of a controller:

public class AuditInfo
{      
    public string RemoteAddress { get; set; }

    public string XForwardedFor { get; set; }

    public Guid UserId { get; set; }

    public string UserName { get; set; }
}

My OpenAccess db property in my service classes needs to have an instance of this class injected in to it in order to use as auditing information in various database calls.

The problem is that this is not a class that can be instantiated at Application_Start because at least two properties of it, RemoteAddress and XForwardedFor are populated at the earliest stage of OnActionExecuting, i.e. once the Request variables exist.

Therefore, I instantiate this in the OnActionExecuting method of my BaseController class as such:

protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
    base.OnActionExecuting(filterContext);
    db.AuditInfo = AuditInfo;                                      
}

public AuditInfo AuditInfo
{
    get
    {
        return new AuditInfo()
        {
            RemoteAddress = this.Request.ServerVariables["REMOTE_ADDR"],
            XForwardedFor = this.Request.ServerVariables["X_FORWARDED_FOR"],
            UserId = this.UserId,
            UserName = this.UserName
        };
    }
}

So - my problem/questions are:

  1. I don't like this direct reach in to the OpenAccess db property in OnActionExecuting.
  2. I'd like this AuditInfo basically to be injected in to any AuditInfo property anywhere
  3. I don't think I can use constructor injection for AuditInfo because Services depend on db - controllers depend on services - db depends on AuditInfo BUT AuditInfo is not available until a controller is instantiated and received its first request. => circular dependency...

How would I setup autofac to inject AuditInfo in to any class that has it as a property? Or is there a better way of sidestepping the circular dependency and using some form of lambda/lazy constructor properties?

Is it at all concerning that AuditInfo gets re-initialized potentially unnecessarily at every request even though a lot of requests may be part of the same session and not have different ip address/user info?

Thanks

t316
  • 1,149
  • 1
  • 15
  • 28
  • I disagree with problem/question 3 - as Steven points out you can use `HttpContext.Current`. So `AuditInfo` does not depend on the controller, so there is no circular dependency, so you could constructor-inject the `AuditInfo` if you want. – default.kramer Oct 05 '11 at 19:13
  • Well - I think this is because the proposed solution is using a global static variable to reference the Request object rather than initializing the AuditInfo object inside of the controller where the Request object naturally exists. I think that the server variables don't even exist yet in the Application_Start, where th injection is occuring, because an action has to be called first, no? – t316 Oct 06 '11 at 02:06

2 Answers2

3

It turns out Autofac's MVC Integration can resolve an HttpRequestBase for you. So you don't need to reference HttpContext.Current.Request directly.

Autofac's implementation uses HttpContext.Current behind the scenes. This works because the MVC framework sets HttpContext.Current before your code (or Autofac's) runs. So there's no circular dependency - the Request "naturally exists" on HttpContext.Current.Request just as much as in your controller. (This question kind of explains how)

So you could do an IAuditInfoFactory as Steven suggests but demand an HttpRequestBase in its constructor instead of using HttpContext.Current if it makes you feel better about not referencing static variables.

Also, there's no circular dependency and you could constructor-inject the AuditInfo if you want:

builder.Register(c => c.Resolve<IAuditInfoFactory>().CreateNew())
    .As<AuditInfo>()
    .InstancePerHttpRequest();
Community
  • 1
  • 1
default.kramer
  • 5,943
  • 2
  • 32
  • 50
  • Steven did a great job of framing the foundation of the correct answer however the lack of HttpRequestBase injection and default.kramer's addition of that point makes his answer a little more complete. Not sure - how I am suposed to handle the equitable marking of the correct answer in a situation like this - please feel free to let me know. For now I am marking default.kramer's answer as correct. Thanks – t316 Oct 12 '11 at 13:56
2

The answer is: Use a factory.

Inject an IAuditInfoFactory into the type that needs it, and create an implementation like this:

public class HttpRequestAuditInfoFactory : IAuditInfoFactory
{
    // Service for requesting information about the current user.
    private readonly ICurrentUserServices user;

    public HttpRequestAuditInfoFactory(ICurrentUserServices user)
    {
        this.user = user;
    }

    AuditInfo IAuditInfoFactory.CreateNew()
    {
        var req = HttpContext.Current.Request;

        return new AuditInfo()
        {
            RemoteAddress = req.ServerVariables["REMOTE_ADDR"],
            XForwardedFor = req.ServerVariables["X_FORWARDED_FOR"],
            UserId = this.user.UserId,
            UserName = this.user.UserName
        };
    }
}

You can register that class as follows:

builder.RegisterType<HttpRequestAuditInfoFactory>()
    .As<IAuditInfoFactory>()
    .SingleInstance();

Now you can inject

Steven
  • 166,672
  • 24
  • 332
  • 435
  • Thanks for the quick response. This should work, but doesn't this solution really rely on referencing HttpContext.Current.Request and reading variables from it in a foreign context rather than storing the necessary variables during the natural lifecycle of HttpContext.Current.Request and injecting those variables in to the foreign context with loose coupling? Meaning - the IAuditInfoFactory implementation needs to reference System.Web and directly know about HttpContext.Current.Request for this to work... – t316 Oct 05 '11 at 18:14
  • This particular `HttpRequestAuditInfoFactory` implementation does indeed takes a hard dependency on the `HttpContext`. In that sense it is platform aware. This is not a problem, but for that reason it is not (or should not be) part of your application, but should be part of what we call the [Composition Root](http://blog.ploeh.dk/2011/07/28/CompositionRoot.aspx) (CR). This is the start-up path of the application. No other part than the CR should be aware of the existance of this `HttpRequestAuditInfoFactory`. The application only knows about the `IAuditInfoFactory`. – Steven Oct 05 '11 at 19:07
  • Because the rest of the application knows about `IAuditInfoFactory` but not about the `HttpRequestAuditInfoFactory`, it makes it easy to migrate (part of) your application to -for instance- a Windows Service. You will need Windows Service specific implementation of the `IAuditInfoFactory` and wire it up, instead of the `HttpRequestAuditInfoFactory` in the CR (the `main` method) of the Windows Service. – Steven Oct 05 '11 at 19:10