無業遊民寫的最後一個.net有關專案框架

星仔007發表於2024-06-30

理想很豐滿,現實往往很殘酷。

一種按照ddd的方式,根據業務來把自己需要的模組一個一個寫出來,再按照模組把需要的介面一個一個的寫出來,堆砌一些中介軟體,以及解耦的command,handler等等

,一個專案就這麼成型了。上面的專案有一個非常清晰的特點,就是按需開發,不需要去可以定義業務相關的公共的模組,有就有沒就沒。這專案看起來沒有什麼公共框架,就是一個專案。當然這樣效率效能也是最高的,不需要過多的包裝一層又一層的公共程式碼。

有關示例如下,不做過多的贅述:

liuzhixin405/netcore-micro (github.com)

一種業務非常大,開發人員只需要寫業務實現,這就需要一個公共框架,提供公共的可複製模組讓業務人員寫業務程式碼。

下面以為簡潔的方式呈現這種開發模式,專案層級如下:

三個模組分別是業務模組,主機,基礎模組。業務模組Business透過dll形式提供給host來完成註冊和釋出。

主機host可以存放公共的基礎模組,例如註冊、登入、認證等,這裡省略。

業務模組存放業務程式碼,包括提供介面。

流程如下:request => 業務模組controller => business => service=> repository

整個專案介面不變,實現可各異。

在倉儲層定義幾個公共的方法,

 public interface IRepository<TEntity,TID> where TEntity : IEntity<TID>
 {
     Task<ApiResult> AddAsync(TEntity entity);
     Task<ApiResult> UpdateAsync(TEntity entity);

     Task<ApiResult> DeleteAsync(Expression<Func<TEntity, bool>> filter);
     Task<ApiResult> DeleteAsync(TID id);
     // 通用分頁查詢
     Task<PagedResult<TEntity>> GetPagedAsync(PagingParameters<TEntity> pagingParameters);

     // 其他常用操作
     Task<IEnumerable<TEntity>> FindAsync(Expression<Func<TEntity, bool>> filter);

 }

服務層也是同樣的方法

 [Intercept("business-service log")]
 public interface IService
 {
    Task<ApiResult> AddAsync(IRequestDto requestDto);
    Task<ApiResult> UpdateAsync(IRequestDto requestDto);
    Task<ApiResult> DeleteAsyncc(IRequestDto requestDto);
    Task<ApiResult> GetPagedAsyncc(IRequestDto requestDto) ;
    Task<ApiResult> FindAsyncc(IRequestDto requestDto);
 }

依賴注入還是老一套,實現它就行。

 public interface IModule
 {
     void ConfigureService(IServiceCollection services, IConfiguration configuration = null);
     void Configure(IApplicationBuilder app, IWebHostEnvironment env = null);
 }

 public abstract class ModuleBase : IModule
 {
     public virtual void ConfigureService(IServiceCollection services, IConfiguration configuration = null)
     {
     }

     public virtual void Configure(IApplicationBuilder app, IWebHostEnvironment env = null)
     {
     }
 }

在主機透過掃描assembly來註冊服務

using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Project.Base.Reflect;
using System.Reflection;
using Project.Base.ProjExtension;
using Project.Base.Common;
using Project.Base.DependencyInjection;
using Project.Base.Module;
namespace Project.Host
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);
            builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
            builder.Configuration.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true);
            builder.Configuration.AddJsonFile("appsettings.Modules.json", optional: false, reloadOnChange: true);
            //IModule注入 ,然後掃描呼叫ConfigureService,Business注入需要的服務入口
            builder.Services.InitModule(builder.Configuration);
            var sp = builder.Services.BuildServiceProvider();
            var moduleInitializers = sp.GetServices<IModule>();
            foreach (var moduleInitializer in moduleInitializers)
            {
                moduleInitializer.ConfigureService(builder.Services, builder.Configuration);
            }
            // Add services to the container.
            var assemblys = GolbalConfiguration.Modules.Select(x => x.Assembly).ToList();


            var mvcBuilder=builder.Services.AddControllers().ConfigureApplicationPartManager(apm => {

                var folder = Path.Combine(Directory.GetCurrentDirectory(), "bus_lib");
                var serviceList = (builder.Configuration.GetSection("ServiceList").Get<string[]>()) ?? new string[] { "ADM" };//預設載入基礎服務
                string[] serviceFiles = Directory.GetFiles(folder, "*.Api.dll").Where(x =>
                    serviceList.Any(y => x.Contains(y))
                ).ToArray();

                foreach (var file in serviceFiles)
                {
                    if (File.Exists(file))
                    {
                        var assembly = Assembly.LoadFrom(file);
                        var controllerAssemblyPart = new AssemblyPart(assembly);
                        apm.ApplicationParts.Add(controllerAssemblyPart);
                    }
                }
            });
            foreach (var assembly in assemblys)
            {
                // 掃描並註冊其他程式集中的控制器
                mvcBuilder.AddApplicationPart(assembly);
                // 掃描並註冊其他程式集中的服務   針對特性注入
                builder.Services.ReisterServiceFromAssembly(assembly);
            } 

            
            // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();
            builder.Services.AddBusinessServices();

            var app = builder.Build();
            ServiceLocator.Instance = app.Services;

            //imodule 的Configure呼叫,business可以實現中介軟體等操作
            foreach (var moduleInitializer in moduleInitializers)
            {
                moduleInitializer.Configure(app, app.Environment);
            }
            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }

            app.UseHttpsRedirection();

            app.UseAuthorization();


            app.MapControllers();

            app.Run();
        }
    }
}

