如何借助Azure Functions运行ASP.NET Core管道
嘿,我来帮你逐个解决这些Azure Functions和ASP.NET Core集成的问题,都是实际开发里容易踩的坑,咱们一步步来~
一、实现通配函数让HttpRequestMessage流经ASP.NET Core管道
要让所有请求都通过ASP.NET Core的处理逻辑,你需要先创建一个通配符路由的Http触发器,再在函数内部搭起ASP.NET Core的中间件管道,把HttpRequestMessage转换成ASP.NET Core能处理的HttpContext,流程如下:
添加必要的NuGet包
先安装这些依赖包:Microsoft.AspNetCore.Mvc.Core、Microsoft.AspNetCore.Http.Extensions、Microsoft.Extensions.DependencyInjection,确保能构建ASP.NET Core的服务和管道。编写通配符函数并集成管道
用静态构造函数初始化ASP.NET Core的应用构建器(避免每次请求都重建),然后在函数里完成请求转换、管道执行和响应转换:using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.Extensions.DependencyInjection; using System.Net.Http; using System.Threading.Tasks; using System.IO; public static class AspNetCoreProxyFunction { private static readonly IApplicationBuilder _appBuilder; static AspNetCoreProxyFunction() { // 初始化服务容器,注册MVC/Razor Pages等服务 var services = new ServiceCollection(); services.AddMvc(); services.AddRazorPages(); var serviceProvider = services.BuildServiceProvider(); // 构建ASP.NET Core中间件管道,和普通Web项目配置一致 _appBuilder = new ApplicationBuilder(serviceProvider); _appBuilder.UseRouting(); _appBuilder.UseEndpoints(endpoints => { endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}"); endpoints.MapRazorPages(); }); } [FunctionName("AspNetCorePipelineProxy")] public static async Task<HttpResponseMessage> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "*", Route = "{*route}")] HttpRequestMessage req) { // 创建ASP.NET Core HttpContext并复制请求信息 var httpContext = new DefaultHttpContext(); var request = httpContext.Request; request.Method = req.Method.Method; request.Scheme = req.RequestUri.Scheme; request.Host = new HostString(req.RequestUri.Host, req.RequestUri.Port); request.Path = new PathString(req.RequestUri.AbsolutePath); request.QueryString = new QueryString(req.RequestUri.Query); // 复制请求体内容 if (req.Content != null) { request.ContentLength = req.Content.Headers.ContentLength; request.ContentType = req.Content.Headers.ContentType?.ToString(); await req.Content.CopyToAsync(request.Body); request.Body.Position = 0; // 重置流位置,确保中间件能读取 } // 让请求流经ASP.NET Core管道 await _appBuilder.Build().Invoke(httpContext); // 将ASP.NET Core响应转换为HttpRequestMessage返回 var response = new HttpResponseMessage(httpContext.Response.StatusCode); response.Content = new StreamContent(httpContext.Response.Body); foreach (var header in httpContext.Response.Headers) { response.Content.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); } return response; } }
二、解决Functions 2中Razor Page返回View报错的问题
这个错误主要是因为Azure Functions的默认环境找不到Razor视图文件的路径,你需要手动配置视图的查找位置并启用运行时编译:
添加Razor运行时编译包
安装Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilationNuGet包。配置视图文件路径
在服务注册的时候,指定Razor页面/视图所在的物理路径,同时设置视图文件的复制属性:services.AddMvc() .AddRazorRuntimeCompilation(options => { // 假设你的Razor Pages放在项目根目录的Pages文件夹下 options.FileProviders.Add(new PhysicalFileProvider( Path.Combine(Directory.GetCurrentDirectory(), "Pages"))); });另外,右键你的Razor Page文件(比如
Index.cshtml),设置复制到输出目录为「如果较新则复制」,这样部署到Azure时视图文件才会被包含进去。
三、自行调用Controller并获取IActionResult返回值
如果你想直接调用Controller的Action方法并拿到IActionResult,可以通过ASP.NET Core的控制器激活器和执行器来实现,确保完整走一遍Action的生命周期:
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.DependencyInjection; using System.Threading.Tasks; public static async Task<IActionResult> InvokeControllerAction(IServiceProvider serviceProvider, HttpContext httpContext) { // 获取必要的服务 var controllerActivator = serviceProvider.GetRequiredService<IControllerActivator>(); var actionInvokerFactory = serviceProvider.GetRequiredService<IActionInvokerFactory>(); // 构建Controller上下文,指定要调用的Controller和Action var controllerContext = new ControllerContext() { HttpContext = httpContext, ActionDescriptor = new ControllerActionDescriptor { ControllerTypeInfo = typeof(HomeController).GetTypeInfo(), ActionName = "Index" // 替换成你要调用的Action名称 } }; // 创建Controller实例 var controller = controllerActivator.Create(controllerContext) as Controller; try { // 方式1:直接调用Action方法 IActionResult actionResult = await controller.Index(); // 如果是ViewResult这类需要渲染的结果,需要执行它 if (actionResult is IStatusCodeActionResult statusResult) { httpContext.Response.StatusCode = statusResult.StatusCode ?? 200; } if (actionResult is IActionResultExecutor executor) { await executor.ExecuteAsync(controllerContext, actionResult); } return actionResult; // 方式2:用ActionInvoker完整执行Action的所有逻辑(包括过滤器等) // var actionInvoker = actionInvokerFactory.CreateInvoker(controllerContext); // await actionInvoker.InvokeAsync(); // return controllerContext.Result; } finally { // 释放Controller实例 controllerActivator.Release(controllerContext, controller); } }
注意:要确保Controller的所有依赖项都已经注册到服务容器里,不然创建Controller实例时会抛出依赖注入的错误。
内容的提问来源于stack exchange,提问作者Paul Shell

