增量生成器简化BlazorServer兼容BlazorAuto模式
使用增量生成器简化BlazorServer兼容Auto模式
文末有生成代码示例
啰里八嗦
Blazor作为.Net生态的一个前端框架,有多种开发模式,在初期,有Blazor Server和Blazor WebAssembly两种模式,各自的优点和缺点都很明显。后来,随着.Net8一起而来的还有Blazor Auto模式,它可以说是结合了Server和WASM的优点,但是对于习惯了Server写法的我来说,讲项目改造成Auto模式,工作量却是大且无聊的,那么有没有一种方式可以让我在写Server模式的同时,无感地实现Auto呢(当然只是一种夸张的说法,虽然大部分情况是可行的,但实际开发Server的时候肯定还要考虑一下WASM的情况是否可行)
主角登场
AutoWasmApiGenerator
是一个增量生成器,用于生成BlazorServer项目的WebApi接口,以便在BlazorAuto模式下使用
比如现在有一个BlazorServer项目的Razor页面
// UserIndex.razor
@code {
[Inject, NotNull] IUserService? Service { get; set; }
}
如果IUserService
的实现不支持运行在WebAssembly
,比如连接数据库,或者访问服务器文件等等,那么这种情况下,需要Server端提供WebApi,并且在Client端提供IUserService
的接口调用实现,本文的目的就是通过增量生成器,完成Server端WebApi生成和Client端的接口调用
使用
项目结构
Server
端项目Client
端项目Shared
双端共用的项目,一般是共用的页面、实体模型、等等
使用步骤
- 在Shared项目中引用AutoWasmApiGenerator项目
<PackageReference Include="AutoWasmApiGenerator" Version="*" />
- 在Shared或Client项目中添加接口
[WebController(Authorize = true)]
[ApiInvokerGenerate]
public interface IHelloService
{
Task<string> SayHelloAsync(string name);
}
- 在Server项目中任意一个文件标注
WebControllerAssemblyAttribute
, 生成的文件为TestServiceController
[assembly: AutoWasmApiGenerator.WebControllerAssembly]
- 在Client项目中或生成控制器调用类的项目中的任意一个文件标注
ApiInvokerAssemblyAttribute
, 生成的文件为TestServiceApiInvoker
[assembly: AutoWasmApiGenerator.ApiInvokerAssembly]
- 在Client项目中注册
builder.Services.AddScoped<ITestService, TestServiceApiInvoker>();
- 通过
ITestService
调用服务
@code
{
[Inject] public ITestService TestService { get; set; }
private async Task LogAsync()
{
await TestService.LogAsync("Hello World", "path", CancellationToken.None);
}
}
相关特性介绍
WebControllerAttribute
标注服务接口,根据接口生成控制器
ApiInvokerGenerateAttribute
标注服务接口,根据接口生成调用类
ApiInvokeNotSupportedAttribute
标注方法,指示不生成该方法对应的接口,调用该接口时,抛出异常
WebMethodAttribute
标注方法,指定请求方式
[WebMethod(Method = WebMethod.Post)]
Task<bool> LogAsync(string message);
属性
名称 | 类型 | 说明 |
---|---|---|
Method | [WebMethod] | 指定请求方法,默认为Post |
Route | string? | 指定Action路由,null时为方法名称 |
AllowAnonymous | bool | 是否支持匿名访问,会覆盖Authorize设置 |
Authorize | bool | 是否需要授权 |
WebMethod
可能的值
- Get
- Post
- Put
- Delete
WebMethodParameterBindingAttribute
标注参数,指定参数绑定方式
[WebMethod(Method = WebMethod.Post)]
Task<bool> Log3Async([WebMethodParameterBinding(BindingType.FromBody)] string message, [WebMethodParameterBinding(BindingType.FromQuery)] string path,[WebMethodParameterBinding(BindingType.Ignore)] CancellationToken token);
属性
名称 | 类型 | 说明 |
---|---|---|
Type | [BindingType ] | 参数绑定类型 |
BindingType
- Ignore 忽略
- FromQuery 从查询字符串中获取值。
- FromRoute 从路由数据中获取值。
- FromForm 从发布的表单域中获取值。
- FromBody 从请求正文中获取值。
- FromHeader 从 HTTP 标头中获取值。
- FromServices 从服务容器中获取值。
生成代码示例
接口定义
[WebController(Authorize = true)]
[ApiInvokerGenerate]
public interface IHelloService
{
[ApiInvokeNotSupported]
Task<int> NotSupport();
Task<string> SayHelloAsync(string name);
Task<int> TestHeaderParameter([WebMethodParameterBinding(BindingType.FromHeader)] string name);
Task<int> TestQueryParameter([WebMethodParameterBinding(BindingType.FromQuery)] string name);
[WebMethod(Route = "{name}")]
Task<int> TestRouterParameter([WebMethodParameterBinding(BindingType.FromRoute)] string name);
Task<int> TestFormParameter([WebMethodParameterBinding(BindingType.FromForm)] string name);
[WebMethod(Route = "{id}")]
Task<string> TestMultiParameter([WebMethodParameterBinding(BindingType.FromRoute)] int id
, [WebMethodParameterBinding(BindingType.FromQuery)] string name);
Task<string> TestQueryAndBodyParameter([WebMethodParameterBinding(BindingType.FromQuery)] int id
, [WebMethodParameterBinding(BindingType.FromBody)] RequestTest body);
}
WebController
// <auto-generated/>
#pragma warning disable
#nullable enable
namespace Blazor.Test.Client.Services
{
[global::Microsoft.AspNetCore.Mvc.ApiController]
[global::Microsoft.AspNetCore.Mvc.Route("api/[controller]")]
[global::Microsoft.AspNetCore.Authorization.Authorize]
[global::System.CodeDom.Compiler.GeneratedCode("AutoWasmApiGenerator.ControllerGenerator", "0.0.8.0")]
/// <inheritdoc/>
public class HelloServiceController : global::Microsoft.AspNetCore.Mvc.ControllerBase
{
private readonly Blazor.Test.Client.Services.IHelloService proxyService;
public HelloServiceController(Blazor.Test.Client.Services.IHelloService service)
{
proxyService = service;
}
[global::Microsoft.AspNetCore.Mvc.HttpPost("SayHello")]
[global::Microsoft.AspNetCore.Authorization.Authorize]
[global::System.CodeDom.Compiler.GeneratedCode("AutoWasmApiGenerator.ControllerGenerator", "0.0.8.0")]
public System.Threading.Tasks.Task<string> SayHelloAsync([global::Microsoft.AspNetCore.Mvc.FromBody]string name)
=> proxyService.SayHelloAsync(name);
[global::Microsoft.AspNetCore.Mvc.HttpPost("TestHeaderParameter")]
[global::Microsoft.AspNetCore.Authorization.Authorize]
[global::System.CodeDom.Compiler.GeneratedCode("AutoWasmApiGenerator.ControllerGenerator", "0.0.8.0")]
public System.Threading.Tasks.Task<int> TestHeaderParameter([global::Microsoft.AspNetCore.Mvc.FromHeader]string name)
=> proxyService.TestHeaderParameter(name);
[global::Microsoft.AspNetCore.Mvc.HttpPost("TestQueryParameter")]
[global::Microsoft.AspNetCore.Authorization.Authorize]
[global::System.CodeDom.Compiler.GeneratedCode("AutoWasmApiGenerator.ControllerGenerator", "0.0.8.0")]
public System.Threading.Tasks.Task<int> TestQueryParameter([global::Microsoft.AspNetCore.Mvc.FromQuery]string name)
=> proxyService.TestQueryParameter(name);
[global::Microsoft.AspNetCore.Mvc.HttpPost("TestRouterParameter/{name}")]
[global::Microsoft.AspNetCore.Authorization.Authorize]
[global::System.CodeDom.Compiler.GeneratedCode("AutoWasmApiGenerator.ControllerGenerator", "0.0.8.0")]
public System.Threading.Tasks.Task<int> TestRouterParameter([global::Microsoft.AspNetCore.Mvc.FromRoute]string name)
=> proxyService.TestRouterParameter(name);
[global::Microsoft.AspNetCore.Mvc.HttpPost("TestFormParameter")]
[global::Microsoft.AspNetCore.Authorization.Authorize]
[global::System.CodeDom.Compiler.GeneratedCode("AutoWasmApiGenerator.ControllerGenerator", "0.0.8.0")]
public System.Threading.Tasks.Task<int> TestFormParameter([global::Microsoft.AspNetCore.Mvc.FromForm]string name)
=> proxyService.TestFormParameter(name);
[global::Microsoft.AspNetCore.Mvc.HttpPost("TestMultiParameter/{id}")]
[global::Microsoft.AspNetCore.Authorization.Authorize]
[global::System.CodeDom.Compiler.GeneratedCode("AutoWasmApiGenerator.ControllerGenerator", "0.0.8.0")]
public System.Threading.Tasks.Task<string> TestMultiParameter([global::Microsoft.AspNetCore.Mvc.FromRoute]int id, [global::Microsoft.AspNetCore.Mvc.FromQuery]string name)
=> proxyService.TestMultiParameter(id, name);
[global::Microsoft.AspNetCore.Mvc.HttpPost("TestQueryAndBodyParameter")]
[global::Microsoft.AspNetCore.Authorization.Authorize]
[global::System.CodeDom.Compiler.GeneratedCode("AutoWasmApiGenerator.ControllerGenerator", "0.0.8.0")]
public System.Threading.Tasks.Task<string> TestQueryAndBodyParameter([global::Microsoft.AspNetCore.Mvc.FromQuery]int id, [global::Microsoft.AspNetCore.Mvc.FromBody]Blazor.Test.Client.Services.RequestTest body)
=> proxyService.TestQueryAndBodyParameter(id, body);
}
}
接口调用类
using Microsoft.Extensions.DependencyInjection;
// <auto-generated/>
#pragma warning disable
#nullable enable
namespace Blazor.Test.Client.Services
{
[global::System.CodeDom.Compiler.GeneratedCode("AutoWasmApiGenerator.HttpServiceInvokerGenerator", "0.0.8.0")]
/// <inheritdoc/>
public partial class HelloServiceApiInvoker : Blazor.Test.Client.Services.IHelloService
{
private readonly global::System.Text.Json.JsonSerializerOptions _JSON_OPTIONS_gen;
private readonly global::System.Net.Http.IHttpClientFactory clientFactory;
private readonly global::AutoWasmApiGenerator.IHttpClientHeaderHandler headerHandler;
public HelloServiceApiInvoker(global::System.Net.Http.IHttpClientFactory factory, global::System.IServiceProvider services)
{
clientFactory = factory;
headerHandler = services.GetService<global::AutoWasmApiGenerator.IHttpClientHeaderHandler>() ?? global::AutoWasmApiGenerator.DefaultHttpClientHeaderHandler.Default;
_JSON_OPTIONS_gen = new global::System.Text.Json.JsonSerializerOptions() { PropertyNameCaseInsensitive = true };
}
[global::System.CodeDom.Compiler.GeneratedCode("AutoWasmApiGenerator.HttpServiceInvokerGenerator", "0.0.8.0")]
public System.Threading.Tasks.Task<int> NotSupport()
=> throw new global::System.NotSupportedException();
[global::System.CodeDom.Compiler.GeneratedCode("AutoWasmApiGenerator.HttpServiceInvokerGenerator", "0.0.8.0")]
public async System.Threading.Tasks.Task<string> SayHelloAsync(string name)
{
var _client_gen = this.clientFactory.CreateClient("HelloService");
var _request_gen = new global::System.Net.Http.HttpRequestMessage();
_request_gen.Method = global::System.Net.Http.HttpMethod.Post;
await headerHandler.SetRequestHeaderAsync(_request_gen);
var _url_gen = $"api/HelloService/SayHello";
var _json_gen = global::System.Text.Json.JsonSerializer.Serialize(name);
_request_gen.Content = new StringContent(_json_gen, global::System.Text.Encoding.Default, "application/json");
_request_gen.RequestUri = new Uri(_url_gen, UriKind.Relative);
var _response_gen = await _client_gen.SendAsync(_request_gen);
_response_gen.EnsureSuccessStatusCode();
var _str_gen = await _response_gen.Content.ReadAsStringAsync();
return _str_gen;
}
[global::System.CodeDom.Compiler.GeneratedCode("AutoWasmApiGenerator.HttpServiceInvokerGenerator", "0.0.8.0")]
public async System.Threading.Tasks.Task<int> TestHeaderParameter(string name)
{
var _client_gen = this.clientFactory.CreateClient("HelloService");
var _request_gen = new global::System.Net.Http.HttpRequestMessage();
_request_gen.Method = global::System.Net.Http.HttpMethod.Post;
await headerHandler.SetRequestHeaderAsync(_request_gen);
var _url_gen = $"api/HelloService/TestHeaderParameter";
_request_gen.Headers.Add(nameof(name), $"{name}");
_request_gen.RequestUri = new Uri(_url_gen, UriKind.Relative);
var _response_gen = await _client_gen.SendAsync(_request_gen);
_response_gen.EnsureSuccessStatusCode();
var _str_gen = await _response_gen.Content.ReadAsStringAsync();
int.TryParse(_str_gen, out var val);
return val;
}
[global::System.CodeDom.Compiler.GeneratedCode("AutoWasmApiGenerator.HttpServiceInvokerGenerator", "0.0.8.0")]
public async System.Threading.Tasks.Task<int> TestQueryParameter(string name)
{
var _client_gen = this.clientFactory.CreateClient("HelloService");
var _request_gen = new global::System.Net.Http.HttpRequestMessage();
_request_gen.Method = global::System.Net.Http.HttpMethod.Post;
await headerHandler.SetRequestHeaderAsync(_request_gen);
var _url_gen = $"api/HelloService/TestQueryParameter";
var _queries_gen = new List<string>();
_queries_gen.Add($"{nameof(name)}={name}");
_url_gen = $"{_url_gen}?{string.Join("&", _queries_gen)}";
_request_gen.RequestUri = new Uri(_url_gen, UriKind.Relative);
var _response_gen = await _client_gen.SendAsync(_request_gen);
_response_gen.EnsureSuccessStatusCode();
var _str_gen = await _response_gen.Content.ReadAsStringAsync();
int.TryParse(_str_gen, out var val);
return val;
}
[global::System.CodeDom.Compiler.GeneratedCode("AutoWasmApiGenerator.HttpServiceInvokerGenerator", "0.0.8.0")]
public async System.Threading.Tasks.Task<int> TestRouterParameter(string name)
{
var _client_gen = this.clientFactory.CreateClient("HelloService");
var _request_gen = new global::System.Net.Http.HttpRequestMessage();
_request_gen.Method = global::System.Net.Http.HttpMethod.Post;
await headerHandler.SetRequestHeaderAsync(_request_gen);
var _url_gen = $"api/HelloService/TestRouterParameter/{name}";
_request_gen.RequestUri = new Uri(_url_gen, UriKind.Relative);
var _response_gen = await _client_gen.SendAsync(_request_gen);
_response_gen.EnsureSuccessStatusCode();
var _str_gen = await _response_gen.Content.ReadAsStringAsync();
int.TryParse(_str_gen, out var val);
return val;
}
[global::System.CodeDom.Compiler.GeneratedCode("AutoWasmApiGenerator.HttpServiceInvokerGenerator", "0.0.8.0")]
public async System.Threading.Tasks.Task<int> TestFormParameter(string name)
{
var _client_gen = this.clientFactory.CreateClient("HelloService");
var _request_gen = new global::System.Net.Http.HttpRequestMessage();
_request_gen.Method = global::System.Net.Http.HttpMethod.Post;
await headerHandler.SetRequestHeaderAsync(_request_gen);
var _url_gen = $"api/HelloService/TestFormParameter";
var _formDatas_gen = new List<global::System.Collections.Generic.KeyValuePair<string, string>>();
_formDatas_gen.Add(new global::System.Collections.Generic.KeyValuePair<string, string>(nameof(name), $"{name}"));
var _formContent_gen = new global::System.Net.Http.FormUrlEncodedContent(_formDatas_gen);
_formContent_gen.Headers.ContentType = new("application/x-www-form-urlencoded");
_request_gen.Content = _formContent_gen;
_request_gen.RequestUri = new Uri(_url_gen, UriKind.Relative);
var _response_gen = await _client_gen.SendAsync(_request_gen);
_response_gen.EnsureSuccessStatusCode();
var _str_gen = await _response_gen.Content.ReadAsStringAsync();
int.TryParse(_str_gen, out var val);
return val;
}
[global::System.CodeDom.Compiler.GeneratedCode("AutoWasmApiGenerator.HttpServiceInvokerGenerator", "0.0.8.0")]
public async System.Threading.Tasks.Task<string> TestMultiParameter(int id, string name)
{
var _client_gen = this.clientFactory.CreateClient("HelloService");
var _request_gen = new global::System.Net.Http.HttpRequestMessage();
_request_gen.Method = global::System.Net.Http.HttpMethod.Post;
await headerHandler.SetRequestHeaderAsync(_request_gen);
var _url_gen = $"api/HelloService/TestMultiParameter/{id}";
var _queries_gen = new List<string>();
_queries_gen.Add($"{nameof(name)}={name}");
_url_gen = $"{_url_gen}?{string.Join("&", _queries_gen)}";
_request_gen.RequestUri = new Uri(_url_gen, UriKind.Relative);
var _response_gen = await _client_gen.SendAsync(_request_gen);
_response_gen.EnsureSuccessStatusCode();
var _str_gen = await _response_gen.Content.ReadAsStringAsync();
return _str_gen;
}
[global::System.CodeDom.Compiler.GeneratedCode("AutoWasmApiGenerator.HttpServiceInvokerGenerator", "0.0.8.0")]
public async System.Threading.Tasks.Task<string> TestQueryAndBodyParameter(int id, Blazor.Test.Client.Services.RequestTest body)
{
var _client_gen = this.clientFactory.CreateClient("HelloService");
var _request_gen = new global::System.Net.Http.HttpRequestMessage();
_request_gen.Method = global::System.Net.Http.HttpMethod.Post;
await headerHandler.SetRequestHeaderAsync(_request_gen);
var _url_gen = $"api/HelloService/TestQueryAndBodyParameter";
var _queries_gen = new List<string>();
_queries_gen.Add($"{nameof(id)}={id}");
_url_gen = $"{_url_gen}?{string.Join("&", _queries_gen)}";
var _json_gen = global::System.Text.Json.JsonSerializer.Serialize(body);
_request_gen.Content = new StringContent(_json_gen, global::System.Text.Encoding.Default, "application/json");
_request_gen.RequestUri = new Uri(_url_gen, UriKind.Relative);
var _response_gen = await _client_gen.SendAsync(_request_gen);
_response_gen.EnsureSuccessStatusCode();
var _str_gen = await _response_gen.Content.ReadAsStringAsync();
return _str_gen;
}
}
}
感谢⭐start⭐支持项目源码
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!