業務需求注入程式碼如下:

using ADM001_User.Model;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using MongoDB.Bson.Serialization.Serializers;
using MongoDB.Bson.Serialization;
using MongoDB.Bson;
using MongoDB.Driver;
using Project.Base.IRepository;
using Project.Base.Module;
using Project.Base.Reflect;
using Project.Base.Repository;
using Project.Base.Services;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ADM001_User.Business.Settings;
using Project.Base.Model;

namespace ADM001_User.Business
{
    public class UserModule : ModuleBase
    {

        public override void ConfigureService(IServiceCollection services, IConfiguration configuration = null)
        {
            services.AddDbContext<UserDbContext>(options =>
       options.UseInMemoryDatabase("InMemoryDb"));

            services.AddScoped<IRepository<User, int>, GenericRepository<User, int, UserDbContext>>();
            services.AddTransient<IService, UserService>();

            AddMongo(services);
            AddMongoRepository<User, int>(services, "users");

        }


        private static IServiceCollection AddMongo(IServiceCollection services)
        {
            BsonSerializer.RegisterSerializer(new GuidSerializer(BsonType.String));
            BsonSerializer.RegisterSerializer(new DateTimeOffsetSerializer(BsonType.String));
            services.AddSingleton(serviceProvider =>
            {
                var configuration = serviceProvider.GetService<IConfiguration>();
                var serviceSettings = configuration.GetSection(nameof(ServiceSettings)).Get<ServiceSettings>();
                var mongoDbSettings = configuration.GetSection(nameof(MongoDbSettings)).Get<MongoDbSettings>();
                var mongoClient = new MongoClient(mongoDbSettings.ConenctionString);
                return mongoClient.GetDatabase(serviceSettings.ServiceName);
            });
            return services;
        }
        private static IServiceCollection AddMongoRepository<T, TID>(IServiceCollection services, string collectionName) where T : IEntity<TID>
        {
            services.AddSingleton<IRepository<User, int>>(serviceProvider =>
            {
                var db = serviceProvider.GetService<IMongoDatabase>();
                return new MongoRepository<User, int>(db, "collectionname");
            });
            return services;
        }
    }
}

在business層加了aop,透過proxy的方式

using Castle.DynamicProxy;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace Project.Base.Reflect
{
    public static class ServiceExtension
    {
        private static readonly ProxyGenerator _generator = new ProxyGenerator();
        public static IServiceCollection AddBusinessServices(this IServiceCollection services)
        {
            var folder = Path.Combine(Directory.GetCurrentDirectory(), "bus_lib");

            var dllFiles = Directory.GetFiles(folder, "*.Business.dll");

            var assemblies = dllFiles.Select(Assembly.LoadFrom).ToArray();

            var businessTypes = assemblies.SelectMany(a => a.GetTypes().Where(t => t.IsClass&&!t.IsAbstract)).Where(type => type.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IBusiness<>))).ToList();
            CastleInterceptor castleInterceptor = new CastleInterceptor();

            foreach (var type in businessTypes)
            {
                var interfaceType = type.GetInterfaces().First(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IBusiness<>));
                services.AddTransient(interfaceType, provider =>
                {
                    var target = ActivatorUtilities.CreateInstance(provider, type);
                    return _generator.CreateInterfaceProxyWithTarget(interfaceType, target, castleInterceptor);
                });
            }

            return services;
        }
    }
}

在你需要的每個方法前加上特性就可以了

using Project.Base.Model;
using Project.Base.ProjAttribute;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ADM001_User.Business
{
    /// <summary>
    /// 有需要就實現前後動作
    /// </summary>
    public class AddAop: BaseAopAttribute
    {
        public override Task After(BusinessAopContext aopContext)
        {
            return Task.CompletedTask;
        }

        public override Task Before(BusinessAopContext aopContext)
        {
            return Task.CompletedTask;
        }
    }
}

再控制器層加了個公共的,不管是controller攔截還是公共的部分都可以寫到這裡

 [Route("api/[controller]/[action]")]
 [ApiController]
 public class InitController<TModel>:ControllerBase
 {
     protected readonly ILogger<InitController<TModel>> _logger;
      public InitController(ILogger<InitController<TModel>> logger)
     {
         _logger = logger;

     }
   
 }

該框架主打就是一個簡陋,像日誌,快取 ,訊息中介軟體都可以提前約定好公共介面,service層介面呼叫,business層注入需要的實現。按照介面和實現分離的方式該專案還需要調整下目錄

地址如下:

liuzhixin405/single-arch (github.com)

相關文章