增量生成器简化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双端共用的项目,一般是共用的页面、实体模型、等等

使用步骤

  1. 在Shared项目中引用AutoWasmApiGenerator项目
    <PackageReference Include="AutoWasmApiGenerator" Version="*" />
    
  2. 在Shared或Client项目中添加接口
[WebController(Authorize = true)]
[ApiInvokerGenerate]
public interface IHelloService
{
    Task<string> SayHelloAsync(string name);
}
  1. 在Server项目中任意一个文件标注WebControllerAssemblyAttribute, 生成的文件为TestServiceController
[assembly: AutoWasmApiGenerator.WebControllerAssembly]
  1. 在Client项目中或生成控制器调用类的项目中的任意一个文件标注ApiInvokerAssemblyAttribute, 生成的文件为TestServiceApiInvoker
[assembly: AutoWasmApiGenerator.ApiInvokerAssembly]
  1. 在Client项目中注册
builder.Services.AddScoped<ITestService, TestServiceApiInvoker>();
  1. 通过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⭐支持项目源码

生成器源码

posted @   yaoqinglin_mtiter  阅读(243)  评论(3编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示