Josh Russo
Josh Russo

Reputation: 3241

Autofac failing to pickup in Web API and OWIN

I'm trying to setup a project that uses both MVC and Web API via OWIN and I'm having trouble getting Autofac to take a effect.

Here's how I'm initializing Web API:

public partial class Startup
{
    public static void ConfigureWebApi(IAppBuilder app)
    {
        var config = BuildHttpConfiguration();

        var container = AutoFacConfig.BuildContainer();
        config.DependencyResolver = new AutofacWebApiDependencyResolver(container);

        app.UseAutofacMiddleware(container);
        app.UseAutofacWebApi(config);
        app.UseWebApi(config);
    }

    private static HttpConfiguration BuildHttpConfiguration()
    {
        var config = new HttpConfiguration();

        // Response formatter config
        config.Formatters.Remove(
            GlobalConfiguration.Configuration.Formatters.XmlFormatter);
        config.Formatters.JsonFormatter.SerializerSettings.ContractResolver =
            new CamelCasePropertyNamesContractResolver();

        // Setup Web API error reporting
        var customErrors = (CustomErrorsSection)ConfigurationManager.GetSection("system.web/customErrors");

        IncludeErrorDetailPolicy errorDetailPolicy;

        switch (customErrors.Mode)
        {
            case CustomErrorsMode.RemoteOnly:
                errorDetailPolicy
                    = IncludeErrorDetailPolicy.LocalOnly;
                break;
            case CustomErrorsMode.On:
                errorDetailPolicy
                    = IncludeErrorDetailPolicy.Never;
                break;
            case CustomErrorsMode.Off:
                errorDetailPolicy
                    = IncludeErrorDetailPolicy.Always;
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }

        config.IncludeErrorDetailPolicy = errorDetailPolicy;

        config.MapHttpAttributeRoutes();

        SwaggerConfig.ConfigureSwagger(config);

        return config;
    }
}

The BuildContainer() method looks like the following. This method is used to build the container for both MVC and Web API:

public static IContainer BuildContainer()
{
    var builder = new ContainerBuilder();

    // Register your MVC controllers.
    builder.RegisterControllers(typeof(MvcApplication).Assembly)
        .PropertiesAutowired();

    builder.RegisterApiControllers(typeof(MvcApplication).Assembly);

    // OPTIONAL: Register model binders that require DI.
    builder.RegisterModelBinders(Assembly.GetExecutingAssembly());
    builder.RegisterModelBinderProvider();

    // OPTIONAL: Register web abstractions like HttpContextBase.
    builder.RegisterModule<AutofacWebTypesModule>();

    // OPTIONAL: Enable property injection in view pages.
    builder.RegisterSource(new ViewRegistrationSource());

    // OPTIONAL: Enable property injection into action filters.
    builder.RegisterFilterProvider();

    // Bind the core types
    Core.Infrastructure.AutoFacConfig.BuildContainer(builder);

    builder.RegisterType<Postal.EmailService>().As<Postal.IEmailService>();

    // Effectively auto-wires the anything with an interface within Web assembly infrastructure folder
    builder.RegisterAssemblyTypes(typeof(IJwtHelper).Assembly)
        .Where(t => t.Namespace != null && t.Namespace.StartsWith("MyApp.Web.Infrastructure") && t.GetInterfaces().Any())
        .AsImplementedInterfaces()
        .InstancePerLifetimeScope();

    // Set the dependency resolver to be Autofac.
    return builder.Build();
}

I have Web API controllers setup in an area and I had everything working with standard MVC controllers. I would like to use Web API controllers for where it's appropriate, but every time I try to request a controller based on ApiController I get the error:

An error occurred when trying to create a controller of type 'XxxController'. Make sure that the controller has a parameterless public constructor.

-- Edit --

