在我們前面的文章中,經常利用客製化ActionFilter來撰寫一些通用的邏輯(例如:Log、Authorize等),並且可以很輕鬆的掛載到函式上面來套用,這樣的做法可以讓function中只專注在自己本分的工作,而通用的工作就交給ActionFilter來實現,這樣的做法其實就叫做AOP(Aspect-Oriented Programming),而Asp.Net MVC迷人處之一也是內建了AOP,讓我們可以遵循SoC(Seperated of Concern)的原則來進行開發,而今天要介紹的是如何將這種做法延伸帶到UI層之外,讓他們同樣可以享受到AOP的好處。
大家可以從Github ApiSample - Tag Day18開始今天的練習
※AOP實現原理
大家還記得我們在開始開發Api的時候,就使用了DI Framework來做為我們Api的核心組成架構嗎?透過DI Framework可以讓我們程式碼的耦合性降低,甚至可以在執行時期改變介面(Interface)所對應實現的個體(Instance)
而Autofac除了這個好處之外,他還可以用Proxy Pattern的方式在對介面注入實體時,動態產生一個Proxy Class,讓我們可以輕鬆嵌入AOP的程式碼!
※實作AOP,替Service加上Log
若要讓Autofac支援AOP,我們必須另外安裝一個Library,來讓Autofac具有AOP的功能,我們使用Nuget來安裝它
使用Nuget安裝Autofac.Extra: Castle Dynamic Proxy Support
在Extensions專案實作LogInterceptor,在執行function的前後加上Log
public class LogInterceptor : IInterceptor
{
public ILogger Logger { get; set; }
public LogInterceptor(ILogger logger)
{
this.Logger = logger;
}
public void Intercept(IInvocation invocation)
{
this.Logger.Debug("呼叫方法:{0}, 參數:{1}... ",
invocation.Method.Name,
string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray()));
invocation.Proceed();
this.Logger.Debug("執行結果:{0}.", invocation.ReturnValue);
}
}
修改BL的ServiceModule,讓Class支援AOP
public class ServiceModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
var service = Assembly.Load("ApiSample.BL.Services");
builder.RegisterAssemblyTypes(service)
.AsImplementedInterfaces()
.EnableInterfaceInterceptors();
}
}
修改BL的ExtensionModule,註冊Interceptor
public class ExtensionModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
var mappings = Assembly.Load("ApiSample.Utility.Extensions");
builder.RegisterAssemblyTypes(mappings)
.AsImplementedInterfaces();
builder.RegisterAssemblyTypes(mappings)
.Where(i => i.Name.EndsWith("Interceptor"))
.AsSelf();
}
}
在ProductService上加上Interceptor,讓Autofac知道這個Class需要AOP
[Intercept(typeof(LogInterceptor))]
public class ProductService : IProductService
{
}
執行查詢,可以看到成功的產生Log
※透過Mobule指定Interceptor
除了透過在Class或Interface上面加上Attribute來讓Autofac會注入Interceptor之外,也可以透過在Module中設定Class所需要的Interceptor,這麼一來我們可以讓Interceptor變成不會綁死在某一隻Class或Interface上面,而是更能夠依照實際需求很有彈性的在需要的時候才指定Interceptor。
將原本ProductService的Interceptor移除
public class ProductService : IProductService
{
}
在Service Module中直接指定ProductService需要LogInterceptor
protected override void Load(ContainerBuilder builder)
{
var service = Assembly.Load("ApiSample.BL.Services");
builder.RegisterAssemblyTypes(service)
.AsImplementedInterfaces()
.EnableInterfaceInterceptors();
builder.RegisterType<ProductService>()
.As<IProductService>()
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(LogInterceptor));
}
重新執行查詢,Log檔也有正確的產生!
※使用AOP進行權限檢查
雖然我們已經在Controller做了一層防護,但有時候我們希望可以限定某個Class能夠確認是登入的使用者才能呼叫,那我們也可以透過AOP的方式來實作
在Utility的Extensions建立AuthInterceptor,來檢查執行方法的使用者是否擁有權限
public class AuthInterceptor : IInterceptor
{
private string role;
public AuthInterceptor(string role)
{
if (string.IsNullOrWhiteSpace(role))
{
throw new ArgumentNullException("role");
}
this.role = role;
}
public void Intercept(IInvocation invocation)
{
if (!Thread.CurrentPrincipal.Identity.IsAuthenticated ||
!Thread.CurrentPrincipal.IsInRole(this.role))
{
throw new UnauthorizedAccessException();
}
invocation.Proceed();
}
}
修改BL的ExtensionsModule,增加AuthInterceptor的設定
public class ExtensionModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
var mappings = Assembly.Load("ApiSample.Utility.Extensions");
builder.RegisterAssemblyTypes(mappings)
.AsImplementedInterfaces();
builder.RegisterAssemblyTypes(mappings)
.Where(i => i.Name.EndsWith("Interceptor"))
.AsSelf();
builder.Register(i => new AuthInterceptor("Administrator"))
.As<AuthInterceptor>();
}
}
修改BL的ServiceModule,增加AuthInterceptor
public class ServiceModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
var service = Assembly.Load("ApiSample.BL.Services");
builder.RegisterAssemblyTypes(service)
.AsImplementedInterfaces()
.EnableInterfaceInterceptors();
builder.RegisterType<ProductService>()
.As<IProductService>()
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(LogInterceptor), typeof(AuthInterceptor));
}
}
設定中斷點檢測,有產生Log也有檢查權限!
※本日小結
透過AOP的模式,我們可以將程式職責區分得更加清楚,並且更易於重複使用。再加上DI Framework讓AOP的Interceptor不需要把邏輯跟其它邏輯綁死,而是可以透過修改替換Module來決定現在需要哪些Interceptor,這些都是讓程式碼具有更高彈性的地方!關於今天的內容,歡迎大家一起討論^_^