ASP.Net Web API依赖注入

前言

近期为了提高团队的每日报告和周报的效率和简化管理,开发了一个局域网内的小型活动日志记录网站。

公司是微软系的死忠,那么后台自然用ASP.Net了,这里我只需要一个API服务,所以使用了Web API2,数据库用现成的SQL Server,前台用自己喜欢的React做SPA应用,账户验证因为是局域网,直接用windows验证,非常方便,这样4天基本完成需求和测试。

代码不涉及公司业务,可查阅Github-DailyUpdates

问题

0x01

在写多个API Controllers的时候,一个明显的问题就是,这些API都是需要验证的,那么每个API Controller文件都需要在实例化的时候,获取一遍身份信息,这样造成了代码赘余。

首先的解决方案就是使用依赖注入。

0x02

由于API的每次请求可能是不同的用户,因此每次的验证信息是不一致的,如果使用依赖注入,必须为每次请求提供不同的Container,避免注入的信息错误。

代码

C#提供了IDependencyResolver的依赖注入解决器的接口,需要我们自身去实现。这里直接用Unity库提供Container服务。

0x01 WebAPIResolver.cs
using Microsoft.Practices.Unity;  
using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Web;  
using System.Web.Http;  
using System.Web.Http.Dependencies;

namespace XXX.Unity  
{
    public sealed class WebApiDependencyResolver : System.Web.Http.Dependencies.IDependencyResolver
    {
        private SharedDependencyScope sharedScope;

        public WebApiDependencyResolver()
        {
            this.sharedScope = new SharedDependencyScope(this);
        }

        public IDependencyScope BeginScope()
        {
            return this.sharedScope;
        }

        public void Dispose()
        {
            this.sharedScope.Dispose();
        }

        public object GetService(Type serviceType)
        {
            var container = UnityContainerProvider.Current;
            if (container == null)
                return null;

            // Do not try to create interfaces or abstract types that are not registered
            if ((serviceType.IsAbstract || serviceType.IsInterface) && !container.IsRegistered(serviceType))
                return null;

            try
            {
                return container.Resolve(serviceType);
            }
            catch (ResolutionFailedException)
            {
                return null;
            }
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            try
            {
                return UnityContainerProvider.Current.ResolveAll(serviceType);
            }
            catch (ResolutionFailedException)
            {
                return null;
            }
        }

        private sealed class SharedDependencyScope : System.Web.Http.Dependencies.IDependencyScope
        {
            WebApiDependencyResolver _parent;

            public SharedDependencyScope(WebApiDependencyResolver parent)
            {
                _parent = parent;
            }

            public object GetService(Type serviceType)
            {
                return _parent.GetService(serviceType);
            }

            public IEnumerable<object> GetServices(Type serviceType)
            {
                return _parent.GetServices(serviceType);
            }

            public void Dispose()
            {
                // NO-OP, as the container is shared.
            }
        }
    }
}
0x02 UnityContainerProvider.cs

提供Container

using Microsoft.Practices.Unity;  
using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Reflection;  
using System.Web;

namespace XXX.Unity  
{
    public static class UnityContainerProvider
    {
        private static IUnityContainer _rootContainer;

        internal static void Configure()
        {
            if (_rootContainer != null)
                return;

            _rootContainer = new UnityContainer();


            _rootContainer.RegisterTypes(SelectTypesToRegister(),
                getFromTypes: SelectInterfacesToRegister,
                getName: SelectNameToRegister,
                getLifetimeManager: WithLifetime.None);

            // Register instances to be used when resolving constructor parameter dependencies
            _rootContainer.RegisterInstance(new DomainName()); // 因为DomainName这个类构造参数是可选的,依赖注入不知道该如何解决,我就直接帮他声明好了

            ConfigureWebApi();
        }

        private static IEnumerable<Assembly> SelectAssembliesToRegister()
        {
            return from assembly in System.Web.Compilation.BuildManager.GetReferencedAssemblies().Cast<Assembly>()
                   where assembly.FullName.StartsWith("Aspen.DailyUpdates")
                   select assembly;
        }

        private static IEnumerable<Type> SelectTypesToRegister()
        {
            return from type in AllClasses.FromAssemblies(SelectAssembliesToRegister(), skipOnError: true)
                   where IsPart(type)
                   select type;
        }

