今年年初进了一家新公司,进入之后一边维护老项目一边了解项目流程,为了接下来的项目重做积累点经验。

先说下老项目吧,.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文件即可

.netcore 定制化项目开发的思考和实现的更多相关文章

  1. J2EE学习从菜鸟变大鸟之八 企业级项目开发的思考

    什么是企业级项目开发 "企业级项目".企业级项目开发,Java也是企业级项目开发,这个我们到处说.听,每天被我们挂在嘴边,可是到底什么项目才算是"企业级"?自己 ...

  2. kettle系列-4.kettle定制化开发工具类

    要说的话这个工具类还是比较简单的,每个方法体都比较小,但用起来还是可以的,把开发中一些常用的步骤封装了下,不用去kettle源码中找相关操作的具体实现了. 算了废话不多了,直接上重点,代码如下: im ...

  3. AI应用开发实战 - 定制化视觉服务的使用

    AI应用开发实战 - 定制化视觉服务的使用 本篇教程的目标是学会使用定制化视觉服务,并能在UWP应用中集成定制化视觉服务模型. 前一篇:AI应用开发实战 - 手写识别应用入门 建议和反馈,请发送到 h ...

  4. OpenStack Queens版本Horizon定制化开发

    工具环境 1.VMware workstation 12+: 2.Ubuntu系统+Linux Pycharm: 3.OpenStack Queens版本Horizon代码: 问题及解决 1.项目代码 ...

  5. LBPL--基于Asp.net、 quartz.net 快速开发定时服务的插件化项目

    LBPL 这一个基于Asp.net. quartz.net 快速开发定时服务的插件化项目 由于在实际项目开发中需要做定时服务的操作,大体上可以理解为:需要动态化监控定时任务的调度系统. 为了实现快速开 ...

  6. PLDroidPlayer 是七牛推出的一款免费的适用于 Android 平台的播放器 SDK,采用全自研的跨平台播放内核,拥有丰富的功能和优异的性能,可高度定制化和二次开发。 https://developer.qiniu.com/pili/sdk/…

    PLDroidPlayer PLDroidPlayer 是一个适用于 Android 平台的音视频播放器 SDK,可高度定制化和二次开发,为 Android 开发者提供了简单.快捷的接口,帮助开发者在 ...

  7. Gradle 实现 Android 多渠道定制化打包

    Gradle 实现 Android 多渠道定制化打包 版权声明:本文为博主原创文章,未经博主允许不得转载. 最近在项目中遇到需要实现 Apk 多渠道.定制化打包, Google .百度查找了一些资料, ...

  8. 简历生成平台项目开发-STEP2问卷调查结果统计分析

    根据之前设计的调查问卷,截止目前为止,一共收到64份问卷结果.一共16题,分别从基本信息.是否对简历制作有需要.对产品期望的特点和建议采纳四个方面设计问题.下面逐题分析问卷结果: 1.您的性别 可以看 ...

  9. Vue.js-组件化前端开发新思路

    Vue.js-组件化前端开发新思路 12017.04.14 18:31:25字数 6228阅读 5632 本文章是我最近在公司的一场内部分享的内容.我有个习惯就是每次分享都会先将要分享的内容写成文章. ...

随机推荐

  1. AQS源码三视-JUC系列

    AQS源码三视-JUC系列 前两篇文章介绍了AQS的核心同步机制,使用CHL同步队列实现线程等待和唤醒,一个int值记录资源量.为上层各式各样的同步器实现画好了模版,像已经介绍到的ReentrantL ...

  2. iOS全埋点解决方案-数据存储

    前言 ​ SDK 需要把事件数据缓冲到本地,待符合一定策略再去同步数据. 一.数据存储策略 ​ 在 iOS 应用程序中,从 "数据缓冲在哪里" 这个纬度看,缓冲一般分两种类型. 内 ...

  3. 微信小程序使用echarts遇到的问题

    问题1:ec-canvas出现上下滑动页面会漂移 解决方法:在标签内加 force-use-old-canvas="true" 问题2:echarts的tooltip会超出边界 解 ...

  4. 1个程序员单干之:怎样给我的升讯威在线客服系统编写堪比 MSDN 的用户手册

    本系列文章详细介绍使用 .net core 和 WPF 开发 升讯威在线客服与营销系统 的过程. 免费在线使用 & 免费私有化部署:https://kf.shengxunwei.com 视频实 ...

  5. 工具分享:清理 Markdown 中没有引用的图片

    前言: 之前,我写笔记的工具一直都是 notion,而且没有写博客的习惯.但是一是由于 notion 的服务器在国外,有时候很不稳定:二是由于 notion 的分享很不方便,把笔记分享给别人点开链接之 ...

  6. XtraBackup 搭建从库的一般步骤及 XtraBackup 8.0 的注意事项

    搭建从库,本质上需要的只是一个一致性备份集及这个备份集对应的位置点信息.之前介绍的几个备份工具(MySQL中如何选择合适的备份策略和备份工具)均可满足. 这里,我们重点看看如何基于 XtraBacku ...

  7. Myers差分算法的理解、实现、可视化

    作者:Oto_G QQ: 421739728 目录 简介 基础 差异的描述 好的差异比较 算法介绍 名词解释 两个定理 绘制编辑图 感谢 简介 本文章对Myers差分算法(Myers Diff Alg ...

  8. 开发工具-SSMS官方下载地址

    更新记录 2022年6月10日 完善标题. SQL Server Management Studio官方下载地址 https://docs.microsoft.com/en-us/sql/ssms/d ...

  9. SAP BDC 用户输入日期转系统日期格式: CONVERT_DATE_TO_EXTERNAL

    BDC中,日期输入格式不正确:可调用FM  CONVERT_DATE_TO_EXTERNAL DATA:l_bdcfield LIKE bdcdata-fval."BDC field val ...

  10. Vue的基础语法

    前言 Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架.与其它大型框架不同的是, Vue 被设计为可以自底向上逐层应用.Vue 的核心库只关注视图层,不仅易于上手, ...