Azure Functions添加中间件/格式化器:实现ASP.NET MVC InputFormatters功能遇阻
在Azure Functions中实现类似ASP.NET MVC InputFormatters的输入格式化逻辑
我完全懂你想要的效果——不管Azure Function的触发源是ServiceBus、Http还是其他类型,都能像ASP.NET MVC的InputFormatters那样,统一接管输入的解析和格式化逻辑。之前尝试添加Message => OpenType、String => OpenType这类开放转换器没起效,还碰到过时文档的问题,确实挺闹心的,我之前也踩过类似的坑,给你分享下现在有效的解决方案:
核心思路:用自定义输入转换器(IConverter)替代旧版开放转换器
新版Azure Functions(尤其是隔离进程模型)已经弃用了旧的开放转换器机制,现在统一用IConverter<TInput, TOutput>接口来实现自定义输入转换,这个方式对所有触发源都适用。
1. 实现自定义转换器
针对你需要的转换类型,分别实现IConverter接口:
示例1:String → OpenType 转换器
public class StringToOpenTypeConverter : IConverter<string, OpenType> { public OpenType Convert(string input, ConverterContext context) { // 这里写你的自定义格式化逻辑:比如自定义反序列化、特殊字符串解析等 var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; return JsonSerializer.Deserialize<OpenType>(input, options) ?? throw new InvalidOperationException("无法解析输入为OpenType"); } }
示例2:ServiceBus Message → OpenType 转换器
public class ServiceBusMessageToOpenTypeConverter : IConverter<Message, OpenType> { public OpenType Convert(Message input, ConverterContext context) { // 读取ServiceBus消息内容,自定义解析 var messageBody = Encoding.UTF8.GetString(input.Body); return new StringToOpenTypeConverter().Convert(messageBody, context); } }
2. 注册自定义转换器
根据你的函数模型(隔离进程/进程内),将转换器注册到依赖注入容器:
隔离进程模型(Program.cs)
var host = new HostBuilder() .ConfigureFunctionsWorkerDefaults(builder => { // 注册所有自定义转换器 builder.Services.AddSingleton<IConverter<string, OpenType>, StringToOpenTypeConverter>(); builder.Services.AddSingleton<IConverter<Message, OpenType>, ServiceBusMessageToOpenTypeConverter>(); }) .Build(); host.Run();
进程内模型(Startup.cs)
public class Startup : FunctionsStartup { public override void Configure(IFunctionsHostBuilder builder) { builder.Services.AddSingleton<IConverter<string, OpenType>, StringToOpenTypeConverter>(); builder.Services.AddSingleton<IConverter<Message, OpenType>, ServiceBusMessageToOpenTypeConverter>(); } }
3. 在函数中直接使用
注册完成后,你只需要在函数绑定中直接声明目标类型OpenType,转换器会自动生效:
Http触发示例
[Function("HttpOpenTypeTrigger")] public async Task<HttpResponseData> Run( [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req, // 直接绑定到OpenType,转换器自动处理输入解析 [FromBody] OpenType inputModel, FunctionContext context) { // 直接使用解析后的inputModel即可 var response = req.CreateResponse(HttpStatusCode.OK); await response.WriteAsJsonAsync(new { Message = "解析成功", Data = inputModel }); return response; }
ServiceBus触发示例
[Function("ServiceBusOpenTypeTrigger")] public void Run( [ServiceBusTrigger("myqueue", Connection = "ServiceBusConnection")] OpenType inputModel, FunctionContext context) { // 直接使用解析后的inputModel处理业务逻辑 Console.WriteLine($"Received model: {JsonSerializer.Serialize(inputModel)}"); }
Http触发的额外优化:用中间件做全局处理
如果是Http触发的函数,你还可以通过中间件实现更全局的输入拦截和格式化,和MVC的中间件逻辑类似:
实现自定义Http中间件
public class CustomInputMiddleware : IFunctionsWorkerMiddleware { public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next) { // 判断当前函数是否是Http触发 if (context.FunctionDefinition.InputBindings.Values.Any(b => b.Type.Equals("httpTrigger", StringComparison.OrdinalIgnoreCase))) { var req = await context.GetHttpRequestDataAsync(); if (req != null) { // 读取原始请求内容,自定义格式化 req.Body.Position = 0; // 重置流位置,避免后续读取失败 var rawContent = await new StreamReader(req.Body).ReadToEndAsync(); var formattedModel = YourCustomGlobalParsingLogic(rawContent); // 将格式化后的模型存入上下文,供函数读取 context.Items["GlobalFormattedModel"] = formattedModel; // 重置流位置(如果后续还需要读取原始内容) req.Body.Position = 0; } } await next(context); } private OpenType YourCustomGlobalParsingLogic(string rawContent) { // 这里写全局统一的解析逻辑 return JsonSerializer.Deserialize<OpenType>(rawContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true })!; } }
注册中间件
在Program.cs中添加中间件注册:
.ConfigureFunctionsWorkerDefaults(builder => { builder.UseMiddleware<CustomInputMiddleware>(); // 其他注册逻辑... })
在函数中使用中间件处理后的模型
[Function("HttpMiddlewareTrigger")] public async Task<HttpResponseData> Run( [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req, FunctionContext context) { var formattedModel = context.Items["GlobalFormattedModel"] as OpenType; // 处理逻辑... }
避坑指南
- 过时文档坑:旧版文档提到的
OpenConverter等API已经被废弃,现在必须用IConverter接口,别再浪费时间在旧方法上了。 - 触发源差异:不同触发源的输入原始类型不同(比如ServiceBus是
Message,Http隔离进程是HttpRequestData),要针对对应的原始类型实现转换器。 - 流位置重置:在Http中间件中读取请求流后,一定要重置流位置,否则后续绑定或函数代码读取流会失败。
内容的提问来源于stack exchange,提问作者Ayman
相关产品推荐
相关产品推荐