        private static IEnumerable<Type> SelectInterfacesToRegister(Type type)
        {
            var allInterfaces = WithMappings.FromAllInterfaces(type);
            return from intf in allInterfaces where IsInNamespace(intf, "xxx.Updates") select intf; // 这里是将所有命名空间以xxx.Updates开头的文件都获取到
        }

        private static string SelectNameToRegister(Type type)
        {
            var attributes = type.GetCustomAttributes(false);

            return WithName.Default(type);
        }

        private static bool IsPart(Type type)
        {
            if (type == null || type.IsAssignableFrom(typeof(Attribute)))
                return false;

            if (IsInNamespace(type, "Services") || IsInNamespace(type, "SingletonServices")) // 要做自动解决的依赖的命名空间以Services结尾的,这里将其声明为需要注册的类型
                return true;

            return false;
        }

        public static bool IsInNamespace(Type type, string namespaceFragment)
        {
            if (type == null)
                return false;

            if (string.IsNullOrEmpty(namespaceFragment))
                return false;

            var typeNamespace = type.Namespace;
            if (string.IsNullOrEmpty(typeNamespace))
                return false;

            return (typeNamespace.StartsWith(namespaceFragment + ".")
                || typeNamespace.EndsWith("." + namespaceFragment)
                || typeNamespace.Contains("." + namespaceFragment + "."));
        }

        static void ConfigureWebApi()
        {
            System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver = new WebApiDependencyResolver();
        }

        internal static IUnityContainer Current
        {
            get
            {
                return _rootContainer;
            }
        }
    }
}
0x03 UnityPerRequestHttpModule.cs

为每次HTTP请求添加中间件过滤,这里实现请求结束时释放生成的依赖注入对象

using Microsoft.Web.Infrastructure.DynamicModuleHelper;  
using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Web;

namespace XXX.Unity  
{
    public class UnityPerRequestHttpModule : IHttpModule
    {
        private static readonly object ModuleKey = new object();

        internal static object GetValue(object lifetimeManagerKey)
        {
            var dict = GetDictionary(HttpContext.Current);

            if (dict != null)
            {
                object obj = null;

                if (dict.TryGetValue(lifetimeManagerKey, out obj))
                {
                    return obj;
                }
            }

            return null;
        }

        internal static void SetValue(object lifetimeManagerKey, object value)
        {
            var dict = GetDictionary(HttpContext.Current);

            if (dict == null)
            {
                dict = new Dictionary<object, object>();

                HttpContext.Current.Items[ModuleKey] = dict;
            }

            dict[lifetimeManagerKey] = value;
        }

        public void Dispose()
        {
        }

        public void Init(HttpApplication application)
        {
            if (application == null)
                throw new ArgumentNullException("application");

            UnityContainerProvider.Configure();

            application.EndRequest += OnEndRequest;
        }

        private void OnEndRequest(object sender, EventArgs e)
        {
            var app = (HttpApplication)sender;

            var dict = GetDictionary(app.Context);

            if (dict != null)
            {
                foreach (var disposable in dict.Values.OfType<IDisposable>())
                {
                    disposable.Dispose();
                }
            }
        }

        private static Dictionary<object, object> GetDictionary(HttpContext context)
        {
            if (context == null)
                throw new ArgumentNullException("context");

            var dict = (Dictionary<object, object>)context.Items[ModuleKey];

            return dict;
        }

        // Internal method to prepare module and register
        internal static void Register()
        {
            DynamicModuleUtility.RegisterModule(typeof(Unity.UnityPerRequestHttpModule));
        }
    }
}
0x04 PerRequestLifetimeManager.cs

给UnityPerRequestHttpModule提供生命周期管理方法

using Microsoft.Practices.Unity;  
using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Web;

namespace XXX.Unity  
{
    public class PerRequestLifetimeManager : LifetimeManager
    {
        private readonly object lifetimeKey = new object();

        public override object GetValue()
        {
            return UnityPerRequestHttpModule.GetValue(this.lifetimeKey);
        }

        public override void SetValue(object newValue)
        {
            UnityPerRequestHttpModule.SetValue(this.lifetimeKey, newValue);
        }

        public override void RemoveValue()
        {
            var disposable = this.GetValue() as IDisposable;

            if (disposable != null)
            {
                disposable.Dispose();
            }

            UnityPerRequestHttpModule.SetValue(this.lifetimeKey, null);
        }
    }
}