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);
}
}
}
- 原文作者:Touchumind
- 原文链接:https://blog.fedepot.com/post/aspnet-dependency-injection/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。