解放双手!使用Roslyn生成代码让你的 HTTP 客户端开发变得如此简单
在现代 .NET 开发中,源代码生成器(Source Generators)是一项强大的功能,它允许开发者在编译时自动生成代码,从而减少样板代码的编写,提高开发效率和代码质量。本文主要介绍使用Roslyn实现两个代码生成器:HttpClientApiSourceGenerator 和 HttpClientApiRegisterSourceGenerator,这两个生成器专门用于简化 HTTP 客户端的开发和配置。
什么是 Mud 代码生成器?
Mud 代码生成器是一个基于 Roslyn 的源代码生成器,用于自动生成数据实体、服务层相关代码,提高开发效率。服务层代码生成包含以下主要功能:
- 服务类代码生成 - 根据实体类自动生成服务接口和服务实现类
- 依赖注入代码生成 - 自动为类生成构造函数注入代码,包括日志、缓存、用户管理等常用服务
- 服务注册代码生成 - 自动生成服务注册扩展方法,简化依赖注入配置
- HttpClient API 代码生成 - 自动为标记了 HTTP 方法特性的接口生成 HttpClient 实现类
HttpClient API 源生成器详解
核心功能
HttpClientApiSourceGenerator 是一个专门用于生成 HttpClient 实现类的源代码生成器。它基于 Roslyn 技术,能够自动为标记了 [HttpClientApi] 特性的接口生成完整的 HttpClient 实现类,支持 RESTful API 调用。
工作原理
源生成器的工作流程如下:
- 扫描项目中的接口,查找标记了 [HttpClientApi] 特性的接口
- 分析接口中定义的方法和参数
- 根据 HTTP 方法特性(如 [Get], [Post], [Put] 等)生成相应的实现代码
- 处理各种参数特性(如 [Path], [Query], [Body], [Header])
- 生成完整的 HttpClient 实现类,包括构造函数、日志记录、错误处理等
使用示例
让我们通过一个具体的示例来了解如何使用这个生成器:
[HttpClientApi]
public interface IDingTalkApi
{
[Get("/api/v1/user/{id}")]
Task<UserDto> GetUserAsync([Query] string id);
[Post("/api/v1/user")]
Task<UserDto> CreateUserAsync([Body] UserDto user);
[Put("/api/v1/user/{id}")]
Task<UserDto> UpdateUserAsync([Path] string id, [Body] UserDto user);
[Delete("/api/v1/user/{id}")]
Task<bool> DeleteUserAsync([Path] string id);
}
当项目编译时,HttpClientApiSourceGenerator 会自动生成一个实现该接口的类,大致如下:
// 自动生成的代码
public partial class DingTalkApi : IDingTalkApi
{
private readonly HttpClient _httpClient;
private readonly ILogger<DingTalkApi> _logger;
private readonly JsonSerializerOptions _jsonSerializerOptions;
public DingTalkApi(HttpClient httpClient, ILogger<DingTalkApi> logger)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_jsonSerializerOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = false,
PropertyNameCaseInsensitive = true
};
}
public async Task<UserDto> GetUserAsync(string id)
{
// 自动生成的 HTTP GET 请求逻辑
_logger.LogDebug("开始HTTP GET请求: {Url}", "/api/v1/user/{id}");
var url = $"/api/v1/user/{id}";
using var request = new HttpRequestMessage(HttpMethod.Get, url);
// 处理查询参数
var queryParams = new List<string>();
if (id != null)
queryParams.Add($"id={id}");
if (queryParams.Any())
url += "?" + string.Join("&", queryParams);
// 发送请求并处理响应
// ... 完整的请求处理逻辑
}
}
支持的 HTTP 方法
该生成器支持所有标准的 HTTP 方法:
[HttpClientApi]
public interface IExampleApi
{
[Get("/api/resource/{id}")]
Task<ResourceDto> GetResourceAsync([Path] string id);
[Post("/api/resource")]
Task<ResourceDto> CreateResourceAsync([Body] ResourceDto resource);
[Put("/api/resource/{id}")]
Task<ResourceDto> UpdateResourceAsync([Path] string id, [Body] ResourceDto resource);
[Delete("/api/resource/{id}")]
Task<bool> DeleteResourceAsync([Path] string id);
[Patch("/api/resource/{id}")]
Task<ResourceDto> PatchResourceAsync([Path] string id, [Body] object patchData);
[Head("/api/resource/{id}")]
Task<bool> CheckResourceExistsAsync([Path] string id);
[Options("/api/resource")]
Task<HttpResponseMessage> GetResourceOptionsAsync();
}
参数特性详解
生成器支持多种参数特性,以处理不同的 HTTP 请求参数:
Path 参数特性
用于替换 URL 模板中的路径参数:
[Get("/api/users/{userId}/orders/{orderId}")]
Task<OrderDto> GetOrderAsync([Path] string userId, [Path] string orderId);
Query 参数特性
用于生成查询字符串参数:
[Get("/api/users")]
Task<List<UserDto>> GetUsersAsync(
[Query] string name,
[Query] int? page,
[Query] int? pageSize);
Body 参数特性
用于设置请求体内容:
[Post("/api/users")]
Task<UserDto> CreateUserAsync([Body] UserDto user);
// 支持自定义内容类型
[Post("/api/users")]
Task<UserDto> CreateUserAsync([Body(ContentType = "application/xml")] UserDto user);
// 支持字符串内容
[Post("/api/logs")]
Task LogMessageAsync([Body(UseStringContent = true)] string message);
Header 参数特性
用于设置请求头:
[Get("/api/protected")]
Task<ProtectedData> GetProtectedDataAsync([Header] string authorization);
// 自定义头名称
[Get("/api/protected")]
Task<ProtectedData> GetProtectedDataAsync([Header("X-API-Key")] string apiKey);
复杂参数处理
生成器还能处理复杂的参数类型:
复杂查询参数
支持复杂对象作为查询参数,自动展开为键值对:
[Get("/api/search")]
Task<List<UserDto>> SearchUsersAsync([Query] UserSearchCriteria criteria);
public class UserSearchCriteria
{
public string Name { get; set; }
public int? Age { get; set; }
public string Department { get; set; }
}
// 生成的查询字符串:?Name=John&Age=30&Department=IT
路径参数自动替换
自动处理 URL 模板中的路径参数:
[Get("/api/users/{userId}/orders/{orderId}/items/{itemId}")]
Task<OrderItemDto> GetOrderItemAsync(
[Path] string userId,
[Path] string orderId,
[Path] string itemId);
// 自动替换:/api/users/123/orders/456/items/789
错误处理与日志记录
生成的代码包含完整的错误处理和日志记录:
public async Task<UserDto> GetUserAsync(string id)
{
try
{
_logger.LogDebug("开始HTTP GET请求: {Url}", "/api/v1/user/{id}");
// 请求处理逻辑
using var response = await _httpClient.SendAsync(request);
var responseContent = await response.Content.ReadAsStringAsync();
_logger.LogDebug("HTTP请求完成: {StatusCode}, 响应长度: {ContentLength}",
(int)response.StatusCode, responseContent?.Length ?? 0);
if (!response.IsSuccessStatusCode)
{
_logger.LogError("HTTP请求失败: {StatusCode}, 响应: {Response}",
(int)response.StatusCode, responseContent);
throw new HttpRequestException($"HTTP请求失败: {(int)response.StatusCode} - {response.ReasonPhrase}");
}
// 响应处理逻辑
}
catch (Exception ex)
{
_logger.LogError(ex, "HTTP请求异常: {Url}", url);
throw;
}
}
HttpClient API 注册源生成器详解
核心功能
HttpClientApiRegisterSourceGenerator 是另一个重要的组件,它自动为标记了 [HttpClientApi] 特性的接口生成依赖注入注册代码,简化 HttpClient 服务的配置。
工作原理
该生成器的工作流程如下:
- 扫描项目中的接口,查找标记了 [HttpClientApi] 特性的接口
- 提取特性中的配置参数(如 BaseUrl、Timeout 等)
- 生成用于依赖注入的扩展方法
- 自动注册接口和实现类到服务容器中
使用示例
首先定义 HTTP API 接口:
[HttpClientApi("https://api.dingtalk.com", Timeout = 30)]
public interface IDingTalkApi
{
[Get("/api/v1/user/{id}")]
Task<UserDto> GetUserAsync([Query] string id);
[Post("/api/v1/user")]
Task<UserDto> CreateUserAsync([Body] UserDto user);
}
[HttpClientApi("https://api.wechat.com", Timeout = 60)]
public interface IWeChatApi
{
[Get("/api/v1/user/{id}")]
Task<UserDto> GetUserAsync([Query] string id);
}
生成器会自动生成以下注册代码:
// 自动生成的代码 - HttpClientApiExtensions.g.cs
using System;
using System.Net.Http;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.Extensions.DependencyInjection
{
public static class HttpClientApiExtensions
{
public static IServiceCollection AddWebApiHttpClient(this IServiceCollection services)
{
services.AddHttpClient<global::YourNamespace.IDingTalkApi, global::YourNamespace.DingTalkApi>(client =>
{
client.BaseAddress = new Uri("https://api.dingtalk.com");
client.Timeout = TimeSpan.FromSeconds(30);
});
services.AddHttpClient<global::YourNamespace.IWeChatApi, global::YourNamespace.WeChatApi>(client =>
{
client.BaseAddress = new Uri("https://api.wechat.com");
client.Timeout = TimeSpan.FromSeconds(60);
});
return services;
}
}
}
配置选项
HttpClientApi 特性参数
// 基本配置
[HttpClientApi("https://api.example.com")]
public interface IExampleApi { }
// 配置超时时间
[HttpClientApi("https://api.example.com", Timeout = 120)]
public interface IExampleApi { }
// 使用命名参数
[HttpClientApi(BaseUrl = "https://api.example.com", Timeout = 60)]
public interface IExampleApi { }
使用方式
在应用程序启动时调用
// 在 Program.cs 或 Startup.cs 中
var builder = WebApplication.CreateBuilder(args);
// 自动注册所有 HttpClient API 服务
builder.Services.AddWebApiHttpClient();
// 或者与其他服务注册一起使用
builder.Services
.AddControllers()
.AddWebApiHttpClient();
在控制台应用程序中使用
// 在控制台应用程序中
var services = new ServiceCollection();
// 注册 HttpClient API 服务
services.AddWebApiHttpClient();
var serviceProvider = services.BuildServiceProvider();
var dingTalkApi = serviceProvider.GetRequiredService<IDingTalkApi>();
两个生成器的协同工作
HttpClientApiRegisterSourceGenerator 与 HttpClientApiSourceGenerator 完美配合,提供完整的开发体验:
- HttpClientApiSourceGenerator 生成接口的实现类
- HttpClientApiRegisterSourceGenerator 生成依赖注入注册代码
- 完整的开发体验:定义接口 → 自动生成实现 → 自动注册服务
完整示例
// 1. 定义接口
[HttpClientApi("https://api.dingtalk.com", Timeout = 30)]
public interface IDingTalkApi
{
[Get("/api/v1/user/{id}")]
Task<UserDto> GetUserAsync([Query] string id);
}
// 2. 自动生成实现类 (由 HttpClientApiSourceGenerator 生成)
// public partial class DingTalkApi : IDingTalkApi { ... }
// 3. 自动生成注册代码 (由 HttpClientApiRegisterSourceGenerator 生成)
// public static class HttpClientApiExtensions { ... }
// 4. 在应用程序中使用
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddWebApiHttpClient(); // 自动注册
var app = builder.Build();
// 5. 在服务中注入使用
public class UserService
{
private readonly IDingTalkApi _dingTalkApi;
public UserService(IDingTalkApi dingTalkApi)
{
_dingTalkApi = dingTalkApi;
}
public async Task<UserDto> GetUserAsync(string userId)
{
return await _dingTalkApi.GetUserAsync(userId);
}
}
高级配置
自定义 HttpClient 配置
如果需要更复杂的 HttpClient 配置,可以在注册后继续配置:
builder.Services.AddWebApiHttpClient()
.ConfigureHttpClientDefaults(httpClient =>
{
httpClient.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
UseProxy = false,
AllowAutoRedirect = false
});
});
添加自定义请求头
builder.Services.AddHttpClient<IDingTalkApi, DingTalkApi>(client =>
{
client.BaseAddress = new Uri("https://api.dingtalk.com");
client.Timeout = TimeSpan.FromSeconds(30);
client.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0");
client.DefaultRequestHeaders.Add("X-API-Key", "your-api-key");
});
生成的代码结构
obj/Debug/net8.0/generated/
├── Mud.ServiceCodeGenerator/
├── HttpClientApiSourceGenerator/
│ └── YourNamespace.DingTalkApi.g.cs
└── HttpClientApiRegisterSourceGenerator/
└── HttpClientApiExtensions.g.cs
最佳实践
- 统一配置:在 [HttpClientApi] 特性中统一配置所有 API 的基础设置
- 合理超时:根据 API 的响应时间设置合理的超时时间
- 命名规范:遵循接口命名规范(I{ServiceName}Api)
- 错误处理:在服务层处理 API 调用异常
- 日志记录:利用生成的日志记录功能监控 API 调用
如何查看生成的代码
要查看生成的代码,可以在项目文件中添加以下配置:
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>
生成的代码将位于 obj/[Configuration]/[TargetFramework]/generated/ 目录下,文件名以 .g.cs 结尾。
解放双手!使用Roslyn生成代码让你的 HTTP 客户端开发变得如此简单的更多相关文章
- 解放双手,自动生成“x.set(y.get)”,搞定vo2dto转换
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 给你机会,你也不中用啊 这些年从事编程开发以来,我好像发现了大部分研发那些不愿意干的 ...
- 10个 解放双手的 IDEA 插件,这些代码都不用写(第二弹)
本文案例收录在 https://github.com/chengxy-nds/Springboot-Notebook 大家好,我是小富~ 鸽了很久没发文,不写文章的日子真的好惬意,每天也不用愁着写点什 ...
- 10行Python代码自动清理电脑内重复文件,解放双手!
大家好,又到了Python办公自动化系列. 今天分享一个系统层面的自动化案例: 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很多已经做 ...
- 写完批处理脚本,再写个Gradle脚本,解放双手
前言 上一篇写个批处理来帮忙干活---遍历&字符串处理中,我们已经学习如何写批处理脚本来帮我们做一些简单的重复性工作,本篇继续来学习如何用 Gradle 写脚本,让它也来帮我们干活 Gradl ...
- 解放双手,markdown文章神器,Typora+PicGo+七牛云图床实现自动上传图片
本文主要分享使用Typora作为Markdown编辑器,PicGo为上传图片工具,使用七牛云做存储来解放双手实现图片的自动化上传与管理.提高写作效率,提升逼格.用过 Markdown 的朋友一定会深深 ...
- 生成代码,从 T1 到 T16 —— 自动生成多个类型的泛型
当你想写一个泛型 的类型的时候,是否想过两个泛型参数.三个泛型参数.四个泛型参数或更多泛型参数的版本如何编写呢?是一个个编写?类小还好,类大了就杯具! 事实上,在 Visual Studio 中生成代 ...
- Android注解使用之通过annotationProcessor注解生成代码实现自己的ButterKnife框架
前言: Annotation注解在Android的开发中的使用越来越普遍,例如EventBus.ButterKnife.Dagger2等,之前使用注解的时候需要利用反射机制势必影响到运行效率及性能,直 ...
- mybatis Generator生成代码及使用方式
本文原创,转载请注明:http://www.cnblogs.com/fengzheng/p/5889312.html 为什么要有mybatis mybatis 是一个 Java 的 ORM 框架,OR ...
- x01.CodeBuilder: 生成代码框架
根据 Assembly 生成代码框架. 这是学习 AvalonEdit 的一个副产品.学习时,照着源代码新建文件夹,新建文件,添加方法与属性,虽然只是个框架,也要花费大量时间.为什么不让它自动生成呢? ...
- mybatis generator maven插件自动生成代码
如果你正为无聊Dao代码的编写感到苦恼,如果你正为怕一个单词拼错导致Dao操作失败而感到苦恼,那么就可以考虑一些Mybatis generator这个差价,它会帮我们自动生成代码,类似于Hiberna ...
随机推荐
- 微软写了份GPT-4V说明书:166页详细讲解,提示词demo示例全都有
克雷西萧箫发自凹非寺 量子位公众号 QbitAI 多模态王炸大模型 GPT-4V,166 页"说明书"重磅发布!而且还是微软团队出品. 什么样的论文,能写出 166 页? 不仅详细 ...
- Windows 10 补丁包 msu 转 cab 用 dism 安装
dism /online /add-package /packagepath:.\Windows6.1-KB2533623-x64.cab /norestart 或 dism /online /add ...
- Spring IoC 容器配置模版
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...
- CMake构建学习笔记25-SpatiaLite库的构建
1. 引言 尝试使用CMake构建SpatiaLite及其依赖库,但是没有成功.因为SpatiaLite及其依赖库很多都是老牌的C库,这种库由于年代的原因一般都不提供CMake的构建方式,在Windo ...
- Linux虚拟机Nginx代理vue前端与SpringBoot后端资源
1.Nginx 安装配置 详细参见菜鸟教程:https://www.runoob.com/linux/nginx-install-setup.html 2.nginx.conf内容 user ngin ...
- MathType安装及使用教程
本文介绍了MathType的安装.配置及在Word中的使用方法.内容包括软件下载与安装步骤.在Word中添加和删除MathType快捷方式.MathType与LaTeX公式的相互转换,并提供了常见问题 ...
- 【iOS】APP的优化---IPA大小的压缩
众所周知,在App Store中超过一定大小的文件只能使用WiFi下载(近期提升到了150M,之前是100M).虽然提升了一点,但是我们仍需要注意安装包的大小.毕竟除了游戏很少有人喜欢下很大的应用. ...
- Manim实现水波纹特效
本文将介绍如何使用ManimCE框架实现一个水波纹特效,让你的数学动画更加生动有趣. 1. 实现原理 水波纹特效通过WaterRipple类实现,这是一个自定义的Animation子类.让我们从代码角 ...
- Lec 07 操作系统管理页表映射
lec 07 操作系统管理页表映射 目录 lec 07 操作系统管理页表映射 0 Contents 1 操作系统设置页表映射 何时设置页表映射? 2 立即映射 分配物理页的简单实现 OS填写页表基地址 ...
- ORB-SLAM demo测试
给了Example,从官网上下了个数据集,跟着跑就得了 https://blog.csdn.net/u010128736/article/category/6461394 用深度相机,也就是RGB-D ...