使用 Source Generator 自动生成 WEB API
使用 Source Generator 自動生成 WEB API
Intro
上次我們介紹了使用 Source Generator 的應(yīng)用,有小伙伴留言說想要自動生成一套 ABP 相關(guān)的東西,我對 ABP 不怎么熟悉,所以寫了一個簡單版的雛形,可以根據(jù)自定義的模板去動態(tài)生成,有需要的可以參考一下
Generator 示例
[Generator] public?class?ControllersGenerator?:?ISourceGenerator {public?void?Initialize(GeneratorInitializationContext?context){}public?void?Execute(GeneratorExecutionContext?context){var?appDbContextType?=?context.Compilation.GetTypeByMetadataName("AspNetCoreWebDemo.Models.AppDbContext");//?從編譯信息中獲取?DbSet<>?類型var?dbContextType?=?context.Compilation.GetTypeByMetadataName(typeof(DbSet<>).FullName);//?獲取?DbContext?中的?DbSet<>?屬性var?propertySymbols?=?appDbContextType.GetMembers().OfType<IMethodSymbol>().Where(x?=>?x.MethodKind?==?MethodKind.PropertyGet&&?x.ReturnType?is?INamedTypeSymbol{IsGenericType:?true,IsUnboundGenericType:?false,}?typeSymbol&&?ReferenceEquals(typeSymbol.ConstructedFrom.ContainingAssembly,?dbContextType.ContainingAssembly)).ToArray();var?propertyReturnType?=?propertySymbols.Select(r?=>Tuple.Create((INamedTypeSymbol)r.ReturnType,?r.Name.Replace("get_",?"",?StringComparison.OrdinalIgnoreCase))).ToArray();(string?typeName,?string?propertyName)[]?models?=?propertyReturnType.Select(t?=>?(t.Item1.TypeArguments.First().Name,?t.Item2)).ToArray();//Debugger.Launch();foreach?(var?additionalFile?in?context.AdditionalFiles){var?templateName?=?Path.GetFileNameWithoutExtension(additionalFile.Path).Replace("Template",?"");var?template?=?additionalFile.GetText(context.CancellationToken);if?(template?is?not?null){foreach?(var?(typeName,?propertyName)?in?models){var?code?=?template.ToString().Replace("{PropertyName}",?propertyName,?StringComparison.OrdinalIgnoreCase).Replace("{TypeName}",?typeName,?StringComparison.OrdinalIgnoreCase);context.AddSource($"{propertyName}{templateName}",?code);}}}} }上面的示例通過 AdditionalFiles 來獲取要生成的代碼模板,替換模板內(nèi)的部分 Placeholder,最終生成期望的代碼,而且模板可以是多個模板,也就會生成 N 多份內(nèi)容
Template 模板
Service 模板
Service 模板和上一篇文章中生成的代碼基本是一樣的,只是命令空間不同
上一篇文章中是在代碼里寫死的代碼,而現(xiàn)在我們則是把相同的部分抽出來獨立成一個模板,利用模板來動態(tài)生成最終的代碼,這樣的好處在于,我們最終可以在項目里配置而不是直接寫死在 Generator 代碼里,會更加的靈活,修改起來也比較方便
using?AspNetCoreWebDemo.Models; using?WeihanLi.EntityFramework;namespace?AspNetCoreWebDemo.Business {public?partial?interface?I{PropertyName}Service:?IEFRepository<AppDbContext,?{TypeName}>{}public?partial?class?{PropertyName}Service:?EFRepository<AppDbContext,?{TypeName}>,??I{PropertyName}Service{public?{PropertyName}Service(AppDbContext?dbContext)?:?base(dbContext){}} }Controller 模板
Controller 模板就是我們要來生成 API 的模板,實現(xiàn)了比較簡單的增刪改查,而且聲明的類型是分部類(partial) 這樣我們就可以比較方便進行擴展,在同一個類增加自定義的邏輯
using?AspNetCoreWebDemo.Business; using?AspNetCoreWebDemo.Models; using?Microsoft.AspNetCore.Mvc; using?System.Threading.Tasks; using?WeihanLi.Common.Helpers; using?WeihanLi.Common.Models; using?WeihanLi.EntityFramework;namespace?AspNetCoreWebDemo.Controllers {[Route("api/[controller]")]public?partial?class?{PropertyName}Controller?:?ControllerBase{private?readonly?I{PropertyName}Service?_service;public?{PropertyName}Controller(I{PropertyName}Service?service){_service?=?service;}[HttpGet]public?Task<IPagedListResult<{TypeName}>>?List([FromQuery]?PagedRequest?request){return?_service.PagedAsync(request.PageNum,?request.PageSize,?ExpressionHelper.True<{TypeName}>(),?x?=>?x.Id,?false,HttpContext.RequestAborted);}[HttpGet("{id}")]public?Task<{TypeName}>?Details(int?id){return?_service.FetchAsync(x?=>?x.Id?==?id,HttpContext.RequestAborted);}[HttpPost]public?async?Task<{TypeName}>?Create([FromBody]?{TypeName}?model){var?result?=?await?_service.InsertAsync(model);model.Id?=?result;return?model;}[HttpPut("{id}")]public?async?Task<{TypeName}>?Update(int?id,?[FromBody]?{TypeName}?model){model.Id?=?id;var?result?=?await?_service.UpdateWithoutAsync(model,?x?=>?x.Id);return?model;}[HttpDelete("{id}")]public?async?Task<ActionResult<{TypeName}>>?Delete(int?id){var?t?=?await?_service.FetchAsync(x?=>?x.Id?==?id);if?(t?is?null){return?NotFound();}await?_service.DeleteAsync(x?=>?x.Id?==?id);return?t;}} }Project Structure
上面是 Generator 的一些介紹,接著我們來看一個使用這個 Genertor 的項目結(jié)構(gòu)
項目里只有一個 DbContext 和 Models,以及我們要自定義的模板
UsersController 這里是為了測試分部類的自定義 controller 補充邏輯,UserController 文件的內(nèi)容如下:
Generated Files
根據(jù) Controller 模板生成的 controller 文件如下:
using?AspNetCoreWebDemo.Business; using?AspNetCoreWebDemo.Models; using?Microsoft.AspNetCore.Mvc; using?System.Threading.Tasks; using?WeihanLi.Common.Helpers; using?WeihanLi.Common.Models; using?WeihanLi.EntityFramework;namespace?AspNetCoreWebDemo.Controllers {[Route("api/[controller]")]public?partial?class?UsersController?:?ControllerBase{private?readonly?IUsersService?_service;public?UsersController(IUsersService?service){_service?=?service;}[HttpGet]public?Task<IPagedListResult<User>>?List([FromQuery]?PagedRequest?request){return?_service.PagedAsync(request.PageNum,?request.PageSize,?ExpressionHelper.True<User>(),?x?=>?x.Id,?false,HttpContext.RequestAborted);}[HttpGet("{id}")]public?Task<User>?Details(int?id){return?_service.FetchAsync(x?=>?x.Id?==?id,HttpContext.RequestAborted);}[HttpPost]public?async?Task<User>?Create([FromBody]?User?model){var?result?=?await?_service.InsertAsync(model);model.Id?=?result;return?model;}[HttpPut("{id}")]public?async?Task<User>?Update(int?id,?[FromBody]?User?model){model.Id?=?id;var?result?=?await?_service.UpdateWithoutAsync(model,?x?=>?x.Id);return?model;}[HttpDelete("{id}")]public?async?Task<ActionResult<User>>?Delete(int?id){var?t?=?await?_service.FetchAsync(x?=>?x.Id?==?id);if?(t?is?null){return?NotFound();}await?_service.DeleteAsync(x?=>?x.Id?==?id);return?t;}} }Configure
來看一下 Startup 的配置吧,Startup 中我只配置了 DbContext 和 注冊服務(wù)配置,詳細如下:
public?class?Startup {public?Startup(IConfiguration?configuration){Configuration?=?configuration;}public?IConfiguration?Configuration?{?get;?}public?void?ConfigureServices(IServiceCollection?services){services.AddDbContext<AppDbContext>(options?=>options.UseInMemoryDatabase("Test"));services.RegisterAssemblyTypesAsImplementedInterfaces(x?=>?x.Name.EndsWith("Service"),?ServiceLifetime.Scoped,?typeof(Startup).Assembly);services.AddControllers();}public?void?Configure(IApplicationBuilder?app,?IWebHostEnvironment?env){app.UseRouting();app.UseEndpoints(endpoints?=>{endpoints.MapControllers();});//?數(shù)據(jù)初始化using?var?scope?=?app.ApplicationServices.CreateScope();var?dbContext?=?scope.ServiceProvider.GetRequiredService<AppDbContext>();dbContext.Users.Add(new?User()?{?Id?=?1,?Age?=?10,?Name?=?"Xiao?Ming",?});dbContext.SaveChanges();} }Run
dotnet run 運行一下看一下效果吧:
首先訪問 /api/users,輸出結(jié)果如下
訪問 /api/users/1
以上兩個 endpoint 都是自動生成的,我們再訪問 /api/users/test 來測試一下分部類中的接口
可以看到,無論是自動生成的代碼還是分部類中自定義的接口都是工作的了
More
如果想要進行更多的自定義,只需要根據(jù)需要調(diào)整 Generator 獲取生成時所需的類型等信息,再調(diào)整相應(yīng)的模板即可,模板通過 AdditionalFiles 來訪問,包含進項目中,使用方式如下:
<AdditionalFiles?Include="Templates/*.txt"?/>模板可以增加多個,可以根據(jù)自己的項目需要進行定制,定義好自己的模板,自動生成一套 ABP 相關(guān)的代碼我覺得也是可以實現(xiàn)的,感興趣的可以試試看
References
https://github.com/WeihanLi/SamplesInPractice/tree/master/SourceGeneratorSample/AspNetCoreWebDemo
https://github.com/WeihanLi/SamplesInPractice/blob/master/SourceGeneratorSample/Generators/ControllersGenerator.cs
使用 Source Generator 代替 T4 動態(tài)生成代碼
C# 強大的新特性 Source Generator
總結(jié)
以上是生活随笔為你收集整理的使用 Source Generator 自动生成 WEB API的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: NET问答: 如何将 DataTable
- 下一篇: .NET5 开发手机提词应用,基于内嵌W