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

先说下老项目吧,.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. Web安全学习笔记 SQL注入中

    Web安全学习笔记 SQL注入中 繁枝插云欣 --ICML8 权限提升 数据库检测 绕过技巧 一.权限提升 1. UDF提权 UDF User Defined Function,用户自定义函数 是My ...

  2. AQS源码探究之竞争锁资源

    AQS源码探究---竞争锁资源 我们进入ReentrantLock源码中查看其内部类 Sync 对AQS进行扩展公共方法并定义抽象方法的抽象类 FaireSync 实现公平锁的AQS的实现类 UnFa ...

  3. C# WPF后台动态添加控件(经典)

    概述 在Winform中从后台添加控件相对比较容易,但是在WPF中,我们知道界面是通过XAML编写的,如何把后台写好的控件动态添加到前台呢?本节举例介绍这个问题. 这里要用到UniformGrid布局 ...

  4. 找到占用CPU最高的Java线程

    一.找到java进程id jps查看当前运行的java进程id [root@localhost ~]# jps 18354 Jps 9381 Bootstrap 二.找到内存和CPU占用最高的线程pi ...

  5. 806. Number of Lines To Write String - LeetCode

    Question 806. Number of Lines To Write String Solution 思路:注意一点,如果a长度为4,当前行已经用了98个单元,要另起一行. Java实现: p ...

  6. Zookeeper安装学习(一)

    学习内容:Zookeeper本地安装 前提准备:①JDK安装成功 ②通过XShell7将Zookeeper安装包(apache-zookeeper-3.5.7-bin.tar.gz)拷贝到Linux系 ...

  7. 数据库与MySQL的下载使用

    目录 数据存储演变史 数据库应用发展史 数据库本质 数据库分类 关系型数据库 非关系型数据库 SQL与NoSQL MySQL简介 版本问题 下载使用 目录结构 基本使用 简单使用 系统服务 修改密码 ...

  8. .net6.0 中一个接口多个实现的服务注册与注入

    1.现有一个数据库操作接口 如下   它有两个数据操作实现 Sqlserver 和MySql的数据库操作实现类 现在我们需要 将这个两个类 注册到MVC中 注意这里注册的服务类型都是 IDataBas ...

  9. 题解 P1276 校门外的树(增强版)

    前言 本蒟蒻重学线段树,发现了这道题可以用线段树做. 虽然数据范围很小可以直接暴力,但由于在练习线段树所以打算用线段树写这道题. 本题解针对已经有线段树基础的巨佬,不懂线段树原理的话可以学习线段树后再 ...

  10. Halodoc使用 Apache Hudi 构建 Lakehouse的关键经验

    Halodoc 数据工程已经从传统的数据平台 1.0 发展到使用 LakeHouse 架构的现代数据平台 2.0 的改造.在我们之前的博客中,我们提到了我们如何在 Halodoc 实施 Lakehou ...