當前位置: 妍妍網 > 碼農

.NET 6 WebApplication 打造最小API

2024-05-15碼農

前言

. NET 6 在 Preview4 時給我們帶來了一個新的 API: WebApplication,透過這個API我們可以打造更小的輕量級API服務。

我們來嘗試一下如何使用WebApplication設計一個小型API服務系統。

環境準備

.NET SDK v6.0.0-preview.6.21355.2

Visual Studio 2022 Preview

首先看看原始版本的WebApplication,官方已經提供了樣例樣版,開啟我們的 VS 2022,選擇新建計畫選擇ASP.NET Core empty,Framework選擇.NET 6.0 (preview)點選建立,即可生成一個簡單的最小程式碼範例:

如果我們在.csproj裏在配置節PropertyGroup增加使用C#10新語法讓自動進行型別推斷來隱式的轉換成委托,則可以更加精簡:

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>preview</LangVersion>
</PropertyGroup>

當然僅僅是這樣,是無法用於生產的,畢竟不可能所有的業務單元我們塞進這麽一個小小的運算式裏。不過借助WebApplication我們可以打造一個輕量級的系統,可以滿足基本的依賴註入的小型服務。比如透過自訂特性型別,在啟動階段告知系統為哪些服務註入哪些存取路徑,形成路由鍵和終結點。具體程式碼如下:

首先我們建立一個簡易的特性類,只包含httpmethod和path:

[AttributeUsage(AttributeTargets.Method)]
public classWebRouter : Attribute
{
publicstring path;
public HttpMethod httpMethod;
publicWebRouter(string path)
{
this.path = path;
this.httpMethod = HttpMethod.Post;
}
publicWebRouter(string path, HttpMethod httpMethod)
{
this.path = path;
this.httpMethod = httpMethod;
}
}

接著我們按照一般的分層設計一套DEMO套用層/倉儲服務:

publicinterfaceIMyService
{
Task<MyOutput> Hello(MyInput input);
}
publicinterfaceIMyRepository
{
Task<boolSaveData(MyOutput data);
}
public classMyService : IMyService
{
privatereadonly IMyRepository myRepository;
publicMyService(IMyRepository myRepository)
{
this.myRepository = myRepository;
}
[WebRouter("/", HttpMethod.Post)]
publicasync Task<MyOutput> Hello(MyInput input)
{
var result = new MyOutput() { Words = $"hello {input.Name ?? "nobody"}" };
await myRepository.SaveData(result);
returnawait Task.FromResult(result);
}
}
public classMyRepository : IMyRepository
{
publicasync Task<boolSaveData(MyOutput data)
{
Console.WriteLine($"保存成功:{data.Words}");
returnawait Task.FromResult(true);
}
}

最後我們需要將我們的服務接入到WebApplication的map裏,怎麽做呢?首先我們需要定義一套代理型別用來反射並獲取到具體的服務型別。這裏為了簡單的演示,我只設計包含一個入參和沒有入參的情況下:

publicabstract classDynamicPorxy
{
publicabstract Delegate Instance { getset; }
}
public classDynamicPorxyImpl<TsvcTimplTinputToutput> : DynamicPorxywhereTimpl :  classwhereTinput :  classwhereToutput :  class
{
publicoverride Delegate Instance { getset; }
publicDynamicPorxyImpl(MethodInfo method)
{
Instance = ([FromServices] IServiceProvider sp, Tinput input) => ExpressionTool.CreateMethodDelegate<Timpl, Tinput, Toutput>(method)(sp.GetService(typeof(Tsvc)) as Timpl, input);
}
}
public classDynamicPorxyImpl<TsvcTimplToutput> : DynamicPorxywhereTimpl :  classwhereToutput :  class
{
publicoverride Delegate Instance { getset; }
publicDynamicPorxyImpl(MethodInfo method)
{
Instance = ([FromServices] IServiceProvider sp) => ExpressionTool.CreateMethodDelegate<Timpl, Toutput>(method)(sp.GetService(typeof(Tsvc)) as Timpl);
}
}

接著我們建立一個代理工廠用於建立服務的方法委托並建立代理型別例項返回給呼叫端

