0

We've got an MVC 5.1 app that works with Autofac DI. I've implemented some basic web api functionality to pull and post data which works. We now want to add authentication on the web api controllers. In order to do this, we are going to utilise some of the same Autofac configuration we've written to Mvc. So, we make calls such as:

        builder.RegisterControllers(assembly).InstancePerHttpRequest();
        builder.RegisterApiControllers(assembly);
        builder.RegisterModelBinders(assembly).InstancePerHttpRequest();
        builder.RegisterType<LogAttribute>().PropertiesAutowired();
        builder.RegisterFilterProvider();

        // Needed to allow property injection in custom action filters.
        builder.RegisterType<ExtensibleActionInvoker>().As<IActionInvoker>();
        if (modules != null)
        {
            foreach (var module in modules)
            {
                builder.RegisterModule(module);
            }
        }

We've also got an Api module that contains the code below and is registered (we know because we've debugged and seen the code called):

public class MvcModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterModule(new AutofacWebTypesModule());

        builder.Register(ctx => HttpContext.Current.GetOwinContext()).As<IOwinContext>();
        builder.Register(ctx => HttpContext.Current.User.Identity).As<IIdentity>().InstancePerLifetimeScope();
        builder.Register(ctx => HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>()).As<ApplicationUserManager>().InstancePerLifetimeScope();
        builder.Register(c => BundleTable.Bundles).As<BundleCollection>().InstancePerLifetimeScope();
        builder.Register(c => RouteTable.Routes).As<RouteCollection>().InstancePerLifetimeScope();
        builder.RegisterType<CurrentUser>().As<ICurrentUser>().InstancePerLifetimeScope();
        builder.RegisterType<ApplicationUser>().As<IApplicationUser>().InstancePerLifetimeScope();

        builder.RegisterType<UserManager<ApplicationUser, Guid>>();
        builder.Register(c=>new appContext()).InstancePerHttpRequest();

        base.Load(builder);
    }
}        

public class ApiModule: Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
                .Where(t => !t.IsAbstract && typeof(ApiController).IsAssignableFrom(t))
                .InstancePerMatchingLifetimeScope(AutofacWebApiDependencyResolver.ApiRequestTag);


        builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).PropertiesAutowired();
        builder.RegisterModule<AutofacWebTypesModule>();
    }
}

we then have these two statements which should set resolvers for both mvc and web api (I think):

        GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container);
        DependencyResolver.SetResolver(new Autofac.Integration.Mvc.AutofacDependencyResolver(container));

We've got an Mvc AccountController which looks similar to the following (some methods removed for clarity):

public class AccountController : BaseController
{
    public AccountController(IOwinContext owinContext, IDataManager itemsManager)
        : base(owinContext, itemsManager)
    {
    }

    // POST: /Account/Login
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
    {
        if (ModelState.IsValid)
        {
            var user = await UserManager.FindAsync(model.Email, model.Password);
            if (user != null)
            {
                return await LoginCommon(user, model.RememberMe, returnUrl);
            }
            ModelState.AddModelError("", "Invalid username or password.");
        }

        // If we got this far, something failed, redisplay form
        return View(model);
    }
}

When the app is started, a user can login. As expected, the IOwinContext is injected into the controller by Autofac. In other words, this line in the Mvc module correctly registers the OwinContext:

        builder.Register(ctx => HttpContext.Current.GetOwinContext()).As<IOwinContext>();

The UserManager can find the user if they've previously registered.

Within our Api Account controller, we then have the following action:

        [Route("{email}")]
    public async Task<HttpResponseMessage> GetUser(string email)
    {
        var user = await UserManager.FindByEmailAsync(email);
        if (user != null)
        {
            return Request.CreateResponse(HttpStatusCode.OK, user);
        }

        return Request.CreateResponse(HttpStatusCode.NotFound);
    }         

However, the UserManager doesn't work - an exception is raised:

