通過動態Controller實現從WCF到Web API的遷移.

在《又見AOP之基於RealProxy實現WCF動態代理》這篇文章中,我和大家分享了關於使用動態代理來簡化WCF調用過程的相關內容,當時我試圖解決的問題是,項目中大量通過T4生成甚至手動編寫的“代理方法”。今天,我想和大家分享的是,如何通過動態的Controller來實現從WCF到Web API的遷移。爲什麼會有這個環節呢?因爲我們希望把一個老項目逐步遷移到.NET Core上面,在這個過程中首當其衝的就是WCF,它在項目中主要承擔着內部RPC的角色,因爲.NET Core目前尚未提供針對WCF服務端的支持,因此面對項目中成百上千的WCF接口,我們必須通過Web API重新“包裝”一次,區別於那些通過逐個API進行改造的方式,這裏我們通過Castle動態生成Controller來實現從WCF到Web API的遷移。

如何對類和接口進行組合

首先,我們來思考這樣一個問題,假設現在有一個類BaseClass、一個接口IBaseService及其實現類BaseService,我們有沒有什麼辦法,可以讓這個類和接口組合起來呢?聯繫面向對象編程的相關知識,我們應該可以想到最常見的兩種方式,即BaseService繼承BaseClass(或者反過來)、BaseClass實現IBaseService接口。考慮到語言本身是否支持多繼承的因素,第二種方式可能會更具有適用性。可如果這個問題,就僅僅到這種程度,我相信大家一定會感到失望,因爲這的確沒有什麼好說的。現在的問題是,假如BaseClass類、BaseService類都已經存在了,我們有沒有什麼思路,可以把它們組合到一個類中呢?這又和我們今天要討論的內容有什麼關係呢?

好了,不賣關子啦,下面隆重請出Castle中的Dynamic Proxy,我們曾經介紹過Castle中的動態代理,它可以爲指定的類和接口創建對應的代理類,除此以外,它提供了一種稱爲AdditionalInterfaces的接口,這個接口可以在某個代理對象上“組合”一個或者多個接口,換句話說,代理對象本身包含被代理對象的全部功能,同時又可以包含某個接口的全部功能,這樣就實現了一個類和一個接口的組合。爲什麼我們會需要這樣一個功能呢?因爲假如我們可以把一個ApiController類和指定的接口類如CalculatorService進行組合,在某種程度上,CalculatorService就變成了一個ApiController,這樣就實現了我們的目標的第一步,即動態生成一個ApiController。與此同時,它會包含我們現有的全部功能,爲了方便大家理解,我們從下面這個簡單的例子開始:

/// <summary>
 /// IEchoService定義
 /// </summary>
 public interface IEchoService {
     void Echo (string receiver);
 }

 /// <summary>
 /// IEchoServicee實現
 /// </summary>
 public class EchoService : IEchoService {
     public void Echo (string receiver) {
         Console.WriteLine ($"Hi,{receiver}");
     }
 }

 /// <summary>
 /// 空類EmptyClass
 /// </summary>
 public class EmptyClass { }

 public class EchoInterceptor : IInterceptor {
     private IEchoService _realObject;
     public EchoInterceptor (IEchoService realObject) {
         _realObject = realObject;
     }

     public void Intercept (IInvocation invocation) {
         invocation.Method.Invoke (_realObject, invocation.Arguments);
     }
 }
 
 var container = new WindsorContainer ();
 container.Register (
     Component.For<EchoService, IEchoService>(),
     Component.For (typeof (EchoInterceptor)).LifestyleTransient(),
     Component.For (typeof (EmptyClass)).Proxy.AdditionalInterfaces (typeof(IEchoService))
     .Interceptors (typeof (EchoInterceptor)).LifestyleTransient()
 );

 var emptyClass = container.Resolve<EmptyClass> ();
 var methodInfo = emptyClass.GetType().GetMethod ("Echo");
 methodInfo.Invoke (emptyClass, new object[] { "Dynamic WebApi" });

此時,我們會發現通過Castle動態生成的代理類,同時具備了類和接口的功能。

