Difficulty replacing Web API IHttpControllerActivator or other services

331 views
Skip to first unread message

Michael Powell

unread,
Dec 15, 2016, 5:57:55 PM12/15/16
to Autofac
Hello,

I want to enable Autofac for use in Web API. Extending into OWIN for purposes of self hosted unit testing, but that's only tangential to discussion. For now, focused on Autofac into Web API for the time being.

I want to potentially replace the IHttpControllerActivator service, but I don't know what to do once I have registered that type with the builder. The challenge with Autofac is that the builder is not an IContainer at that moment, which I would otherwise use to resolve an instance of the activator and replace the HttpConfiguration service.

Is there a way to register some sort of callback when the IContainer has been built so that I can register that type?

Or, alternately, is there an easier way to go about registering service(s) via Autofac for use via the HttpConfiguration?

Thanks...

Regards,

Michael Powell

Michael Powell

unread,
Dec 15, 2016, 6:36:33 PM12/15/16
to Autofac


On Thursday, December 15, 2016 at 5:57:55 PM UTC-5, Michael Powell wrote:
Hello,

I want to enable Autofac for use in Web API. Extending into OWIN for purposes of self hosted unit testing, but that's only tangential to discussion. For now, focused on Autofac into Web API for the time being.

To elaborate, I want to do something along these lines, but I wonder if this effort is already captured by Autofac ASP.NET WebApi 2.2 Integration (https://www.nuget.org/packages/Autofac.WebApi2/), such as providing Autofac-based IDependencyResolver, registering IHttpController(s) found in Assembly(ies), and so on...

public static ContainerBuilder RegisterApiControllers(this ContainerBuilder builder,
    HttpConfiguration config, Assembly assy, params Assembly[] otherAssies)
{
    builder
        .RegisterType<AutofacHttpControllerActivator>()
        .As<IAutofacHttpControllerActivator>()
        .SingleInstance();

    //config.Services.Replace(typeof(IHttpControllerActivator),
    //    container.Resolve<IHttpControllerActivator>());

    // We want all the Types from the Assemblies having to do with IHttpController.
    var ctrlType = typeof(IHttpController);

    var ctrlTypes = new[] {assy}.Concat(otherAssies).SelectMany(a => a.GetTypes()
        .Where(t => t.IsClass && !t.IsAbstract && t.IsAssignableFrom(ctrlType))).ToArray();

    return ctrlTypes.Aggregate(builder, (g, x) =>
    {
        g.RegisterType(x).As<IHttpController>().InstancePerDependency();
        return g;
    });
}

Travis Illig

unread,
Dec 16, 2016, 11:37:09 AM12/16/16
to Autofac
Why does the controller activator itself need to be resolved from the container? What does the custom activator look like?

Michael Powell

unread,
Dec 16, 2016, 12:04:55 PM12/16/16
to Autofac


On Friday, December 16, 2016 at 11:37:09 AM UTC-5, Travis Illig wrote:
What does the custom activator look like?

Something like this:

public class AutofacHttpControllerActivator : IAutofacHttpControllerActivator
{
    private class ControllerReleaseResource : IDisposable
    {
        private readonly IDisposable _disposable;

        internal ControllerReleaseResource(IHttpController ctrl)
        {
            _disposable = ctrl as IDisposable;
        }

        public void Dispose()
        {
            if (_disposable == null) return;
            _disposable.Dispose();
        }
    }

    private readonly IContainer _container;

    public AutofacHttpControllerActivator(IContainer container)
    {
        _container = container;
    }

    public virtual IHttpController Create(HttpRequestMessage request,
        HttpControllerDescriptor ctrlDescriptor, Type ctrlType)
    {
        var ctrl = (IHttpController) _container.Resolve(ctrlType);

        request.RegisterForDispose(new ControllerReleaseResource(ctrl));

        return ctrl;
    }
}

Why does the controller activator itself need to be resolved from the container?

For consistency.

Instead of "building" and replacing ASP.NET HttpConfiguration services, it seems like I should wait until after I have the IContainer. I'd like to be able to replace services, many times with "default" implementations, but sometimes with specialized implementations.

Regards,

Michael

Travis Illig

unread,
Dec 17, 2016, 12:23:07 PM12/17/16
to Autofac
Be careful of that version of the controller activator. It's not taking into account request-based lifetime scopes, so you're going to get every controller out of the root lifetime scope. I will assume that what you've posted is a simplified version of whatever it is you're actually doing.

You can always try registering things as a lambda.

builder.Register(ctx => new AutofacHttpControllerActivator(ctx.Resolve<ILifetimeScope>())).As<IHttpControllerActivator>().SingleInstance();

By using SingleInstance there, the lifetime scope that gets resolved will be the root scope - the container.

However, in Web API 2 (e.g., not .NET Core) things aren't very consistent. Some things get cached that shouldn't (like filter instances). There's a whole "dual container" sort of thing where you have the dependency resolver but also a services container on the HttpConfiguration... I would recommend not trying to force a square peg into a round hole. If you don't have to make the code complex just "for consistency" then, seriously, don't. Just build the activator and set it on the Services collection and call it a day. It doesn't really matter when that happens as long as it's before the app starts taking requests.

All of this mess got fixed in ASP.NET Core because it was such a mess.

Michael Powell

unread,
Dec 17, 2016, 12:33:46 PM12/17/16
to aut...@googlegroups.com
On Sat, Dec 17, 2016 at 12:23 PM, Travis Illig <travis...@gmail.com> wrote:
> Be careful of that version of the controller activator. It's not taking into
> account request-based lifetime scopes, so you're going to get every
> controller out of the root lifetime scope. I will assume that what you've
> posted is a simplified version of whatever it is you're actually doing.

Yes, it's a draft first version. Thanks for the clarification and for
the insights.

> You can always try registering things as a lambda.
>
> builder.Register(ctx => new
> AutofacHttpControllerActivator(ctx.Resolve<ILifetimeScope>())).As<IHttpControllerActivator>().SingleInstance();
>
> By using SingleInstance there, the lifetime scope that gets resolved will be
> the root scope - the container.
>
> However, in Web API 2 (e.g., not .NET Core) things aren't very consistent.
> Some things get cached that shouldn't (like filter instances). There's a
> whole "dual container" sort of thing where you have the dependency resolver
> but also a services container on the HttpConfiguration... I would recommend
> not trying to force a square peg into a round hole. If you don't have to
> make the code complex just "for consistency" then, seriously, don't. Just
> build the activator and set it on the Services collection and call it a day.
> It doesn't really matter when that happens as long as it's before the app
> starts taking requests.

As I've learned/am learning, yes.

> All of this mess got fixed in ASP.NET Core because it was such a mess.

You bet, I appreciate that and am awaiting the vNext to supersede
API/MVC, if I understand the promised life cycle correctly. It's
certainly taken them long enough to work through the thing.

> --
> You received this message because you are subscribed to a topic in the
> Google Groups "Autofac" group.
> To unsubscribe from this topic, visit
> https://groups.google.com/d/topic/autofac/AwDtuJhAXZU/unsubscribe.
> To unsubscribe from this group and all its topics, send an email to
> autofac+u...@googlegroups.com.
> To post to this group, send email to aut...@googlegroups.com.
> Visit this group at https://groups.google.com/group/autofac.
> For more options, visit https://groups.google.com/d/optout.
Reply all
Reply to author
Forward
0 new messages