在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
With the beta release of ASP.NET MVC 4 and the ASP.NET Web API being released a few weeks ago, I decided it was about time to have a look at what the integration story would like for Autofac. The package is available for download on NuGet. Install-Package Autofac.WebApi2 -Version 4.2.0 While building the preview of the Web API integration I had the following goals in mind:
I had some concerns about how easy this would be given the two different modes of hosting that are supported. When self hosting the entry point is a WCF service, and when hosting in ASP.NET the entry point is a HTTP handler. My concern was that wrapping each call to the API controller in an Autofac lifetime scope would require two completely different mechanisms. Perhaps a HTTP module style implementation and WCF extension similar to those found in the existing MVC and WCF integrations. It turns out this was not the case and if you are keen on learning about the details I will discuss them after we have seen some example code (because everyone wants to see some code sooner rather than later). Example CodeIf you are hosting within ASP.NET your protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); BundleTable.Bundles.RegisterTemplateBundles(); var configuration = GlobalConfiguration.Configuration; var builder = new ContainerBuilder(); // Configure the container with the integration implementations. builder.ConfigureWebApi(configuration); // Register API controllers using assembly scanning. builder.RegisterApiControllers(Assembly.GetExecutingAssembly()); // Register API controller dependencies per request. builder.Register<ILogger>(c => new Logger()).InstancePerApiRequest(); var container = builder.Build(); // Set the dependency resolver implementation. var resolver = new AutofacWebApiDependencyResolver(container); configuration.ServiceResolver.SetResolver(resolver); } In the case of self hosting your bootstrapping code would look like this instead: var configuration = new HttpSelfHostConfiguration("http://localhost:8080"); configuration.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new {id = RouteParameter.Optional} ); var builder = new ContainerBuilder(); // Configure the container with the integration implementations. builder.ConfigureWebApi(configuration); // Register API controllers using assembly scanning. builder.RegisterApiControllers(Assembly.GetExecutingAssembly()); // Register API controller dependencies per request. builder.Register<ILogger>(c => new Logger()).InstancePerApiRequest(); var container = builder.Build(); // Set the dependency resolver implementation. var resolver = new AutofacWebApiDependencyResolver(container); configuration.ServiceResolver.SetResolver(resolver); // Open the HTTP server and listen for requests. using (var server = new HttpSelfHostServer(configuration)) { server.OpenAsync().Wait(); Console.WriteLine("Hosting at http://localhost:8080/{controller}"); Console.ReadLine(); } First we are keeping a hold of the Next the The To avoid naming conflicts with the MVC integration the You will notice that the steps required to configure the integration are the same for both hosting scenarios. The only real difference is that the type of the To register a service for lifetime scoping with both MVC controllers and Web API controllers you can apply both the // Register MVC controller and API controller dependencies per request. builder.Register(c => new Logger()).As<ILogger>() .InstancePerHttpRequest() .InstancePerApiRequest(); Implementation DetailsBecause of the two different hosting mechanisms a new way to manage lifetime scopes needed to found. The approach in the MVC and WCFintegrations are completely different and I didn’t want both ways to be present in the Web API integration. The public interface IHttpControllerFactory { // Methods IHttpController CreateController(HttpControllerContext controllerContext, string controllerName); void ReleaseController(IHttpController controller); } This service is resolved from the dependency resolver if present, and is called by the public class AutofacControllerFactory : DefaultHttpControllerFactory { readonly ILifetimeScope _container; readonly ConcurrentDictionary<IHttpController, ILifetimeScope> _controllers = new ConcurrentDictionary<IHttpController, ILifetimeScope>(); internal static readonly string ApiRequestTag = "AutofacWebRequest"; public AutofacControllerFactory(HttpConfiguration configuration, ILifetimeScope container) : base(configuration) { if (container == null) throw new ArgumentNullException("container"); _container = container; } public override IHttpController CreateController(HttpControllerContext controllerContext, string controllerName) { var lifetimeScope = _container.BeginLifetimeScope(ApiRequestTag); controllerContext.Request.Properties.Add(ApiRequestTag, lifetimeScope); try { var controller = base.CreateController(controllerContext, controllerName); if (controller != null) _controllers.TryAdd(controller, lifetimeScope); return controller; } catch (Exception) { lifetimeScope.Dispose(); throw; } } public override void ReleaseController(IHttpController controller) { ILifetimeScope lifetimeScope; if (controller != null && _controllers.TryRemove(controller, out lifetimeScope)) if (lifetimeScope != null) lifetimeScope.Dispose(); } } The public class AutofacControllerActivator : IHttpControllerActivator { public IHttpController Create(HttpControllerContext controllerContext, Type controllerType) { var requestProperties = controllerContext.Request.Properties; if (!requestProperties.ContainsKey(AutofacControllerFactory.ApiRequestTag)) throw GetInvalidOperationException(); ILifetimeScope lifetimeScope = requestProperties[AutofacControllerFactory.ApiRequestTag] as ILifetimeScope; if (lifetimeScope == null) throw GetInvalidOperationException(); return lifetimeScope.ResolveOptional(controllerType) as IHttpController; } internal static InvalidOperationException GetInvalidOperationException() { return new InvalidOperationException( string.Format(AutofacControllerActivatorResources.LifetimeScopeMissing, typeof(ILifetimeScope).FullName, typeof(HttpRequestMessage).FullName, typeof(AutofacControllerFactory).FullName)); } }
IHttpController httpController = this._controllerFactory.CreateController(controllerContext, str); if (httpController == null) { return TaskHelpers.FromResult<HttpResponseMessage>(request.CreateResponse(HttpStatusCode.NotFound)); } Regardless, back in the The public class AutofacWebApiDependencyResolver : IDependencyResolver { readonly ILifetimeScope _container; public AutofacWebApiDependencyResolver(ILifetimeScope container) { if (container == null) throw new ArgumentNullException("container"); _container = container; } public object GetService(Type serviceType) { return _container.ResolveOptional(serviceType); } public IEnumerable<object> GetServices(Type serviceType) { var enumerableServiceType = typeof(IEnumerable<>).MakeGenericType(serviceType); var instance = _container.Resolve(enumerableServiceType); return (IEnumerable<object>)instance; } } The registration extensions are also straight forward. public static void ConfigureWebApi(this ContainerBuilder builder, HttpConfiguration configuration) { builder.RegisterInstance(configuration); builder.Register<IHttpControllerActivator>(c => new AutofacControllerActivator()) .SingleInstance(); builder.Register<IHttpControllerFactory>(c => new AutofacControllerFactory(c.Resolve<HttpConfiguration>(), c.Resolve<ILifetimeScope>())) .SingleInstance(); } Scanning for controllers is implemented similar to the MVC integration except we are looking for public static IRegistrationBuilder<object, ScanningActivatorData, DynamicRegistrationStyle> RegisterApiControllers(this ContainerBuilder builder, params Assembly[] controllerAssemblies) { return builder.RegisterAssemblyTypes(controllerAssemblies) .Where(t => typeof(IHttpController).IsAssignableFrom(t) && t.Name.EndsWith("Controller")); } Finally, and once again similar to the MVC public static IRegistrationBuilder<TLimit, TActivatorData, TStyle> InstancePerApiRequest<TLimit, TActivatorData, TStyle>( this IRegistrationBuilder<TLimit, TActivatorData, TStyle> registration) { if (registration == null) throw new ArgumentNullException("registration"); return registration.InstancePerMatchingLifetimeScope(AutofacControllerFactory.ApiRequestTag); } That pretty much covers it. Please download the package and report any bugs on the issue tracker or if you need help post your questions on Stack Overflow using the “autofac” tag. It will be interesting to see how much things change between this Beta and the final RTW. Happing API building! |
请发表评论