通過Castle實現類和接口的組合功能

重溫ASP.NET MVC原理

OK,通過第一個例子,我們已經達到了第一個目的。接下來,順着這個思路,我們不妨想象一下,如果把這個BaseClass換成BaseController會怎麼樣呢?因爲在一個OO的語言裏,一切都是Class,所以,Web開發中的Controller同樣不會脫離這個體系。不過,在這之前,我們需要複習下ASP.NET MVC的原理,爲什麼要說這個呢?因爲接下來的內容,都和它有重大的關聯,我們實際上是自己實現了ASP.NET MVC中幾個關鍵的環節,所以,在博主看來,這部分內容是非常重要的,這幾乎是這篇文章中實現的最細節部分,因爲第一個目標,說句實話,Castle幫我們簡化到了只有4行代碼。

一張圖瞭解MVC

 一張圖瞭解MVC

通常來講,當我們在MVC中接收到一個Url請求後,這個請求會被UrlRoutingModule攔截。此時,請求的上下文HttpContext會被封裝到HttpContextWrapper對象中。而根據當前請求的HttpContext,則可以提取出符合當前Url的路由對象RouteData,它會被進一步封裝爲RequestContext對象。接下來,從RequestContext對象中獲取RouteData,它對應一個RouteHandler,是IHttpHandler的一個實現類。對於MVC而言,則對應MvcHandler。通過調用MvcHandler,對應的Controller會被反射激活,進而調用具體的Action。以上就是整個MVC請求的過程描述,可以看出最關鍵的兩個組件是UrlRoutingModule和MvcHandler,前者的作用是解析Controller和Action名稱,後者的作用則是根據Controller名稱去反射調用具體的Action,大家可以通過上面的圖來理解這個過程。

在這裏,其實我們只需要關注第二部分:-D,即MvcHandler,因爲我們會在默認路由的基礎上,增加一個自定義路由來“標記”這些動態的Controller,所以,我們集中關注MvcHandler這部分即可,雖然這裏提到它會根據Controller的名稱來反射激活相應的Controller實例、調用具體的Action,但這僅僅是宏觀上的一種認識。我們來看一下,它具體是怎麼反射出Controller以及調用Action的。

IControllerFactory接口

第一個關鍵的組件是IControllerFactory接口,顧名思義,它是作用是創建Controller,可實際上,這個組件除了完成創建Controller的工作以外,還會涉及到Controller類型的解析、Controller實例激活、Controller實例釋放、會話狀態行爲選項獲取等多個功能。這裏有一個激活的過程,我們可以將其理解爲Controller的初始化,因爲Controller在使用的過程中往往會通過IoC容器來注入相關服務,所以,你可以理解爲在構造Controller的過程中,我們需要一個IoC容器來完成依賴注入相關的事情,微軟默認提供了一個DefaultControllerFactory的實現,它內部是通過IHttpControllerActivator接口來完成依賴注入的,而這恰恰是我們要關注的第二個組件。

IHttpControllerActivator接口

老實說,通過自定義IHttpControllerActivator的方式實現依賴注入的方式並不常見,因爲更一般的情況是,大家在Global.asax裏初始化像Unity、Autofac等等類似的容器,然後在Controller裏通過容器去Resolve一個服務出來,對於IHttpControllerActivator接口而言,它只有一個Create()方法,在這篇文章中,我們是通過Castle這個容器來實現依賴注入的,所以,你大概可以想象出它的過程,首先把所有動態生成的Controller全部注入到Ioc容器中,然後再根據傳入的類型獲取對應Controller的實例。在本文中,我們重寫了默認的HttpControllerActivator,這裏主要指Create()方法,因爲我們希望實現的效果是,動態的Controller全部從Castle容器中獲取,而靜態的Controller依然按照微軟的設計來獲取。

IHttpControllerSelector接口

