Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the identity not loaded when resolving WebApi but is when resolving Mvc controllers

I am using Autofac for an Inversion of Control container which is configured like this

    public void Configuration(IAppBuilder app) {
        configureIoC(app);
        configureAuth(app);
    }

    void configureIoC(IAppBuilder app) {
        var b = new ContainerBuilder();
        //...
        b.Register(c => HttpContext.Current?.User?.Identity 
                    ?? new NullIdentity()).InstancePerLifetimeScope();
        var container = b.Build();
        app.UseAutofacMiddleware(container);
        DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
        GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container);
    }

I believe the fact that this is Autofac versus some other container is probably irrelevant to what I'm seing. They key line here is the one configuring any dependency on IIdentity to be plucked from HttpContext.Current.

I use it like this so that I can have stub-able access to the current user anywhere I want.

public interface ICurrentUser {
    Task<AppUser> Get();
}
public class CurrentUserProvider : ICurrentUser {
    public async Task<AppUser> Get() => await users.FindByNameAsync(currentLogin.GetUserId());

    public CurrentUserProvider(AppUserManager users, IIdentity currentLogin) {
        this.users = users;
        this.currentLogin = currentLogin;
    }

}

I've used this pattern on past projects and it works fine. I'm currently applying it to an existing project and seeing a very strange thing.

  • When an Asp.net Mvc controller depends on ICurrentUser everything works fine
  • When a WebApi controller gets an instance of ICurrentUser the Get operation fails since the instance of IIdentity has not been parsed from the cookie and does not yet have Claims loaded into it (AuthenticationType == null)! Oddly, if I pause the debugger after the WebApi controller is instantiated I can hit HttpContext.Current.User.Identity and see that AuthenticationType == "Cookie" and all claims are there.

What this leads me to conclude is that somehow things are happening in the following order

  • If this is a web api route, the Web Api controller creates an instance
  • Asp.Net Identity fills out the current HttpContext Identity
  • If this is an mvc route, the mvc controller creates an instance
  • Any actions are executed

This of course makes no sense at all!

So the questions are as follows

  1. Is my inference of the order of things in the pipeline correct?
  2. How can I control it to work properly? Why would this have worked on other projects but be causing problems here? Am I wiring something up in the wrong order?

Please don't suggest that I create an IdentityProvider to late-resolve IIdentity. I understand how I can fix the issue, what I don't understand is why this is happening to begin with and how to control the pipeline order of things.

like image 529
George Mauer Avatar asked Jan 10 '16 19:01

George Mauer


People also ask

What is the base controller for all WebAPI controllers to inherit from?

In ASP.NET MVC 5 and Web API 2, there were two different Controller base types. MVC controllers inherited from Controller ; Web API controllers inherited from ApiController .

Can a Web API controller return an MVC view?

You can return one or the other, not both. Frankly, a WebAPI controller returns nothing but data, never a view page. A MVC controller returns view pages. Yes, your MVC code can be a consumer of a WebAPI, but not the other way around.

What is the difference between controller and ControllerBase?

Controller derives from ControllerBase and adds support for views, so it's for handling web pages, not web API requests. If the same controller must support views and web APIs, derive from Controller . The following table contains examples of methods in ControllerBase . Returns 400 status code.

What is the difference between Addsingleton and Addscoped?

Singleton is a single instance for the lifetime of the application domain. Scoped is a single instance for the duration of the scoped request, which means per HTTP request in ASP.NET. Transient is a single instance per code request.


1 Answers

I modified your code just a little, since I don't have NullIdentity() and your CurrentUserProvider wasn't compiling here.

I'm installed these packages:

  • Autofac
  • Autofac.Owin
  • Autofac.Owin
  • Autofac.Mvc5
  • Autofac.Mvc5.Owin
  • Autofac.WebApi2
  • Autofac.WebApi2.Owin

My Startup.cs looks like this:

public partial class Startup
{
    public void Configuration(IAppBuilder app)
    {
        configureIoC(app);
        ConfigureAuth(app);
    }

    void configureIoC(IAppBuilder app) {

        var b = new ContainerBuilder();
        //...
        b.RegisterType<CurrentUserProvider>().As <ICurrentUser>().InstancePerLifetimeScope();
        b.Register(c => HttpContext.Current.User.Identity).InstancePerLifetimeScope();

        b.RegisterControllers(typeof(MvcApplication).Assembly);
        b.RegisterApiControllers(typeof(MvcApplication).Assembly);

        var x = new ApplicationDbContext();
        b.Register<ApplicationDbContext>(c => x).InstancePerLifetimeScope();
        b.Register<UserStore<ApplicationUser>>(c => new UserStore<ApplicationUser>(x)).AsImplementedInterfaces().InstancePerLifetimeScope();
        b.RegisterType<ApplicationUserManager>().InstancePerLifetimeScope();
        b.RegisterType<ApplicationSignInManager>().InstancePerLifetimeScope();

        var container = b.Build();

        app.UseAutofacMiddleware(container);
        DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
        GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container);

    }

}

Your ICurrentUser stuff:

public interface ICurrentUser
{
    Task <ApplicationUser> Get();
}

public class CurrentUserProvider : ICurrentUser
{

    private  ApplicationUserManager users;
    private  IIdentity currentLogin;

    public async Task<ApplicationUser> Get()
    {
        return await users.FindByNameAsync(currentLogin.GetUserId());
    }

    public CurrentUserProvider(ApplicationUserManager users, IIdentity currentLogin)
    {
        this.users = users;
        this.currentLogin = currentLogin;
    }

}

Therefore Global.asax:

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        GlobalConfiguration.Configure(WebApiConfig.Register);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
}

My HomeController which is quite simple:

public class HomeController : Controller
{

    private ICurrentUser current;

    public HomeController(ICurrentUser current)
    {
        this.current = current;
    }

    public ActionResult Index()
    {
        var user = current.Get();
        if (user  == null)
            throw new Exception("user is null");
        return View();
    }

}

...and finally a simple ApiController, which I access by typing localhost/api/TestApi/5:

public class TestApiController : ApiController
{

    private ICurrentUser current;

    public TestApiController(ICurrentUser current)
    {
        this.current = current;
    }

    public string Get(int id)
    {
        var user = current.Get();
        if (user == null)
            throw new Exception("user is null");
        return "";
    }

}

If I just start the project (without even logging in), I receive a GenericIdentity object to support IIdentity interface, look at this:

Debugging

And when I step in (F11) in the Get() method, the IIdentity is properly set with that GenericIdentity, because actually there is no one Logged in the application. That's why I think you don't actually need that NullableIdentity.

enter image description here

Try comparing your code with mine and fix yours so we can see if it works, then eventually you'll find out what was the real cause of the problem, rather than just fixing it (we developers like to know why something just got working).

like image 104
Alisson Reinaldo Silva Avatar answered Nov 14 '22 16:11

Alisson Reinaldo Silva