zoukankan      html  css  js  c++  java
  • .netcore 定制化项目开发的思考和实现

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

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

          

  • 相关阅读:
    LeetCode120 Triangle
    LeetCode119 Pascal's Triangle II
    LeetCode118 Pascal's Triangle
    LeetCode115 Distinct Subsequences
    LeetCode114 Flatten Binary Tree to Linked List
    LeetCode113 Path Sum II
    LeetCode112 Path Sum
    LeetCode111 Minimum Depth of Binary Tree
    Windows下搭建PHP开发环境-WEB服务器
    如何发布可用于azure的镜像文件
  • 原文地址:https://www.cnblogs.com/wangpengzong/p/12833014.html
Copyright © 2011-2022 走看看