OK,現在有了Controller以後,我們怎麼讓MVC路由到正確的Controller上面去呢?這時候,必然需要有人來解析路由啊,這就是第三個組件——IHttpControllerSelector。這又是一個顧名思義的接口,充分說明命名是件多麼重要的事情。在這裏我們重寫了SelectController()方法,當路由信息中存在ServiceName和ActionName時,就去檢查容器中是否存在對應的Controller,如果存在就返回一個HttpControllerDescriptor,這是一個用以描述控制器上下文信息的類型。反之,會調用默認的base.SelectController()方法,這樣做還是爲了兼容微軟原來的設計,因爲我們不希望在引入動態Controller後,導致普通的Controller無法正常工作。

IhttpActionSelector接口

同理,我們還需要告訴MvcHandler,它應該調用哪個方法,這時候我們需要IHttpActionSelector,因爲從路由信息中我們可以提取到ActionName參數,因此,通過通過typeof(Controller).GetMethod(ActionName),就可以獲得對應ActionName對應的方法,熟悉反射的朋友應該都知道,它會返回MethodInfo這個類型,實際上IHttpActionSelector所做的事情,就是把MethodInfo傳給MvcHandler,因爲此時只要通過反射調用這個方法即可,Controller的實例在上一步就創建好了,而調用方法所需要的參數,則被存儲在當前請求的上下文HttpContext裏面,至此萬事具備!我們要做的,就是順着這些思路去實現以上組件。

關鍵組件的自定義實現

OK,下面我們來看看如何針對這些組件, 來分別實現我們的自定義組件,實現這些自定義組件並對MVC中的默認組件進行替換,這就是我們這篇文章中實現動態Controller的一個基本原理。

DynamicControllerActivator

DynamicControllerActivator 實現了IHttpControllerActivator接口,這裏我們通過單例模式獲得了DynamicHttpControllerManager對象的一個實例,其內部封裝了Castle的容器接口IWindsorContainer,所以,在這裏我們直接通過controllerType從容器中Resolve對應的Controller即可,而默認情況下,所有的Controller都實現了IHttpController接口,所以,這一步我們需要做一個顯示的類型轉換,後面我們會通過它替換微軟默認的實現,這樣,當一個請求被髮送過來的時候,我們實際上是從這個自定義容器中獲取對應Controller的實例。

public class DynamicHttpControllerActivtor : IHttpControllerActivator
{
 public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
 {
  return (IHttpController)DynamicHttpControllerManager.GetInstance().Resolve(controllerType);
 }
}

DynamicHttpControllerSelector

如果說DynamicControllerActivator 是真正實現控制器的**"激活"部分,那麼在此之前,我們需要實現控制器的"篩選"部分,換言之,一個請求被髮送過來的時候,到底應該用哪一個Controller去處理這個請求呢?所以,我們來看看DynamicHttpControllerSelector這個組件是如何實現的,這裏我們重寫SelectController()這個方法來完成控制器的"篩選"部分的工作。可以注意到,我們首先會判斷路由信息中是否存在ServiceName和ActionName這兩個值,因爲對於動態的Controller,我們默認使用的路由模板是services/{ServiceName}/{ActionName}**,這裏使用services前綴是爲了區別於微軟默認的api前綴,當然,強迫症的你同樣可以使用相同的前綴。

接下來,我們會判斷ServiceName是否在容器中註冊過,如果註冊了就從容器裏取出對應的服務,並構造DynamicHttpControllerDescriptor對象,否則調用父類方法按微軟默認實現去處理。那麼,這個DynamicHttpControllerDescriptor對象,又是何方神聖呢?從名稱上我們大概可以瞭解,這應該是一個對控制器相關信息進行描述的類型,它繼承了HttpControllerDescriptor這個父類,目前沒有任何擴展性的實現。對於DynamicHttpControllerDescriptor,它最重要的參數是構造函數中第三個參數,即 controllerType,因爲DynamicControllerActivator 實際上就是根據它來工作的。

public class DynamicHttpControllerSelector: DefaultHttpControllerSelector
{
    private HttpConfiguration _configuration;
    /// <summary>
    /// 構造函數
    /// </summary>
    /// <param name="configuration"></param>
    public DynamicHttpControllerSelector(HttpConfiguration configuration) :
        base(configuration)
    {
        _configuration = configuration;
    }

