今年年初進了一家新公司,進入之后一邊維護老專案一邊了解專案流程,為了接下來的專案重做積累點經驗,
先說下老專案吧,.net fx 3.5+oracle......
在實際維護中逐漸發現,老專案有標準版、定制版兩種,標準版就是一套代碼,粗略計算了下,全部版本加起來有20+個版本,如果專案重做后還是依照這個模式去開發維護,估計距離猝死也不遠了,并且不同版本代碼的復用率極低(好吧,根本沒有),打個比方,我在標準版中發現了一個bug,需要去其他的20+版本里面都修改一遍,刪庫跑路了解一下,,,,
為了提升工資(偷懶),進公司沒多久就在想辦法,如何去提高不同專案的代碼復用率,然后想起來了wtm、abp、simplcommerce這三種專案,似乎有不同專案中代碼服用的地方,
wtm、abp類似,是將底層的部分controller、view封裝在底層類別庫,然后專案最外層去使用;
simplcommerce是將所有的模塊放在各個類別庫中,然后在主專案中集成;
(或許是我看的不夠深入,歡迎指正)
這三種專案,對于我的不同專案提交代碼復用率來說,不能直接起到作用,但是卻提供了一種思路,我們可以將原始的標準版作為一個類別庫,然后在不同的專案中參考這個類別庫,做到絕大部分的代碼復用,少部分修改,
我們如果想在定制專案中對標準版某個controller的某個action進行修改該怎么辦?
1.我首先想到的是在個性化專案中寫一個同名的controller,然后這個controller繼承自默認版本的對應controller,來達到重寫的目的,但是這個慣性思維陷入誤區了,mvc對于controller的控制不和普通的type繼承一樣,如果同名controller存在,則會報錯,,,在運行時我們可以判斷出是哪個action不同,但是無法通過emit來進行修改,所以這種辦法不可以,
2.第一種辦法不行,那么我們是否可以對于同名controller進行名稱上的修改,比如homecontroller在Tailor.Custom1中修改未TailorCustom1homecontroller,然后利用路由進行重定向?結果發現路由重定向,要么自定義一個路由中間件(求大佬給解決辦法,我不會,,),要么在請求進入的時候對請求進行重定向(這種重定向就是對HttpContext.Request.Path進行特殊判斷和處理,符合條件的進行重定向,但是可能會有很大的問題)
3.使用版本控制的思路,這個似乎可以,我們將標準版default中所有的都作為版本1.0,然后定制化作為2.0,在請求進入的時候,將請求頭添加一個version,如果mvc找不到這個version的controller或者action,會自動轉到默認的1.0版本中
那我們開始新建一個簡化版的專案,大概的分組可以做這樣