System.InvalidOperationException occurred
HResult=-2146233079
Message=No owin.Environment item was found in the context.
Source=Microsoft.Owin.Host.SystemWeb
StackTrace:
   at System.Web.HttpContextExtensions.GetOwinContext(HttpContext context)
   at application1.UI.Infrastructure.Modules.MvcModule.<Load>b__0(IComponentContext ctx) in c:\tfs\application1\MAIN\Source\.NET\application1.UI\Infrastructure\Modules\MvcModule.cs:line 25
InnerException: 

The module that's being called by Autofac is the Mvc module, not the web api one. The line where this exception occurs is this one:

builder.Register(ctx => HttpContext.Current.GetOwinContext()).As<IOwinContext>();

In other words, the HttpContext object doesn't appear to be configured correct. However, as discussed earlier, it is setup because it works in the same application for Mvc.

So the question is, if Mvc and web api are being used in the same application, where should the owin config for the application be set?

I have viewed some questions asked previously about this topic, but to no avail.

Is it possible to configure Autofac to work with ASP.NET MVC and ASP.NET Web Api

and

MVC Web API not working with Autofac Integration

In searching for resolutions to this particular exception, I've found a couple which talk about missing owin configuration:

No owin.Environment item was found in the context

and

No owin.Environment item was found in the context - only on server

However, I can't understand how the configuration could be missing when Owin works fine for the Mvc part of the application.

We're using VS 2013, .Net 4.5.1, Mvc 5.1 and Autofac with web.api integration.

Community
  • 1
  • 1
ossentoo
  • 1,675
  • 2
  • 20
  • 44

2 Answers2

0

We're doing something very similar in a project, which I have got working using Autofac. I have my UserManager injected into UserService (just a domain layer class at the present time rather than an actual service), but can successfully access the UserManager from both Api and MVC controller methods. Here's how I've set things up, which may help. I haven't created separate modules for API and MVC stuff, just thrown it all in together (your resolvers are set up correctly by the way).

First, the Web Module:

public class WebModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterFilterProvider();
        builder.RegisterType<ExtensibleActionInvoker>().As<IActionInvoker>();
        builder.RegisterControllers(Assembly.GetExecutingAssembly())
          .InjectActionInvoker();
        builder.RegisterApiControllers(Assembly.GetExecutingAssembly());

        // OWIN
        builder.Register(c => HttpContext.Current.GetOwinContext())
          .As<IOwinContext>().InstancePerLifetimeScope();

        // Module chaining
        builder.RegisterModule<DataModule>();
    }
}

Next, the Data Module (there's other modules too but they aren't relevant):

public class DataModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        var context = new EntityContext();
        builder.RegisterInstance(context).As<IEntityContext>().SingleInstance();
        builder.Register(x => new UserStore<ApplicationUser>(context))
          .As<IUserStore<ApplicationUser>>().InstancePerLifetimeScope();
        builder.Register(x =>
        {
            var userStore = x.Resolve<IUserStore<ApplicationUser>>();
            var userManager = new UserManager<ApplicationUser>(userStore);
            return userManager;
        });
    }
}

My EntityContext is an IdentityDbContext<ApplicationUser> that I've also wrapped in an interface so I can set up a fake user store and stuff like that.

The only thing that I can see that's different about what you're doing is that you don't seem to be passing the IdentityContext into your UserManager when it's registered.

levelnis
  • 7,665
  • 6
  • 37
  • 61
  • thanks levelnis. I'll take a look. I'm assuming you started off with an MVC application. Did you have to do anything special in the Startup class re: Owin when you were integrating web-api authentication? – ossentoo Jun 26 '14 at 14:14
  • Maybe that's the difference - all of the authentication on our site is done through MVC - there's no Web API-specific authentication going on – levelnis Jun 26 '14 at 14:23
  • so, you mean all api actions are public? Or that you authenticate via MVC and only authenticated users can use the api? – ossentoo Jun 26 '14 at 15:57
  • The latter - authentication happens via MVC and only authenticated users can use the API – levelnis Jun 26 '14 at 15:58
0

It seems I had something none standard with my setup. I recreated the project from new again from the Mvc template and added a single web api controller. I could then login and retrieve user data via web api as expected.

So, the problem is resolved but I still don't know what we did to break it in the first place.

ossentoo
  • 1,675
  • 2
  • 20
  • 44