    public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
    {
        var routeData = request.GetRouteData().Values;
        if (routeData.ContainsKey("ServiceName") && routeData.ContainsKey("ActionName"))
        {
            var serviceName = routeData["ServiceName"].ToString();
            var actionName = routeData["ActionName"].ToString();

            if (DynamicHttpControllerManager.GetInstance().ContainsService(serviceName))
            {
                var controllerInfo = DynamicHttpControllerManager.GetInstance().GetControllerInfo(serviceName);
                var controller = DynamicHttpControllerManager.GetInstance().Resolve(serviceName);
                if (controller == null)
                    return base.SelectController(request);

                var controllerDescriptor = new DynamicHttpControllerDescriptor(_configuration, serviceName, controllerInfo.ControllerType);
                controllerDescriptor.Properties["ServiceName"] = serviceName;
                controllerDescriptor.Properties["ActionName"] = actionName;
                controllerDescriptor.Properties["IsDynamicController"] = true;
                controllerDescriptor.Properties["ServiceType"] = controllerInfo.ServiceType;
                controllerDescriptor.Properties["ControllerType"] = controller.GetType();
                return controllerDescriptor;
            }
                 
        }

        return base.SelectController(request);
    }
}

DynamicHttpActionSelector

既然通過路由中的ServiceName可以對Controller進行**“篩選”,那麼,我們自然可以通過路由中的ActionName來對Action進行篩選"**。Action是控制器中的概念,對應一般的接口或者類,我們稱之爲方法,因此,DynamicHttpActionSelector在這裏實現針對Action的篩選,它繼承ApiControllerActionSelector類並重寫了SelectAction()方法,下面給出具體的實現:

public class DynamicHttpActionSelector : ApiControllerActionSelector
{
    public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
    {
        var isDynamicController = controllerContext.ControllerDescriptor.Properties.ContainsKey("IsDynamicController");
        if (isDynamicController)
        {
            var controllerType = new object();
            if (controllerContext.ControllerDescriptor.Properties.TryGetValue("ControllerType", out controllerType))
            {
                var actionName = controllerContext.ControllerDescriptor.Properties["ActionName"].ToString();
                var methodInfo = ((Type)controllerType).GetMethod(actionName);
                if (methodInfo == null)
                    return base.SelectAction(controllerContext);

                return new DynamicHttpActionDescriptor(controllerContext.ControllerDescriptor, methodInfo);
            }
        }

        return base.SelectAction(controllerContext);
    }
}

和篩選Controller的過程類似,首先我們會判斷這是不是一個動態的Controller,請注意在DynamicHttpControllerSelector中,我們爲ControllerDescriptor添加了大量的Properties,這些Properties可以在這裏繼續使用。顯然,我們只需要關注動態的Controller即可,如果可以通過ActionName找到對應的MethodInfo,那就說明當前Controller中存在指定的Action,反之則需要調用父類方法按微軟默認的實現去處理。其實,這裏不好的一點就是,我們的通過反射獲取MethodInfo時,需要傳入ActionName即方法的名字,而方法的名字是區分大小寫的,這會導致我們的URL必須區分大小寫,這不太符合RESTful API風格。同樣額,這裏定義了一個類型DynamicHttpActionDescriptor,它繼承自ReflectedHttpActionDescriptor,它需要傳入MethodInfo,這樣MVC就知道應該去調用控制器的哪一個方法了。

容器註冊及服務替換

在我們實際的業務系統中,存在着大量的WCF接口,它們都是通過ServiceHost這種方式來託管,然後在調用端通過代理類的方式來相互調用,因此把WCF遷移到Web API上,被拋棄的僅僅是這些.svc的文件,而這些WCF接口依然可以繼續使用。在之前的文章中,我們用Castle的Dynamic Proxy來代替各種手寫的代理類,在這篇文章中我們繼續沿用ICalculator這個接口示例,它包含着最爲簡單加減乘除四個方法,那麼,我們應該怎樣把這個接口變成一個Web API呢?這就是所謂的容器註冊和服務替換啦!首先我們來註冊ICalculator這個服務,它的代碼只有一行:

