真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

Asp.NetMVC插件化開(kāi)發(fā)簡(jiǎn)化方案-創(chuàng)新互聯(lián)

Web 管理系統(tǒng)可以龐大到不可想像的地方,如果想就在一個(gè) Asp.Net MVC 項(xiàng)目中完成開(kāi)發(fā),這個(gè)工程將會(huì)變得非常龐大,協(xié)作起來(lái)也會(huì)比較困難。為了解決這個(gè)問(wèn)題,Asp.Net MVC 引入了 Areas 的概念,將模塊劃分到 Area 中去——然而 Area 仍然是主項(xiàng)目的一部分,多人協(xié)作的時(shí)候仍然很容易造成.csproj項(xiàng)目文件的沖突。

讓客戶(hù)滿(mǎn)意是我們工作的目標(biāo),不斷超越客戶(hù)的期望值來(lái)自于我們對(duì)這個(gè)行業(yè)的熱愛(ài)。我們立志把好的技術(shù)通過(guò)有效、簡(jiǎn)單的方式提供給客戶(hù),將通過(guò)不懈努力成為客戶(hù)在信息化領(lǐng)域值得信任、有價(jià)值的長(zhǎng)期合作伙伴,公司提供的服務(wù)項(xiàng)目有:主機(jī)域名、雅安服務(wù)器托管、營(yíng)銷(xiāo)軟件、網(wǎng)站建設(shè)、古浪網(wǎng)站維護(hù)、網(wǎng)站推廣。

對(duì)于這類(lèi)系統(tǒng),比較好的解決辦法是采用 SOA 的方式,把一個(gè)大的 Web 系統(tǒng)劃分成若干微服務(wù),通過(guò)一個(gè)含授權(quán)中心的 Web 集散框架組織起來(lái)。不過(guò)這里我要講的是另一種方法,插件化的開(kāi)發(fā)方案。

完整的插件化開(kāi)發(fā)會(huì)涉及到插件管理的方方面面,甚至還包括插件的熱插拔處理——當(dāng)然這些都是可以做到的——但今天我要說(shuō)的是一個(gè)簡(jiǎn)化方案,只是將業(yè)務(wù)模塊當(dāng)作插件在單獨(dú)的項(xiàng)目中開(kāi)發(fā),而后在發(fā)布的時(shí)候仍然以 Area 的形式集成到主 Web 項(xiàng)目當(dāng)中。嚴(yán)格的說(shuō),這并不是插件化,而只是模塊化,但它是插件化的第一步。

第 1 個(gè)實(shí)驗(yàn)

第一個(gè)實(shí)驗(yàn)的目的是為了把 Area 剝離出來(lái)作為單獨(dú)的項(xiàng)目開(kāi)發(fā)。所以先使用同樣版本的 .NET Framework 的 Asp.Net MVC Framework 創(chuàng)建兩個(gè)項(xiàng)目,這里我們選用了

  • .NET Framework 4.6

  • Microsoft.AspNet.Mvc 5.2.3

建立兩個(gè) MVC 項(xiàng)目,分別名為PluginWebAppPlugin1。

Asp.Net MVC 插件化開(kāi)發(fā)簡(jiǎn)化方案

PluginWebApp 項(xiàng)目

這個(gè)項(xiàng)目作為 Web 主項(xiàng)目,現(xiàn)在暫時(shí)不改它。但要檢查一下Global.asax.cs中,Application_Start事件中有這么一句:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    // ....
}

這是在注冊(cè)所有 Area。雖然現(xiàn)在 PluginWebApp 并沒(méi)有建 Area,但是這句話對(duì)于我們來(lái)說(shuō)是必不可少的。

Plugin1 項(xiàng)目

這是作為插件的項(xiàng)目,我們把它當(dāng)作一個(gè) Area 來(lái)開(kāi)發(fā)。所以先添加 Area。

操作:在“解決方案資源管理器”中“Plugin1”項(xiàng)目中點(diǎn)擊右鍵,選擇“添加→區(qū)域(A)”,輸入Plugin1為作 Area 名稱(chēng)

這樣,Plugin1 項(xiàng)目中就存在一個(gè)Areas目錄以及其目錄Plugin1,再把這個(gè)項(xiàng)目中除Areas目錄、packages.configWeb.config之外的所有其它目錄和文件刪除,之后整個(gè)項(xiàng)目看起來(lái)就像這樣:

Asp.Net MVC 插件化開(kāi)發(fā)簡(jiǎn)化方案

