1
votes

I have a SPA ASP.NET WebAPI application which previously allowed anonymous access. I have now configured ASP.Net Identity for it but I cannot get the Identity related controllers and my application's other controllers to work at the same time :-( It's either one or the other!

I have added the startup class to my project:

using Test.MyProject;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.DataHandler.Encoder;
using Microsoft.Owin.Security.OAuth;
using Newtonsoft.Json.Serialization;
using Owin;
using System;
using System.Configuration;
using System.Linq;
using System.Net.Http.Formatting;
using System.Web.Http;

[assembly: OwinStartup(typeof(Test.Client.Startup))]
namespace Test.Client
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            HttpConfiguration httpConfig = new HttpConfiguration();

            ConfigureOAuthTokenGeneration(app);

            ConfigureWebApi(httpConfig);

            app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

            //app.UseWebApi(httpConfig);  // If this line is commented out my application's controllers work. But then my Account Controller does't work. It if is included, my application's controllers don't work, whilst the Account Controller work

            GlobalConfiguration.Configure(WebApiConfig.Register);
        }

        private void ConfigureOAuthTokenGeneration(IAppBuilder app)
        {
            // Configure the db context and user manager to use a single instance per request
            app.CreatePerOwinContext(ApplicationDbContext.Create);
            app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
        }

        private void ConfigureWebApi(HttpConfiguration config)
        {
            config.MapHttpAttributeRoutes();

            var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
            jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
        }
    }
}

And I have added controllers for managing Users and Roles. The statement GlobalConfiguration.Configure(WebApiConfig.Register) was previously in the application start event in global.aspx.cs but now moved to the startup class to have everything in the same place.

The WebApiConfig.Register method looks like this:

public static void Register(HttpConfiguration config)
{
    var container = new UnityContainer();
    // Web API configuration and services

    string appStorageProvider = ConfigurationManager.AppSettings["StorageProvider"];
    var provider =(TestComposition.StorageProvider) Enum.Parse(typeof (TestComposition.StorageProvider), appStorageProvider, true);

    TestComposition.Setup(container, provider);
    container.RegisterType<GeneralLogger, GeneralLogger>();

    container.RegisterType<IExceptionLogger, ExceptionLogger>();

    config.EnableCors();

    config.DependencyResolver = new UnityResolver(container);
    config.Services.Add(typeof (IExceptionLogger), container.Resolve<GeneralLogger>());

    // Web API routes
    config.MapHttpAttributeRoutes();
}

In my new AccountController I have code which allows me to retrieve the ApplicationUserManager from the OwinContext set up in the Startup class.

protected ApplicationUserManager AppUserManager
{
    get
    {
        return _AppUserManager ?? Request.GetOwinContext().GetUserManager<ApplicationUserManager>();
    }
}

With app.UseWebApi(httpConfig) commented out as shown above my application works as it used to. But if I invoke any action on my new AccountController I get this:

Request.GetOwinContext() error CS1061: 'HttpRequestMessage' does not contain a definition for 'GetOwinContext' and no extension method 'GetOwinContext' accepting a first argument of type 'HttpRequestMessage' could be found (are you missing a using directive or an assembly reference?)

If I comment in the app.UseWebApi(httpConfig) statement the AccountController works but then my other controllers don't work. Here I get errors like these:

{ "message": "An error has occurred.", "exceptionMessage": "An error occurred when trying to create a controller of type 'TestController'. Make sure that the controller has a parameterless public constructor.", "exceptionType": "System.InvalidOperationException", "stackTrace": " at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)\r\n at System.Web.Http.Controllers.HttpControllerDescriptor.CreateController(HttpRequestMessage request)\r\n at System.Web.Http.Dispatcher.HttpControllerDispatcher.d__1.MoveNext()", "innerException": { "message": "An error has occurred.", "exceptionMessage": "Type 'MyProject.Api.TestController' does not have a default constructor", "exceptionType": "System.ArgumentException", "stackTrace": " at System.Linq.Expressions.Expression.New(Type type)\r\n at System.Web.Http.Internal.TypeActivator.Create[TBase](Type instanceType)\r\n at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, Func`1& activator)\r\n at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)" } }

Any idea what is going on here?

1

1 Answers

1
votes

The problem is that you are not using the same HttpConfiguration instance during start up for configuring WebApi with OWIN.

This way your OWIN Web API middleware has no knowledge of UnityContainer, and will use its default implementation. Because of this the creation of your controllers failed.

Please use the same HttpConfiguration for both Web Api configuration and UnityContainer registration:

public class Startup {
    public void Configuration(IAppBuilder app) { 
        ConfigureOAuthTokenGeneration(app);

        ConfigureWebApi(app);

        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);            
    }

    private void ConfigureOAuthTokenGeneration(IAppBuilder app) {
        // Configure the db context and user manager to use a single instance per request
        app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
    }

    private void ConfigureWebApi(IAppBuilder app) {
        // configure Web Api
        GlobalConfiguration.Configure(WebApiConfig.Register);
        // Manually assign httpConfig from GlobalConfiguration
        HttpConfiguration httpConfig = GlobalConfiguration.Configuration;
        // Use same config with OWIN app
        app.UseWebApi(httpConfig);
    }
}

You are configuring web api in multiple places. The WebApiConfig.Register method should consolidate everything you want configured for the HttpConfiguration:

public static void Register(HttpConfiguration config) {
    var container = new UnityContainer();
    // Web API configuration and services

    string appStorageProvider = ConfigurationManager.AppSettings["StorageProvider"];
    var provider =(TestComposition.StorageProvider) Enum.Parse(typeof (TestComposition.StorageProvider), appStorageProvider, true);

    TestComposition.Setup(container, provider);
    container.RegisterType<GeneralLogger, GeneralLogger>();

    container.RegisterType<IExceptionLogger, ExceptionLogger>();

    config.EnableCors();

    config.DependencyResolver = new UnityResolver(container);
    config.Services.Add(typeof (IExceptionLogger), container.Resolve<GeneralLogger>());

    // Web API routes
    config.MapHttpAttributeRoutes();

    // configure formatter
    var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
    jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
}