DynamicHttpControllerManager.GetInstance().RegisterType<CalculatorService, ICalculator>();

這是一個典型的依賴注入,其中CalculatorService是ICalculator的實現類,它到底做了什麼呢?我們來看看本質:

public void RegisterType<TImplement, TInterface>(string serviceName = "")
{
    if (string.IsNullOrEmpty(serviceName))
        serviceName = GetServiceName<TImplement>();

    _container.Register(
        Component.For(typeof(TImplement), typeof(TInterface)),
        Component.For<DynamicApiInterceptor<TInterface>>().LifestyleTransient(),
        Component.For<BaseController<TInterface>>().Proxy.AdditionalInterfaces(typeof(TInterface))
            .Interceptors<DynamicApiInterceptor<TInterface>>().LifestyleTransient()
            .Named(serviceName)
        );

    _controllerInfoList.Add(serviceName, new DynamicControllerInfo(typeof(TInterface)));
}

有沒有覺得這段代碼非常熟悉,實際上這就是我們這篇文章最開始提出的問題:怎麼樣對一個類和接口進行租戶。一開始我們是用一個最普通的類、一個最普通的接口來演示這種可能性,而這裏我們不過將其推廣到一個特殊的場景,如果這個類是一個繼承了ApiController的BaseController呢?這是一個由一般到特殊的過程。如你所見,內部的確使用了Castle的容器來處理依賴注入,而_controllerInfoList則存儲了Controller相關的信息,方便我們在整個流程中隨時獲取這些信息。完成容器註冊以後,我們就可以着手對MVC中的默認組件進行替換工作啦,我個人建議,替換工作放在整個Global.asax的最前面:

var configuration = GlobalConfiguration.Configuration;
var dynamicControllerSelector = new DynamicHttpControllerSelector(configuration);
var dynamicHttpControllerActivtor = new DynamicHttpControllerActivtor();
var dynamicActionSelector = new DynamicHttpActionSelector();
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector), dynamicControllerSelector);
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpActionSelector), dynamicActionSelector);
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), dynamicHttpControllerActivtor);

假設現在我希望調用ICalcultor接口中的Add方法,理論上它的URL應該是http://localhost/Service/Calculator/Add,因爲截至到目前爲止,所有的接口默認都是通過Get來訪問的,下面是整個流程第一次跑通時的截圖:

遷移後的ICalculator接口

接口遷移後的二三事

現在,我們完成了ICalculator接口的改造,它從一個WCF服務變成了一個Web API,而在這個過程中,我們發現一點點問題。首先,Web API中的URL是不區分大小寫的,而我們這裏的ServiceName、ActionName都是嚴格區分大小寫的。其次,接口方法中的out、ref、params等關鍵字不適用於Web API語境,需要進一步對接口進行改造。再者,Web API需要區分GET、POST、PUT、DELETE等動詞,返回值需要統一調整爲JSON格式。最後,完成改造的動態API需要通過RestSharp或者HttpClient等HTTP客戶端來調用,以替換原有的WCF代理方法。這裏簡單對後面這兩個問題做下說明,因爲前兩個問題,都是歷史遺留問題啦,哈哈?。

HTTP動詞支持

爲了讓接口支持不同的HTTP動詞,我們需要對整個設計進行進一步優化。爲什麼我會把這件事情看得如此重要呢?因爲在我看來,RESTful風格的API大概會有這樣幾種級別,第一種級別指僅僅使用了HTTP協議來設計API,第二種級別是在API設計中引入資源的概念,第三種級別是合理地使用HTTP動詞如GET、POST、PUT等,第四種級別是使用HATEOSA來返回用戶接下來可能的意圖。可惜在實際的應用種,能做到第二種級別的都相當不容易啦。比如某接口不支持GET操作,原因是它需要附加token在Body中,因此在改造接口的過程中,哪怕參數是最簡單的值類型,它還是必須要用POST方式來請求。可其實這種問題,如果把token附加在HTTP首部中,或者乾脆就使用標準的Authorizatin字段完全就能解決啊。爲了讓這個方案更完美一點,我們對DynamicHttpActionDescriptor進行改造,重寫它的SupportedHttpMethods屬性:

var isDynamicController = controllerDescriptor.Properties.ContainsKey("IsDynamicController");
if (isDynamicController)
{
    var serviceType = controllerDescriptor.Properties["ServiceType"];
    var httpVerbAttributes = ((Type)serviceType).GetMethod(methodInfo.Name).GetCustomAttributes<Attribute>()
        .Where(t => typeof(IActionHttpMethodProvider).IsAssignableFrom(t.GetType()))
        .ToList();

    if (httpVerbAttributes.Any())
    {
        //根據路由來獲取Http動詞
        if (httpVerbAttributes.Count > 1)
            throw new Exception($"Multiple http verb matched in method {methodInfo.Name} of {((Type)serviceType).Name}");

             _httpVerbs = GetHttpVerbByRoute(httpVerbAttributes);
        }
        else
        {
            //根據方法名稱獲取Http動詞
            _httpVerbs = GetHttpVerbByMethod(methodInfo);
        }
    }
}

其原理說起來並不複雜,檢查方法上是否有HTTPGet、HttpPost、HttpPut等標籤,如果存在,則添加相應的HTTP動詞到**_httpVerbs集合中;如果不存在,則根據方法的名字來構建相應的HTTP動詞。譬如以Add、Create等開頭的方法對應POST請求,以Get開頭的方法對應GET請求,以Update開頭的方法對應PUT請求,以Delete開頭的方法對應DELETE請求等。最終,我們只需要把_httpVerbs**作爲SupportedHttpMethods屬性的返回值即可。

接口返回值優化

通常在編寫控制器的時候,我們會使用JSON作爲接口的返回值,這是因爲JSON在信息冗餘度上相比XML更低,而且JSON和JavaScript有着密不可分的聯繫,所以使用JSON作爲返回值會流行起來一點都不奇怪。我們知道,WCF是可以實現Web Service這種所謂的SOAP架構的,而WebService本質上是使用XML進行通信的HTTP,在調用WCF接口的時候,接口的參數、返回值都會被序列化爲XML。平時我們手寫Controller的時候,通常是在Controller層調用一層薄薄的Service層,然後對結果進行封裝,使其成爲對前端更友好的數據類型,可當我們調用動態的Controller時,其接口的返回值是在接口中定義好的,我們不可能去修改已經在使用中的Service定義。