The API area config looks like the following. (Side note: I know this isn't the "proper" way to configure Web API. I include the {action} because I feel there are too many controller files otherwise.)

public class ApiAreaRegistration : AreaRegistration
{
    public override string AreaName
    {
        get { return "Api"; }
    }

    public override void RegisterArea(AreaRegistrationContext context)
    {
        context.Routes.MapHttpRoute(
            "Api_default",
            "Api/{controller}/{action}/{id}",
            new { action = "Index", id = UrlParameter.Optional }
        );

    }

}

The authentication controller looks like this:

public class AuthenticationController : MyAppApiJwtController
{
    private readonly IUserRepository _userRepository;
    private readonly IAppSettingsHelper _appSettingsHelper;
    private readonly IJwtHelper _jwtHelper;
    private readonly IDeviceRepository _deviceRepository;
    private readonly IINLDataService _inlDataService;

    public AuthenticationController(IUserRepository userRepository, IAppSettingsHelper appSettingsHelper, IJwtHelper jwtHelper, IDeviceRepository deviceRepository, IINLDataService inlDataService)
    {
        _userRepository = userRepository;
        _appSettingsHelper = appSettingsHelper;
        _jwtHelper = jwtHelper;
        _deviceRepository = deviceRepository;
        _inlDataService = inlDataService;
    }

    [HttpPost]
    [AllowAnonymous]
    public LoginResponseModel Login(LoginModel model)
    {
        ...
    }
}

The MyAppApiJwtController looks like this:

public class MyAppApiJwtController : ApiController
{
    internal IAuthenticationManager AuthenticationManager
    {
        get { return Request.GetOwinContext().Authentication; }
    }

    private JwtUserIdentity _currentJwtUser;
    public JwtUserIdentity CurrentJwtUser
    {
        get
        {
            if (_currentJwtUser != null)
                return _currentJwtUser;

            if (User == null)
                return null;

            _currentJwtUser = new JwtUserIdentity((ClaimsIdentity)User.Identity);

            return _currentJwtUser;
        }

    }
}

-- Edit 2 --

The URL I'm attempting to use is http://localhost:20630/api/authentication/login

-- Edit 3 --

The MVC configuration looks like the following. This is called just before the Web API configuration:

public partial class Startup
{
    public static void ConfigureMvc()
    {
        AreaRegistration.RegisterAllAreas();

        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);

        MvcConfig.RegisterRoutes(RouteTable.Routes);

        MvcConfig.ValueConfig();

        BundleConfig.RegisterBundles(BundleTable.Bundles);

        AutomapperConfig.Configure();
        JsonConfig.Configure();

        AutoFacConfig.ConfigureContainer();

    }
}

Upvotes: 0

Views: 748

Answers (1)

Federico Dipuma
Federico Dipuma

Reputation: 18295

ASP.NET Web API does not support MVC areas by default.
This code:

public class ApiAreaRegistration : AreaRegistration
{
    public override string AreaName
    {
        get { return "Api"; }
    }

    public override void RegisterArea(AreaRegistrationContext context)
    {
        context.Routes.MapHttpRoute(
            "Api_default",
            "Api/{controller}/{action}/{id}",
            new { action = "Index", id = UrlParameter.Optional }
        );
    }
}

Will instruct the framework to map any route starting with Api to MVC controllers, but, at the same time, such controllers exists only for Web API. This conflict directly relates to the exception thrown when the Dependency Resolver tries to create an instance of the controller (the exception message may be misleading).

What could be happening:

  1. MVC is executed first, and tries to map your route api/authentication/login. This URI matches your AreaRegistration, so it will try to route the request to the AuthenticationController inside your Api area.
  2. MVC asks the Dependency Resolver to create an instance of the above controller, that must inherit from Controller (that's because we are in the MVC context).
  3. The resolver does not have a registration for a MVC Controller that is called AuthenticationController (the AuthenticationController inherits from ApiController), and it returns null (because that's the expected behavior of the IServiceProvider.GetService method).
  4. MVC then reverts to its default implementation for creating the controller, but finds that AuthenticationController class does not have a parameterless constructor. An exception is thrown.

Please try by removing this area declaration: it is not useful (add your api prefix inside RoutePrefix or Route attributes for your controllers/actions) and works only for MVC, while you are defining Web API as an OWIN middleware.

Reference:
ASP.Net WebAPI area support

Upvotes: 1

Related Questions