iT邦幫忙

DAY 20
3

使用Asp.Net MVC打造Web Api系列 第 20

使用Asp.Net MVC打造Web Api (20) - 整合AOP功能

在我們前面的文章中,經常利用客製化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來安裝它

  1. 使用Nuget安裝Autofac.Extra: Castle Dynamic Proxy Support

  2. 在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);
            }
        }    
    
  3. 修改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();                      
            }
        }
    
  4. 修改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();            
            }
        }
    
  5. 在ProductService上加上Interceptor,讓Autofac知道這個Class需要AOP

        [Intercept(typeof(LogInterceptor))]
        public class ProductService : IProductService
        {
        }        
    
  6. 執行查詢,可以看到成功的產生Log

※透過Mobule指定Interceptor
除了透過在Class或Interface上面加上Attribute來讓Autofac會注入Interceptor之外,也可以透過在Module中設定Class所需要的Interceptor,這麼一來我們可以讓Interceptor變成不會綁死在某一隻Class或Interface上面,而是更能夠依照實際需求很有彈性的在需要的時候才指定Interceptor。

  1. 將原本ProductService的Interceptor移除

        public class ProductService : IProductService
        {
        }        
    
  2. 在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));
        }
    
  3. 重新執行查詢,Log檔也有正確的產生!

※使用AOP進行權限檢查
雖然我們已經在Controller做了一層防護,但有時候我們希望可以限定某個Class能夠確認是登入的使用者才能呼叫,那我們也可以透過AOP的方式來實作

  1. 在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();
            }
        }
    
  2. 修改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>();
            }
        }
    
  3. 修改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));
            }
        }
    
  4. 設定中斷點檢測,有產生Log也有檢查權限!

※本日小結
透過AOP的模式,我們可以將程式職責區分得更加清楚,並且更易於重複使用。再加上DI Framework讓AOP的Interceptor不需要把邏輯跟其它邏輯綁死,而是可以透過修改替換Module來決定現在需要哪些Interceptor,這些都是讓程式碼具有更高彈性的地方!關於今天的內容,歡迎大家一起討論^_^


上一篇
使用Asp.Net MVC打造Web Api (19) - 使用Glimpse調校網站
下一篇
使用Asp.Net MVC打造Web Api (21) - 發行網站到Azure
系列文
使用Asp.Net MVC打造Web Api30

1 則留言

0

我要留言

立即登入留言