【复杂系统迁移 .NET Core平台系列】之界面层
源寶導(dǎo)讀:微軟跨平臺(tái)技術(shù)框架—.NET Core已經(jīng)日趨成熟,已經(jīng)具備了支撐大型系統(tǒng)穩(wěn)定運(yùn)行的條件。本文將介紹明源云ERP平臺(tái)從.NET Framework向.NET Core遷移過(guò)程中的實(shí)踐經(jīng)驗(yàn)。
一、背景
? ? 隨著ERP的產(chǎn)品線越來(lái)越多,業(yè)務(wù)關(guān)聯(lián)也日益復(fù)雜,應(yīng)用間依賴關(guān)系也變得錯(cuò)綜復(fù)雜,單體架構(gòu)的弱點(diǎn)日趨明顯。19年初,由于平臺(tái)底層支持了分應(yīng)用部署模式,將ERP從應(yīng)用子系統(tǒng)層面進(jìn)行了切割分離,邁出了從單體架構(gòu)向微服務(wù)架構(gòu)轉(zhuǎn)型的堅(jiān)實(shí)一步。不久的將來(lái),ERP會(huì)進(jìn)一步將各業(yè)務(wù)拆分成眾多的微服務(wù),而微服務(wù)勢(shì)必需要進(jìn)行容器化部署和運(yùn)行管理,這就要求ERP技術(shù)底層必須支持跨平臺(tái),所以將現(xiàn)有ERP系統(tǒng)從.NET Framework遷移到 .NET Core平臺(tái)勢(shì)在必行。
? ? 上一篇《【復(fù)雜系統(tǒng)遷移 .NET Core平臺(tái)系列】之遷移項(xiàng)目工程》的文章中,我們分享了在改造.NET Core工程和類庫(kù)過(guò)程中的經(jīng)驗(yàn),本文我們將分享在界面層改造過(guò)程中遇到的問(wèn)題和解決思路。
? ? ERP目前使用的是Asp.Net WebForm框架,在.NET Core中已經(jīng)被廢棄了,那么這部分就只有采用重寫(xiě)的方式。在Asp.Net Core Mvc中RazorPage的訪問(wèn)方式跟WebForm基于物理路徑的訪問(wèn)提供了類似的機(jī)制;并且PageModel也跟Aspx的CodeBehind的方式類似。就是基于這樣類似的方式,提供了結(jié)構(gòu)和邏輯復(fù)用的可能性。基于這個(gè)起點(diǎn),本篇將從如下步驟講述界面層的改造過(guò)程:
頁(yè)面路由:改造Url重定向的邏輯,定位到頁(yè)面;
Aspx頁(yè)面:改造Aspx頁(yè)面為cshtml頁(yè)面;
模板頁(yè)面:改造Master為L(zhǎng)ayout頁(yè)面;
服務(wù)端控件:改造服務(wù)端用戶Ascx頁(yè)面。
二、頁(yè)面路由改造
? ? ERP的頁(yè)面有兩種,一種是由建模配置出來(lái)的叫標(biāo)準(zhǔn)頁(yè)面,由平臺(tái)托管來(lái)提供功能;另一種是自定義頁(yè)面,自己寫(xiě)Aspx實(shí)現(xiàn)所有業(yè)務(wù)邏輯。標(biāo)準(zhǔn)頁(yè)面使用Url重寫(xiě),而自定義頁(yè)面直接用路徑訪問(wèn)。在Asp.Net Core MVC中從Url映射到RazorPage是通過(guò)路由模塊來(lái)實(shí)現(xiàn)的,為了支持以前的訪問(wèn)Url不變,我們需要對(duì)路由進(jìn)行擴(kuò)展。
? ? 區(qū)別于傳統(tǒng)Asp.Net Mvc的頁(yè)面通過(guò)路徑加載,然后運(yùn)行時(shí)編譯不同,在.NET Core中默認(rèn)是直接將*.cshtml頁(yè)面編譯到DLL中,然后通過(guò)AssemblyPart加載到MVC框架中,默認(rèn)RazorPage的路徑是DLL中相對(duì)于Page文件夾的路徑。
? ? 例如下圖所示的頁(yè)面訪問(wèn)路徑為“/PubPlatform/Nav/Home/Default”:
1、標(biāo)準(zhǔn)頁(yè)面路由
? ?舉一個(gè)平臺(tái)的標(biāo)準(zhǔn)頁(yè)面Url的例子:“/std/00002212/822952a2-5091-e711-9bda-001583b5bd1a.aspx”。這個(gè)Url可以通過(guò)HttpModule重寫(xiě)為:
“/_frontend/templates/pages/Grid/template.aspx?FunctionCode=00002212&_pagename=822952a2-5091-e711-9bda-001583b5bd1a”。重寫(xiě)主要目的是找到“822952a2-5091-e711-9bda-001583b5bd1a”這個(gè)ID對(duì)應(yīng)頁(yè)面元數(shù)據(jù)內(nèi)容,然后通過(guò)配置映射到物理路徑的template.aspx頁(yè)面。
?RazorPage是通過(guò)IPageRouteModel Convention來(lái)提供路由的擴(kuò)展的,提供了PageRouteModelConvention和FolderRoute ModelConvention類來(lái)實(shí)現(xiàn)擴(kuò)展,這兩種使用方式可以通過(guò)內(nèi)置的AddPageRoute和AddFolderRouteModelConvention方法來(lái)實(shí)現(xiàn),下面是平臺(tái)使用文件夾路由轉(zhuǎn)換的代碼示例:
? ? 在上述代碼中我們?yōu)樗?templates/pages路徑下的頁(yè)面,添加三個(gè)路由參數(shù):
AppConst.FunctionCodeKey(FucntionCode):模塊代碼 對(duì)應(yīng)上述Url中的00002212;
AppConst.PageNameKey(PageName):元數(shù)據(jù)標(biāo)識(shí) 對(duì)應(yīng)上述Url中的822952a2-5091-e711-9bda-001583b5bd1a;
AppConst.PageKey(Page):RazorPage頁(yè)面的相對(duì)于Page文件夾的路徑(框架自帶)。
? ? TemplateConstraint實(shí)現(xiàn)IActionConstraint用于在路由系統(tǒng)中決定URL是否匹配當(dāng)前頁(yè)面,還是仿照之前HttpModule中Url重寫(xiě)邏輯來(lái)做路由匹配即可實(shí)現(xiàn)Url路由到對(duì)應(yīng)的RazorPage。
2、自定義頁(yè)面路由
? ? 我們先看看改造前和改造后頁(yè)面的組織結(jié)構(gòu)對(duì)比:
? 在改造自定義頁(yè)面路由的時(shí)候我們使用IPageRouteModelProvider擴(kuò)展OnProviders Executing,擴(kuò)展代碼如下:
? ? 在標(biāo)準(zhǔn)頁(yè)面中我們使用了MVC自帶的擴(kuò)展機(jī)制,自定義頁(yè)面我們使用IPageRoute ModelProvider擴(kuò)展OnProvidersExecuting,因?yàn)樵跇?biāo)準(zhǔn)頁(yè)面中我們使用路徑來(lái)做匹配,但是自定義頁(yè)面中需要兼容產(chǎn)品頁(yè)面路徑和項(xiàng)目頁(yè)面路徑,使用了類似于黑名單和白名單的區(qū)別,所以分別采用了不同的策略。另外這里我們擴(kuò)展了OnProvidersExecuted的方法,在所有路由加載完之后來(lái)進(jìn)行路由替換,來(lái)實(shí)現(xiàn)項(xiàng)目頁(yè)面替換產(chǎn)品頁(yè)面。
三、頁(yè)面改造
? ? 解決了路由訪問(wèn)的問(wèn)題,接下來(lái)就要進(jìn)行Aspx頁(yè)面改造成cshtml了。首先我們簡(jiǎn)單介紹下RazorPage的一些基本特性:
前端通過(guò)cshmtl來(lái)渲染Html;
后端通過(guò)PageModel來(lái)處理業(yè)務(wù)邏輯;
通過(guò)OnGet或者OnPost來(lái)提供訪問(wèn);
通過(guò)PageModel屬性和BindProperty來(lái)實(shí)現(xiàn)參數(shù)到模型的綁定;
通過(guò)PageModel屬性實(shí)現(xiàn)PageModel到cshtml頁(yè)面的數(shù)據(jù)傳遞。
? ? ERP所有的Aspx都沒(méi)有*.cs的后臺(tái)代碼。這里簡(jiǎn)單介紹下ERP對(duì)Aspx頁(yè)面的使用規(guī)范:
實(shí)現(xiàn)Html渲染邏輯;
OnLoad方法重寫(xiě)實(shí)現(xiàn)取數(shù)等簡(jiǎn)單后臺(tái)操作;
頁(yè)面回傳請(qǐng)求通過(guò)Ajax處理 從功能特性可以發(fā)現(xiàn)我們不用處理傳統(tǒng)Aspx的回傳的請(qǐng)求。Aspx的OnLoad可以使用RazorPag的OnGet方法中來(lái)替換,然后賦值給PageModel的屬性來(lái)傳遞給cshtml來(lái)綁定數(shù)據(jù),頁(yè)面中只需要將Aspx語(yǔ)法替換成Razor的語(yǔ)法即可。
? ? 語(yǔ)法轉(zhuǎn)換工具可以從這里找到:https://github.com/telerik/razor-converter。
? ? 在之前ERP中由于缺少了繼承自Page的基類所以在頁(yè)面中會(huì)有重復(fù)代碼,在新版本改造中我們將一些通用方法進(jìn)行了抽取,做了如下的繼承層級(jí)的改造:
? ? 有了這一層之后就能對(duì)一些例如權(quán)限、多語(yǔ)言、多時(shí)區(qū)等進(jìn)行通用代碼的封裝。并且為以后通用邏輯等定義一個(gè)可擴(kuò)展的地方。但是封裝之后還有一個(gè)問(wèn)題如果每個(gè)頁(yè)面都需要去設(shè)置繼承關(guān)系就會(huì)顯得有些重復(fù),并且容易犯錯(cuò),在MVC中提供了如下兩種簡(jiǎn)潔的方式:
_ViewStart中定義的邏輯全部頁(yè)面生效;
_ViewImport按照目錄優(yōu)先級(jí),路徑靠近_ViewImport會(huì)重寫(xiě)上層的邏輯,對(duì)下層的頁(yè)面生效。
四、模板頁(yè)面改造
? ? 我們?cè)谧鲰?yè)面的時(shí)候一般都是需要使用Master頁(yè)面來(lái)做布局封裝的,首先我們來(lái)一張看看平臺(tái)Aspx和cshtml的布局層級(jí)對(duì)比圖 :
1、頂層Layout頁(yè)面
? ? 原來(lái)的Master頁(yè)面中有大量的重復(fù)代碼,所以這里做了一層抽象。頂層Layout頁(yè)面用來(lái)定義Html結(jié)構(gòu),通常html中將css部分放在header中,將script部分放在body的最下面。另外css和js分別可以分類為,第三方css/js,通用css/js,文件引用的css/js,系統(tǒng)自定義輸出到界面的css/js和用戶自定義的css和js,這些都是通過(guò)MVC的RenderSection方法,提供類似與類中virutal方法一樣可以由頁(yè)面重寫(xiě)。下面是示例代碼:
2、模板頁(yè)面
? ? Nav模板頁(yè)面定義Body結(jié)構(gòu),包括不同的頁(yè)面布局:
_Nav.cshtml 用來(lái)定義帶導(dǎo)航的頁(yè)面;
_Json.cshtml 用來(lái)定義json頁(yè)面;
_Content.cshtml 用來(lái)定義無(wú)導(dǎo)航的內(nèi)容頁(yè)面;
_Dialog.cshtml 用來(lái)定義彈框頁(yè)面。
3、模板頁(yè)面設(shè)置
? ? 在ERP中因?yàn)闃?biāo)準(zhǔn)頁(yè)面可以掛載到不同的模板中,例如常見(jiàn)的表單頁(yè)面可以加載到帶導(dǎo)航的頁(yè)面中,也可以加載到彈出框中。而且指定模板的方式不是通過(guò)直接跟路徑關(guān)聯(lián).由于使用了預(yù)編譯的機(jī)制,并且頁(yè)面的路徑希望對(duì)其他引用者屏蔽,所以用名字代替頁(yè)面路徑來(lái)設(shè)置模板,從而讓模板頁(yè)面路徑和使用者解耦。通過(guò)如下邏輯來(lái)替換:
在自定義頁(yè)面中我們直接使用名字指定;
在標(biāo)準(zhǔn)建模頁(yè)面中我們使用元數(shù)據(jù)中配置的名字來(lái)指定;
在url中通過(guò)_mp參數(shù)來(lái)指定模板的名字,兼容一些圖書(shū)兼容場(chǎng)景,例如頁(yè)面嵌套的業(yè)務(wù)場(chǎng)景;
最終為了兼容一套名稱和路徑對(duì)應(yīng)關(guān)系,兼容Framework和Core我們使用了類似如下的映射方式。
五、服務(wù)端控件ascx改造
? ? 在Aspx的使用中為了提高代碼的復(fù)用程度,一般都會(huì)使用Ascx的自定義控件,在MVC中有多種的替代方式,在比較單純的數(shù)據(jù)綁定頁(yè)面中我們使用了PartialView來(lái)替代,這部分主要在布局頁(yè)面中使用,有如下模板:
Collapse.cshtml分類組件頁(yè)面,默認(rèn)收起左側(cè)導(dǎo)航;
Footer.cshtml 底部組件,用于返回頂部;
Holder.cshtml 內(nèi)容部件;
Navbar.cshtml 頂部導(dǎo)航組件,用于顯示用戶相關(guān)設(shè)置通知等信息;
Sidebar.cshtml 用于顯示左邊菜單。
? ? 為了將CSS,JS等資源加載更加集中化,我們也將布局頁(yè)面中一些資源按照功能加載進(jìn)行了加載。如下:
模塊化sea.js;
默認(rèn)的一些輸出到界面的js變量,如用戶名,時(shí)區(qū),頁(yè)面元數(shù)據(jù)等;
權(quán)限事件腳本等,(之前這部分可能會(huì)放在后臺(tái)cs代碼中,現(xiàn)在也放在部分頁(yè)里面加載)。
? ? 除了這些簡(jiǎn)單邏輯之外,還有一些組件會(huì)直接使用后臺(tái)的服務(wù)取數(shù)據(jù),然后渲染頁(yè)面這部分使用Core的Component(視圖組件)來(lái)加載 例如tab頁(yè)面的加載,這里需要取得上下文中用戶信息,從而獲取權(quán)限然后判斷是否加載。
? ? 在標(biāo)準(zhǔn)頁(yè)面中會(huì)用到列表,樹(shù)列表等大控件,這類控件的邏輯在后臺(tái)配合RazorEngine來(lái)渲染的,這部分對(duì)Asp.NET Core MVC正好兼容,在原來(lái)是通過(guò)自定義控件來(lái)提供訪問(wèn),在Asp.NET MVC中是通過(guò)Html.XXXFor類似方法提供訪問(wèn),在Core中為了讓cshtml可讀性更好,提供了TagHelper的方式,這里也將訪問(wèn)方式通過(guò)了TagHelper封裝,代碼示例:
六、總結(jié)
? ??在Framework中有很多前端代碼和后端代碼糅合在一個(gè)Aspx頁(yè)面中。有了RazorPage之后可以用cshtml來(lái)渲染界面,使用PageModel來(lái)處理數(shù)據(jù),這樣做到前端邏輯和后端邏輯的分離,并且根據(jù)職責(zé)將之前的不同功能的界面渲染需求,進(jìn)行封裝和職責(zé)的分離,使前端渲染邏輯更加清晰也更容易維護(hù)。在下一篇分享中,我們將介紹針對(duì)頁(yè)面資源文件引用(例如CSS、JS文件)的遷移過(guò)程,大家敬請(qǐng)期待。
------ END ------
作者簡(jiǎn)介
熊同學(xué):?研發(fā)工程師,目前負(fù)責(zé)ERP運(yùn)行平臺(tái)的設(shè)計(jì)與開(kāi)發(fā)工作。
也許您還想看
【復(fù)雜系統(tǒng)遷移 .NET Core平臺(tái)系列】之遷移項(xiàng)目工程
.NET Core MVC擴(kuò)展實(shí)踐
分布式鎖的實(shí)現(xiàn)與探索
ERP緩存實(shí)踐經(jīng)驗(yàn)分享
總結(jié)
以上是生活随笔為你收集整理的【复杂系统迁移 .NET Core平台系列】之界面层的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 【复杂系统迁移 .NET Core平台系
- 下一篇: 解惑小微企业信息化系统上云的顾虑