注意項(xiàng)目中存在一個(gè)Plugin1AreaRegistration.cs文件,在向 Web 應(yīng)用中注冊(cè) Area 的時(shí)候需要它。

現(xiàn)在在Controllers目錄下面添加控制器TestController,相應(yīng)的在Views下面添加Test/Index.cshtml視圖文件。內(nèi)容都不重要,只要能識(shí)別出來(lái)就行,所以在Test/Index.cshtml中修改

中的內(nèi)容為

Testing Page Index

準(zhǔn)備運(yùn)行

AreaRegistration.RegisterAllAreas()會(huì)在加載的 Assembly 中查找所有 Area 定義(AreaRegistration的子類(lèi)),完成 Area 的注冊(cè)。所以我們可以干兩件事情來(lái)安裝 Plugin

  • 把 Plugin1 項(xiàng)目的編譯結(jié)果Plugin1.dll拷貝到PluginWebAppbin目錄下

  • 在 PluginWebApp 項(xiàng)目下創(chuàng)建Areas目錄,下建Plugin1目錄,再把 Plugin1 項(xiàng)目的~/Areas/Plugin1/Views目錄拷貝過(guò)來(lái)

猜測(cè)做了這些操作之后,應(yīng)該可以運(yùn)行 PluginWebApp,輸入正常的 url 路徑之后可以訪問(wèn)到 Plugin1 的 Test 頁(yè)面。

運(yùn)行,并在瀏覽器中輸入http://localhost:5760/plugin1/test(這里的端口號(hào)是由 VS 自動(dòng)分配的,請(qǐng)注意修改)——結(jié)果還不錯(cuò)

Asp.Net MVC 插件化開(kāi)發(fā)簡(jiǎn)化方案

解耦

第一個(gè)實(shí)驗(yàn)成功,實(shí)事證明猜想沒(méi)有問(wèn)題。但于對(duì)開(kāi)發(fā)來(lái)說(shuō),就有問(wèn)題了。插件動(dòng)態(tài)庫(kù)放在PluginWebApp/bin中,與 PluginWebApp 的編譯結(jié)果混在一起了,這在以后發(fā)布、更新的時(shí)候可能造成麻煩。而且既然是插件,似乎應(yīng)該獨(dú)立一點(diǎn),如果 Plugin1 發(fā)布的所有東西都只在PluginWebApp/Areas/Plugin1目錄下就好了。

基于這個(gè)設(shè)想,PluginWebApp/Areas/Plugin1目錄應(yīng)該會(huì)是這樣一個(gè)結(jié)構(gòu):