public classDynamicPorxyFactory
{
publicstatic IEnumerable<(WebRouter, DynamicPorxy)> RegisterDynamicPorxy()
{
foreach (var methodinfo in DependencyContext.Default.CompileLibraries.Where(x => !x.Serviceable && x.Type != "package")
.Select(x => AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(x.Name)))
.SelectMany(x => x.GetTypes().Where(x => !x.IsInterface && x.GetInterfaces().Any()).SelectMany(x => x.GetMethods().Where(y => y.CustomAttributes.Any(z => z.AttributeType == typeof(WebRouter))))))
{
var webRouter = methodinfo.GetCustomAttributes(typeof(WebRouter), false).FirstOrDefault() as WebRouter;
DynamicPorxy dynamicPorxy;
if (methodinfo.GetParameters().Any())
dynamicPorxy = Activator.CreateInstance(typeof(DynamicPorxyImpl<,,,>).MakeGenericType(methodinfo.DeclaringType.GetInterfaces()[0], methodinfo.DeclaringType, methodinfo.GetParameters()[0].ParameterType , methodinfo.ReturnType), newobject[] { methodinfo }) as DynamicPorxy;
else
dynamicPorxy = Activator.CreateInstance(typeof(DynamicPorxyImpl<,,>).MakeGenericType(methodinfo.DeclaringType.GetInterfaces()[0], methodinfo.DeclaringType, methodinfo.ReturnType), newobject[] { methodinfo }) as DynamicPorxy;
yieldreturn (webRouter, dynamicPorxy);
}
}
}

internal classExpressionTool
{
internalstatic Func<TObj, Tin, Tout> CreateMethodDelegate<TObj, Tin, Tout>(MethodInfo method)
{
var mParameter = Expression.Parameter(typeof(TObj), "m");
var pParameter = Expression.Parameter(typeof(Tin), "p");
var mcExpression = Expression.Call(mParameter, method, Expression.Convert(pParameter, typeof(Tin)));
var reExpression = Expression.Convert(mcExpression, typeof(Tout));
return Expression.Lambda<Func<TObj, Tin, Tout>>(reExpression, mParameter, pParameter).Compile();
}
internalstatic Func<TObj, Tout> CreateMethodDelegate<TObj, Tout>(MethodInfo method)
{
var mParameter = Expression.Parameter(typeof(TObj), "m");
var mcExpression = Expression.Call(mParameter, method);
var reExpression = Expression.Convert(mcExpression, typeof(Tout));
return Expression.Lambda<Func<TObj, Tout>>(reExpression, mParameter).Compile();
}
}

最後我們建立WebApplication的擴充套件方法來呼叫代理工廠以及註入IOC容器:

publicstatic classWebApplicationBuilderExtension
{
static Func<string, Delegate, IEndpointConventionBuilder> GetWebApplicationMap(HttpMethod httpMethod, WebApplication webApplication) => (httpMethod) switch
{
(HttpMethod.Get) => webApplication.MapGet,
(HttpMethod.Post) => webApplication.MapPost,
(HttpMethod.Put) => webApplication.MapPut,
(HttpMethod.Delete) => webApplication.MapDelete,
_ => webApplication.MapGet
};
publicstatic WebApplication RegisterDependencyAndMapDelegate(this WebApplicationBuilder webApplicationBuilder, Action<IServiceCollection> registerDependencyAction, Func<IEnumerable<(WebRouter webRouter, DynamicPorxy dynamicPorxy)>> mapProxyBuilder)
{
webApplicationBuilder.Host.ConfigureServices((ctx, services) =>
{
registerDependencyAction(services);
});
var webApplication = webApplicationBuilder.Build();
mapProxyBuilder().ToList().ForEach(item => GetWebApplicationMap(item.webRouter.httpMethod, webApplication)(item.webRouter.path, item.dynamicPorxy.Instance));
return webApplication;
}
}

當然包括我們的自訂容器註入方法:

public classMyServiceDependency
{
publicstaticvoidRegister(IServiceCollection services)
{
services.AddScoped<IMyService, MyService>();
services.AddScoped<IMyRepository, MyRepository>();
}
}

最後改造我們的program.cs的程式碼,透過擴充套件來註入容器和代理委托並最終生成路由-終結點:

await WebApplication.CreateBuilder().RegisterDependencyAndMapDelegate(MyServiceDependency.Register,DynamicPorxyFactory.RegisterDynamicPorxy).RunAsync("http://*:80");

這樣這套小型API系統就基本完成了,可以滿足日常的依賴註入和獨立的業務單元型別編寫,最後我們啟動並呼叫一下,可以看到確實否符合我們的預期成功的呼叫到了套用服務並且倉儲也被正確的執行了:

轉自:a1010

連結:cnblogs.com/gmmy/p/14990077.html