native/default作為標準版web類別庫;
Tailor.Custom* 是定制化網站;
entity是物體、service是服務,物體和服務我們暫且不說,先說明下default這個標準web類別庫,這個類別庫就是上面所說的標準類別庫,讓其他的Tailor.Custom1、Tailor.Custom1.Https、Tailor.Custom2.Https、Tailor.Custom3.Https(以下稱定制專案)去參考,然后再各自的專案中可以個性化修改
標準web類別庫的csproj檔案做適當的修改以更改成web類別庫
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup Label="Globals"> <SccProjectName>SAK</SccProjectName> <SccProvider>SAK</SccProvider> <SccAuxPath>SAK</SccAuxPath> <SccLocalPath>SAK</SccLocalPath> </PropertyGroup> <PropertyGroup> <TargetFramework>netcoreapp3.1</TargetFramework> <OutputType>Library</OutputType> </PropertyGroup> ... </Project>
然后借鑒wtm中使用專案對web類別庫的參考,在標準web類別庫中添加FrameworkServiceExtension.cs檔案
public static IServiceCollection AddFrameworkService(this IServiceCollection services, WebHostBuilderContext webHostBuilderContext = null )//在定制版本的Startup.ConfigureServices中添加services.AddFrameworkService();即可 { CurrentDirectoryHelpers.SetCurrentDirectory(); var configBuilder = new ConfigurationBuilder(); if (!File.Exists(Path.Combine(Directory.GetCurrentDirectory(), "appsettings.json"))) { var binLocation = Assembly.GetEntryAssembly()?.Location; if (!string.IsNullOrEmpty(binLocation)) { var binPath = new FileInfo(binLocation).Directory?.FullName; if (File.Exists(Path.Combine(binPath, "appsettings.json"))) { Directory.SetCurrentDirectory(binPath); configBuilder.SetBasePath(binPath) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddEnvironmentVariables(); } } } else { configBuilder.SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddEnvironmentVariables(); } if (webHostBuilderContext != null) { var env = webHostBuilderContext.HostingEnvironment; configBuilder .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); } var config = configBuilder.Build(); new AppSettingProvider().Initial(config);//添加靜態的配置全域組態檔 var gd = AssemblyHelper.GetGlobalData(); var currentNamespace = MethodBase.GetCurrentMethod().DeclaringType.Namespace; //獲取標準web類別庫的Assembly var currentAssembly = gd.AllAssembly.Where(x => x.ManifestModule.Name == $"{currentNamespace}.dll").FirstOrDefault(); StackTrace ss = new StackTrace(true); MethodBase mb = ss.GetFrame(ss.FrameCount - 1).GetMethod(); var userNamespace = mb.DeclaringType.Namespace;//呼叫標準web類別庫的定制版專案命名空間 services.AddMvc(options => { options.EnableEndpointRouting = false; }); services.AddRazorPages()//添加RazorPages .AddRazorRuntimeCompilation() .ConfigureApplicationPartManager(m => { //將標準web類別庫的Controllers添加到定制版,即我們要運行的網站中 var feature = new ControllerFeature(); if (currentAssembly != null) { m.ApplicationParts.Add(new AssemblyPart(currentAssembly)); } m.PopulateFeature(feature); services.AddSingleton(feature.Controllers.Select(t => t.AsType()).ToArray()); }) .AddControllersAsServices() .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix);//添加多語言支持 //services.Configure<MvcRazorRuntimeCompilationOptions>(options => //{ // if (currentAssembly != null) // { // options.FileProviders.Add( // new EmbeddedFileProvider( // currentAssembly, // currentNamespace // your external assembly's base namespace // ) // ); // } //}); services.AddSingleton<ILoginUserService, LoginUserService>();//添加需要參考的其他服務 services.AddMvc(options => { options.Conventions.Add(new ApiControllerVersionConvention());//添加版本控制時忽略添加的某些重要屬性 }); services.AddApiVersioning(o => { o.ReportApiVersions = true;//回傳版本可使用的版本 //o.ApiVersionReader = new UrlSegmentApiVersionReader(); //o.ApiVersionReader = ApiVersionReader.Combine(new HeaderApiVersionReader("api-version"), new QueryStringApiVersionReader("api-version")); //o.ApiVersionReader = ApiVersionReader.Combine(new QueryStringApiVersionReader("api-version")); o.ApiVersionReader = ApiVersionReader.Combine(new HeaderApiVersionReader("api-version"));//版本號以什么形式,什么欄位傳遞 o.AssumeDefaultVersionWhenUnspecified = true; o.DefaultApiVersion = new ApiVersion(1, 0);//默認版本號 o.ApiVersionSelector = new CurrentImplementationApiVersionSelector(o);//默認以當前最高版本進行訪問 }); return services; }
public static IApplicationBuilder UseFrameworkService(this IApplicationBuilder app, Action<IRouteBuilder> customRoutes = null)//在定制版本的Startup.ConfigureServices中添加services.UseFrameworkService();即可
{ app.UseExceptionHandler("/Home/Error"); app.UseStaticFiles(); app.UseAuthentication(); app.Use(async (context, next) => { try { await next.Invoke(); } catch (ConnectionResetException) { } if (context.Response.StatusCode == 404) { await context.Response.WriteAsync(string.Empty); } }); app.UseMiddleware<CustomRewriteMiddleware>(); if (customRoutes != null) { app.UseMvc(customRoutes); } else { app.UseMvc(routes => { routes.MapRoute( name: "areaRoute", template: "{area:exists}/{controller=Home}/{action=Index}/{id?}"); routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } return app; }
我們在標準web類別庫中,將所有的Controller都添加上默認的版本號1.0
[ApiVersion("1.0")] [Route("[controller]/[action]")] [ApiController] 或者Areas中的添加 [Area("User")]//User時Area的name [ApiVersion("1.0")] [Route("[area]/[controller]/[action]")] [ApiController]
我們的定制版本中,需要重寫的Controller添加上對應標準web類別庫里面對應名字的Controller,對應的ApiVersion修改成大于1.0的版本號,新添加的Controller繼承自對應的標準web類別庫的對應Controller
namespace Tailor.Custom3.Https.Controllers { [ApiVersion("2.0")] [Route("[controller]/[action]")] [ApiController] public class HomeController : Default.Controllers.HomeController { private readonly ILogger<HomeController> _logger; private readonly ILoginUserService _userService; public HomeController(ILogger<HomeController> logger, ILoginUserService userService) : base(logger, userService) { _logger = logger; _userService = userService; } } }
此時,我們如果需要對某些Action進行重寫,則override對應Action,然后進行重寫;//Tailor.Custom1.Https和Tailor.Custom3.Https
我們如果需要對某些cshtml進行重寫,則在對應目錄添加相同名字的cshtml,然后進行重寫;//Tailor.Custom2.Https中只對cshtml進行重寫,Tailor.Custom3.Https中對Controller和cshtml都進行重寫
此時我們就可以寫一個標準版web類別庫,定制專案進行區域更改,如發現標準版web類別庫出現bug,可以只修改一處,處處生成上傳即可;再進一步,我們可以將生成的標準版web類別庫的dll檔案上傳到指定的服務器特定目錄,其他服務器對此目錄進行定時的加載或者判斷版本再去加載,這樣就可以省去很大的精力
但是在實際的專案使用中發現,可能由于Microsoft.AspNetCore.Mvc.Versioning這個包本身的問題,當我們的標準web類別庫中Controller有重名,但是不是同一個Views或者Areas目錄下時,我們的版本控制將會出現所有的同名Controller的可使用版本資訊將會變成所有的控制版本,,,這個暫時可以利用不同Controller名字進行規避,詳見:https://github.com/microsoft/aspnet-api-versioning/issues/630 【已修復】
具體實作代碼地址:https://github.com/wangpengzong/Tailor
Native/Default是標準版網站類別庫
Tailor.Custom* 是定制化網站,可以在此路徑下繼承Native/Default的對應Controller,利用overvide對需要重寫的action進行重寫,不需要重寫的不進行overvide即可,或者對cshtml進行重寫,不需要重寫的不在對應路徑下增加cshtml檔案即可
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/36132.html
標籤:.NET Core
上一篇:IoTClient開發6 - S7-200SmarTcp協議客戶端實作
下一篇:EFCore資料操作