Plugin1
  |---bin
  `---Views

當(dāng)然,把Plugin1.dll拷貝到bin目錄中去很容易,但還得讓 Asp.Net 加載它。于是嘗試在Application_Start中寫(xiě)了幾句代碼來(lái)加載

// 先不考慮任意插件的問(wèn)題,只加載 Plugin1 作為實(shí)驗(yàn)var dll = Sever.MapPath("~/Areas/Plugin/bin/Plugin1.dll");
Assembly.LoadFile(dll);

加載是加載了,但是http://localhost:5760/plugin1/test打不開(kāi),失?。?/p>

使用 BuildManager 和 PreApplicationStartMethodAttribute

上網(wǎng)查資料之后得知需要使用BuildManager.AddReferencedAssembly()將加載的 Assembly 添加到引用集合中,而這個(gè)事情似乎必須在Application_Start之前完成。

文檔里說(shuō)應(yīng)該在Application_PreStartInit階段,不過(guò)我準(zhǔn)備使用PreApplicationStartMethodAttribute來(lái)完成。為此,在 PluginWebApp 項(xiàng)目的App_Start下添加了一個(gè)PluginInitializer類(lèi)來(lái)干這個(gè)事情:

using System.Web;
using System.Web.Hosting;
using System.Web.Compilation;

[assembly: PreApplicationStartMethod(typeof(PluginWebApp.PluginInitializer), "Initialize")]
namespace PluginWebApp
{
    public static partial class PluginInitializer
    {
        public static void Initialize()
        {
            var dll = HostingEnvironment("~/Areas/Plugin1/bin/Plugin1.dll");
            var assembly = Assembly.LoadFile(dll);
            BuildManager.AddReferencedAssembly(assembly);
        }
    }
}

再次運(yùn)行,成功!

搜索并加載插件

到目前為止還是直接加載的 Plugin1 插件,實(shí)際工作中應(yīng)該去檢查Areas下面的子目錄,加載其bin目錄下的動(dòng)態(tài)庫(kù)。所以還需要修改PluginInitializer,讓它動(dòng)態(tài)搜索各插件目錄的bin/*.dll,并加載。

為此,不妨專(zhuān)門(mén)寫(xiě)一個(gè)PluginLoader類(lèi),因?yàn)檫@個(gè)類(lèi)現(xiàn)在只由PluginInitializer使用,所以直接寫(xiě)成它的嵌套類(lèi)

public static partial class PluginInitializer
{
    public sealed class PluginLoader
    {
        public void Load()
        {
            FindPluginDll(HostingEnvironment("~/Areas"))
                // 并行處理不是必須的,但在插件多的時(shí)候可能會(huì)更快
                .AsParallel()
                .ForAll(file => BuildManager.AddReferencedAssembly(Assembly.Load(file)));
        }

        // 從指定的插件根目錄 (這里是 Areas) 搜索帶 bin 目錄的插件目錄
        // 并將其中的 *.dll 找出來(lái)
        private static string[] FindPluginDll(string root)
        {
            return Directory.EnumerateDirectories(root)
                .Select(dir => Path.Combine(dir, "bin"))
                // 如果沒(méi)有 bin 目錄就忽略
                .Where(Directory.Exists)
                // 將 bin 目錄下的所有 dll 加載到集合中
                .SelectMany(bin => Directory
                    .EnumerateFiles(bin, "*.dll", SearchOption.AllDirectories))
                .ToArray();
        }
    }
}

動(dòng)態(tài)檢索的問(wèn)題解決了,但在實(shí)際開(kāi)發(fā)中又存在另一個(gè)問(wèn)題:運(yùn)行 Web 之后,再次構(gòu)建插件的并將插件內(nèi)容 (binView) 拷貝到主項(xiàng)目Areas下面對(duì)應(yīng)的插件目錄中時(shí),會(huì)因?yàn)樵瓉?lái)的 dll 文件在使用而不能覆蓋。

解決不能在 Web 運(yùn)行狀態(tài)下更新插件的問(wèn)題

在解決這個(gè)問(wèn)題就不能讓 Web 直接加載插件目錄中的 dll。采用 Asp.Net 的 Shadow Copy 的思想,我們可以在App_Data目錄中創(chuàng)建一個(gè)PluginCache目錄,然后在加載插件 dll 之前把所有 dll 拷貝到這個(gè)目錄下來(lái),再?gòu)倪@個(gè)目錄加載 dll。

再來(lái)改造一下PluginLoader

創(chuàng)建目錄和清空緩存都很簡(jiǎn)單,這里就不展示這兩個(gè)步驟的代碼了。
FindPluginDll的代碼在前面可以找到

public sealed class PluginLoader
{
    string PluginFolder { get; } = HostingEnvironment.MapPath("~/Areas");
    string PluginCacheFolder { get; } = HostingEnvironment.MapPath("~/App_Data/PluginCache");

    public void Load()
    {
        // 上述兩個(gè)目錄不存在,則創(chuàng)建,保證目錄存在
        MakeSureFolderExists();
        // 先清空緩存,避免已廢棄的插件還緩存在這里
        ClearCacheFolder();
        // 從各插件目錄把 dll 拷貝到緩存目錄
        CachePlugins();
        // 從緩存目錄加載所有 dll        
        LoadAssemblies();
    }

    private void CachePlugins()
    {
        // 找到所有插件的 dll
        FindPluginDll(PluginFolder)
            // 并行處理
            .AsParallel()
            .ForAll(file =>
            {
                var target = Path.Combine(PluginCacheFolder, Path.GetFileName(file));
                // 拷貝到緩存目錄
                File.Copy(file, target, true);
            });
    }

    private void LoadAssemblies()
    {
        // 在緩存目錄中查找所有 dll
        Directory.EnumerateFiles(PluginCacheFolder, "*.dll", SearchOption.AllDirectories)
            // 并行
            .AsParallel()
            // 加載所有 assembly
            .ForAll(file => BuildManager.AddReferencedAssembly(Assembly.LoadFile(file)));
    }
}

搞定!

細(xì)節(jié)處理

解決 Controller 尋址沖突

主 Web 程序和多個(gè)插件之間如果存在同名的 Controller,就可能造成訪問(wèn) URL 的時(shí)候出現(xiàn) Controller 尋址沖突,為了解決這個(gè)問(wèn)題,需要在注冊(cè)路徑的時(shí)候指定 Controller 的命名空間

主項(xiàng)目 PluginWebApp 的App_Start/RouteConfig.cs
public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
        namespaces: new[] { "PluginWebApp.Controllers" }    // 加了這句話
    );
}
插件的Plugin1AreaRegistration.cs
public override void RegisterArea(AreaRegistrationContext context)
{
    context
        .MapRoute(
            "Plugin1_default",
            "Plugin1/{controller}/{action}/{id}",
            new { controller = "Home", action = "Index", id = UrlParameter.Optional },
            new[] { "Plugin1.Areas.Plugin1.Controllers" }); // 加了這一句
}

處理刪除或拷貝 dll 文件時(shí)可能出現(xiàn)的異常

在作為ForAll的 Lambda 表達(dá)式中,每次刪除文件或拷貝文件都有可能出現(xiàn)異常,而出現(xiàn)這些異常的時(shí)候,不應(yīng)該中斷整個(gè)處理過(guò)程,所以需要使用try ... catch來(lái)處理異常。正常的處理方式應(yīng)該是記錄日志,這里偷個(gè)懶,直接忽略(生產(chǎn)環(huán)境嚴(yán)重不推薦忽略異常)。

由于這個(gè)操作在幾個(gè)地方都會(huì)用到,所以寫(xiě)一個(gè)IgnoreError來(lái)封裝 Lambda:

private static Action IgnoreError(Action action)
{
    return arg =>
    {
        try
        {
            action(arg);
        }
        catch
        {
            // ignore exceptions,
            // should log the error in production environment
        }
    };
}

然后在ForAll中這樣使用:

    .ForAll(IgnoreError(file => DealWithFile(file)));

后記

上述內(nèi)容充其量只是一個(gè)插件化開(kāi)發(fā)的簡(jiǎn)化方案。不過(guò)這個(gè)方案基本上也把一個(gè)插件化框架的結(jié)構(gòu)介紹清楚了。而且采用這種方式開(kāi)發(fā)還有一個(gè)好處:Plugin1 本身就是一個(gè) Web 項(xiàng)目,所以如果之前不刪除那么多東西,并加以適當(dāng)?shù)恼{(diào)整,它是可以獨(dú)立運(yùn)行的,便于開(kāi)發(fā)期調(diào)試。

當(dāng)然這個(gè)框架要用于工作中還需要完善不少工作,包括:

  • 定義插件接口和抽象基類(lèi),提供初始化,注入上下文(比如應(yīng)用配置等),注冊(cè)路由等接口方法。

  • 主項(xiàng)目或框架項(xiàng)目中定義插件管理器,管理插件的生命周期,實(shí)現(xiàn)熱插拔

    • 加載、注冊(cè)

    • 檢查更新、新增插件等

    • 卸載插件 Assembly 并重新加載

  • 使用 Plugins 代替 Areas 目錄,讓插件與 Area 區(qū)分開(kāi)來(lái),這需要

    • 在插件管理器中實(shí)現(xiàn)AreaRegistration.RegisterAllAreas()的一些功能

    • Plugins目錄添加到 Razor 視圖搜索路徑中 (需要自定義RazorViewEngine)

  • 設(shè)計(jì)插件間的資源共享和通信機(jī)制

  • 插件管理的 UI 或 CLI

源代碼

  • on Gitee.com

參考

  • ASP.NET 插件化機(jī)制

  • ASP.NET MVC 4 插件化架構(gòu)簡(jiǎn)單實(shí)現(xiàn)-實(shí)例篇


歡迎關(guān)注作者的開(kāi)發(fā)技術(shù)微信公眾號(hào)

Asp.Net MVC 插件化開(kāi)發(fā)簡(jiǎn)化方案

創(chuàng)新互聯(lián)www.cdcxhl.cn,專(zhuān)業(yè)提供香港、美國(guó)云服務(wù)器,動(dòng)態(tài)BGP最優(yōu)骨干路由自動(dòng)選擇,持續(xù)穩(wěn)定高效的網(wǎng)絡(luò)助力業(yè)務(wù)部署。公司持有工信部辦法的idc、isp許可證, 機(jī)房獨(dú)有T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確進(jìn)行流量調(diào)度,確保服務(wù)器高可用性。佳節(jié)活動(dòng)現(xiàn)已開(kāi)啟,新人活動(dòng)云服務(wù)器買(mǎi)多久送多久。


名稱(chēng)欄目:Asp.NetMVC插件化開(kāi)發(fā)簡(jiǎn)化方案-創(chuàng)新互聯(lián)
當(dāng)前地址:http://weahome.cn/article/csshco.html

其他資訊

在線咨詢(xún)

微信咨詢(xún)

電話咨詢(xún)

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部