雖然微軟的Web API中可以自動對返回值進行序列化,參考最經典的ValuesController,它是微軟對RESTful風格的一種標準實現,具體表現爲Get()、Post()、Put()、Delete()四個方法,分別對應GTE、POST()、PUT()、DELETE(四個HTTP動詞,這就是所謂的約定大於配置,並且這些方法的返回值都不是ActionResult或者IHttpActionResult,但整個框架依然可以幫我們將其序列化爲JSON,這一切是爲什麼呢?其實,我們只需要重寫DynamicHttpActionDescriptor的ReturnType屬性,同時重寫DynamicHttpActionDescriptor的ExecuteAsync()方法就可以達到這一目的:

public override Type ReturnType
{
    get
    {
        return typeof(DynamicApiResult);
    }
}

public override Task<object> ExecuteAsync(HttpControllerContext controllerContext, IDictionary<string, object> arguments, CancellationToken cancellationToken)
{
    return base.ExecuteAsync(controllerContext, arguments, cancellationToken)
        .ContinueWith(task =>
        {
            try
            {
                if (task.Result == null)
                {
                    return new DynamicApiResult() { Flag = true };
                }

                if (task.Result is DynamicApiResult)
                {
                    return task.Result;
                }

                return new DynamicApiResult() { Flag = true, Result = task.Result };
            }
            catch (AggregateException ex)
            {
                throw ex;
            }
        });
}

從代碼中大家大致可以猜出DynamicApiResult的結構了,它包含三個屬性:Flag、Msg、Result。這是一個最常見的Web API的返回值封裝,即通過Flag判斷方法是否調用成功,通過Msg來返回異常信息,通過Result來返回具體的返回值。最近對接某公司的API接口的時候,發現一個非常奇葩的現象,一般沒有返回值可以返回null或者空字符串,可這家公司居然返回的是**”無數據"**,你以爲這是放在Msg裏的嗎?不,人家是放在Result裏的。對此,我只能說,互聯網發展到2019年了,那些年野蠻生長留下的坑卻還一直都在。好了,現在我們來看看接口調用的結果,喏,這次是不是感覺順眼多啦!

優化後的ICalculator接口返回值

POCOController

其實,這篇文章寫到這裏就已經結束啦,因爲對於一般的ASP.NET項目,這篇文章裏所分享這些內容,基本上可以實現我們最初的目標,即把老系統中的WCF接口遷移到Web API上,從長遠的角度來看,這是爲了後續遷移到.NET Core上做準備,其實不單單是WCF,任何的接口、服務都可以順着這種思路去做擴展,手寫Controller雖然是最容易上手的實踐方式,可隨着業務的不斷推進,無一例外地出現接口爆炸的現象,在沒有註冊中心的情況下,業務系統間互相調對方的Web API簡直叫一個混亂,你能想象一家公司裏的不同業務系統,居然沒有通用的網關去做接口的授權嗎?反正我最近是見識到了某友的混亂。這篇文章中的思路,其實是參考了Abp這個框架中的DynamicApiController這一功能,可我們大多數人都沒怎麼好好用過這項技術,.NET Core就來了,Abp官方着手開發的Abp vNext就是基於.NET Core的下一代Abp框架,不知道屆時會不會有這個功能。

既然說到了,NET Core,那麼就不可避免地要說到.NET Core裏的POCOController。因爲ASP.NET與ASP.NET Core的機制完全不同,所以,我們在這篇文章裏的實現是無法直接用到ASP,NET Core裏的,這聽起來有點遺憾是不是,就在我寫這篇博客的前幾天,我看到有人把Abp的DynamicApiController移植到了.NET Core下面,還是熟悉的味道,但內部的原理已然大爲不同。具體來講, .NET Core下的POCOController特性會讓這一切更簡單。所謂POCOController,就是指任意一個類都可以是Controller。我們都知道在ASP.NET下,要寫一個Web API必須繼承ApiController,就是說這個類必須實現了IHttpController接口,就是因爲有這個限制,所以,我們不得不通過Castle來動態生成一個Controller,既然現在ASP.NET Core裏可以打破這一限制,那麼實現起來自然會非常簡單。限於這篇文章的篇幅(截至到這裏已經將近6000餘字),我將在下一篇文章中和大家分享這一特性的相關內容。

本文小結

在傳統的ASP.NET項目向ASP.NET Core遷移的過程中,我們遇到的第一個阻力就是作爲內部RPC使用的WCF。因此,收到上一篇文章基於Castle動態代理這一思路的影響,參考Abp框架中的DynamicApiController功能,我們分享了一種可以爲任意接口動態生成Controller的思路,其核心原理是通過Castle中的AdditionalInterfaces功能,將指定接口和ApiController進行組合,使得一個普通的接口可以像Controller一樣被調用。在這個過程中,我們回顧了ASP.NET MVC的基本原理,瞭解了MVC是如何根據路由篩選Controller、激活Controller和篩選Action,在此基礎上,我們對微軟的MVC進行了一次Hack,使用我們自定義的組件替換了微軟的默認實現,從而可以讓原來託管在ServiceHost上的接口,通過Web API來訪問和調用。當然,這篇文章裏沒有實現自定義的路由、過濾器的支持,所謂拋磚引玉,Abp的代碼本身在Github上就可以找到,大家如何對此感興趣的話,可以做進一步的研究和擴展。我們實現了服務端的切換,這意味着在客戶端同樣需要一次切換,預知後事如何,請大家關注我的下一篇博客,以上就是我這篇博客的全部內容了,謝謝大家!

參考文章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章