Migrating dynamic API Controller Execution Code to ASP.NET Core in .NET 8

I am currently migrating an application originally built on .NET Framework 4.6.1 to .NET 8. The relevant piece of the application is an API Controller that receives a batch of API requests from the browser, and executes them one-by-one on the server side, bundling the responses into a single response.

In the original .NET Framework code, manually created an HttpControllerContext instance, and called the ApiController.ExecuteAsync() method, which works well to replicate the action pipeline and call the correct API Action with the provided querystring and/or form parameters.

In the migrated version, I can’t figure out how to do that. As an initial alternative, I am using reflection to grab the MethodInfo for the controller action. This works for simple cases, but it is very fragile due to possibility of upper/lower case differences, and also method overloading.

Question: I am looking for solutions to effectively migrate and optimize this functionality for ASP.NET Core (.Net 8) and leverage automatic Route binding instead of manually creating the MethodInfo object.

Below is the relevant section of code, before and after migration.

Original code snippet from .NET Framework 4.6.1 that I’m migrating:

// Original .NET Framework 4.6.1 code
private async Task ExecuteItemAsync(
    BatchItem item,
    BatchApiInternalResult batchResult)
{
    try
    {
        var apiPrefix = webFxOptions.ApiRoutePrefix; // Get the API route prefix from the options

        var hostComponents = Request.Host.ToUriComponent().Split(':'); // Split the host into components (hostname and port)

        // Build the URI for the API request
        var builder = new UriBuilder
        {
            Scheme = Request.Scheme,
            Host = hostComponents[0],
            Path = $"api/{item.Controller}/{item.Action}",
            Query = this.GetQueryStringForItem(item) // Get the query string for the item
        };

        // If the host includes a port, set it in the UriBuilder
        if (hostComponents.Length == 2)
        {
            builder.Port = Convert.ToInt32(hostComponents[1]);
        }

        var uri = builder.Uri; // Construct the final URI
        var reqMsg = new HttpRequestMessage(HttpMethod.Get, uri); // Create a new HTTP GET request message

        // Get the controller type based on the item.Controller value
        var type = BatchApiHelper.Controllers.FirstOrDefault(o => o.Name.Equals(item.Controller + "Controller", StringComparison.CurrentCultureIgnoreCase)) ?? GetType();

        // Get the controller instance from the service provider
        var controller = serviceProvider.GetService(type) as ControllerBase;
        var controllerType = BatchApiHelper.Controllers.FirstOrDefault(o => o.Name.Equals(item.Controller + "Controller", StringComparison.CurrentCultureIgnoreCase));
        var controllerDescriptor = new ControllerDescriptor(Configuration, item.Controller, controllerType); // Create a controller descriptor

        // Set up the route and route data for the request
        var route = Configuration.Routes.FirstOrDefault(o => o.RouteTemplate == "api/{controller}/{id}"); // Find the route matching the template
        var routeData = new HttpRouteData(route, new HttpRouteValueDictionary());
        routeData.Values.Add("Controller", item.Controller); // Add controller to route data
        routeData.Values.Add("Action", item.Action); // Add action to route data

        // Create the controller context
        var ctlContext = new HttpControllerContext(Configuration, routeData, reqMsg)
        {
            Controller = controller,
            ControllerDescriptor = controllerDescriptor,
            Request = reqMsg,
        };
        ctlContext.RequestContext.Principal = RequestContext.Principal; // Set the user principal in the context
        reqMsg.SetRequestContext(ctlContext.RequestContext); // Set the request context

        // Execute the controller action
        var httpResponse = await controller.ExecuteAsync(ctlContext, CancellationToken.None);
        batchResult.Result = (httpResponse.Content as ObjectContent)?.Value; // Get the result from the response content
        batchResult.ReasonPhrase = httpResponse.ReasonPhrase; // Set the reason phrase
        batchResult.StatusCode = httpResponse.StatusCode; // Set the status code
        var headers = httpResponse.Headers.Select(o => new KeyValuePair<string, object>(o.Key, o.Value.FirstOrDefault())); // Get the response headers
        batchResult.Headers = headers.ToList();
        batchResult.IsSuccessStatusCode = httpResponse.IsSuccessStatusCode; // Check if the response is successful
    }
    catch (HttpResponseException e) // Handle HttpResponseException separately
    {
        // HttpResponseException is specifically thrown to return HTTP-specific error details in the response.
        // This allows for capturing detailed HTTP response information such as status code and headers.
        if (webFxOptions.SanitizeExceptions)
        {
            // Sanitize the error message if the option is enabled
            batchResult.Error = webFxOptions.DefaultApiExceptionMessage;
            batchResult.Result = new { message = batchResult.Error };
        }
        else
        {
            batchResult.Error = e.ToString();
            batchResult.Result = new { message = e.Message };
        }
        // Capture detailed HTTP response information
        batchResult.ReasonPhrase = e.Response.ReasonPhrase;
        batchResult.StatusCode = e.Response.StatusCode;
        var headers = e.Response.Headers.Select(o => new KeyValuePair<string, object>(o.Key, o.Value.FirstOrDefault()));
        batchResult.Headers = headers.ToList();
        batchResult.IsSuccessStatusCode = e.Response.IsSuccessStatusCode;
    }
    catch (Exception e) // Handle general exceptions
    {
        // General exceptions cover other unexpected errors that may occur during execution.
        if (webFxOptions.SanitizeExceptions)
        {
            // Sanitize the error message if the option is enabled
            batchResult.Error = webFxOptions.DefaultApiExceptionMessage;
            batchResult.Result = new { message = batchResult.Error };
        }
        else
        {
            batchResult.Error = e.ToString();
            batchResult.Result = new { message = e.Message };
        }
        // For general exceptions, set the reason phrase and status code manually
        batchResult.ReasonPhrase = e.GetBaseException().Message;
        batchResult.StatusCode = HttpStatusCode.InternalServerError;
    }
}

Here is the new, “work-in-progress” migrated code for .Net 8:

// New, migrated code for .Net 8
private async Task ExecuteItemAsync(
   BatchItem item,
   BatchApiInternalResult batchResult)
{
    try
    {
        var apiPrefix = webFxOptions.ApiRoutePrefix; // Get the API route prefix from the options

        var hostComponents = Request.Host.ToUriComponent().Split(':'); // Split the host into components (hostname and port)

        // Build the URI for the API request
        var builder = new UriBuilder
        {
            Scheme = Request.Scheme,
            Host = hostComponents[0],
            Path = $"api/{item.Controller}/{item.Action}",
            Query = this.GetQueryStringForItem(item) // Get the query string for the item
        };

        // If the host includes a port, set it in the UriBuilder
        if (hostComponents.Length == 2)
        {
            builder.Port = Convert.ToInt32(hostComponents[1]);
        }

        var uri = builder.Uri; // Construct the final URI
        var reqMsg = new HttpRequestMessage(HttpMethod.Get, uri); // Create a new HTTP GET request message

        // Get the controller type based on the item.Controller value
        var controllerType = BatchApiHelper.Controllers.FirstOrDefault(o => o.Name.Equals(item.Controller + "Controller", StringComparison.CurrentCultureIgnoreCase)) ?? GetType();

        if (controllerType == null)
        {
            throw new Exception($"Controller '{item.Controller}Controller' not found.");
        }
        var methodInfo = controllerType.GetMethod(item.Action);
        var actionDescriptor = new ControllerActionDescriptor
        {
            ControllerName = item.Controller,
            ActionName = item.Action,
            ControllerTypeInfo = controllerType.GetTypeInfo(),
            MethodInfo = methodInfo
        };

        // Create the controller instance
        var controller = ActivatorUtilities.CreateInstance(serviceProvider, controllerType) as ControllerBase;
        var defaultHttpContext = new DefaultHttpContext { RequestServices = serviceProvider, User = Request.HttpContext.User };
        var actionContext = new ActionContext
        {
            HttpContext = defaultHttpContext,
            RouteData = new RouteData(),
            ActionDescriptor = actionDescriptor
        };

        // Set the route data for the request
        actionContext.RouteData.Values["controller"] = item.Controller;
        actionContext.RouteData.Values["action"] = item.Action;

        // Create the action invoker
        var invokerFactory = serviceProvider.GetRequiredService<IActionInvokerFactory>();
        var invoker = invokerFactory.CreateInvoker(actionContext);

        await invoker.InvokeAsync();
        // Get the result executor for ObjectResult
        ObjectResult objectResult = null;
        // Get the type of ControllerActionInvoker
        Type invokerType = invoker.GetType();

        // Get the _result field (assuming it's a private field)
        FieldInfo resultField = invokerType.GetField("_result", BindingFlags.NonPublic | BindingFlags.Instance);

        if (resultField != null)
        {
            // Get the value of _result for the specific invoker instance
            objectResult = resultField.GetValue(invoker) as ObjectResult;
        }
        var content = objectResult?.Value;
        //var objectResult = actionContext.HttpContext.Response. as ObjectResult;
        batchResult.Result = objectResult?.Value; // Get the result from the response content
        batchResult.ReasonPhrase = actionContext.HttpContext.Response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase; // Set the reason phrase
        batchResult.StatusCode = (HttpStatusCode)actionContext.HttpContext.Response.StatusCode; // Set the status code
        var headers = actionContext.HttpContext.Response.Headers.Select(o => new KeyValuePair<string, object>(o.Key, o.Value.FirstOrDefault())); // Get the response headers
        batchResult.Headers = headers.ToList();
        batchResult.IsSuccessStatusCode = actionContext.HttpContext.Response.StatusCode >= 200 && actionContext.HttpContext.Response.StatusCode < 300; // Check if the response is successful
    }
    //catch (HttpResponseException e) // Handle HttpResponseException separately
    //{
    //    // HttpResponseException is specifically thrown to return HTTP-specific error details in the response.
    //    // This allows for capturing detailed HTTP response information such as status code and headers.
    //    if (webFxOptions.SanitizeExceptions)
    //    {
    //        // Sanitize the error message if the option is enabled
    //        batchResult.Error = webFxOptions.DefaultApiExceptionMessage;
    //        batchResult.Result = new { message = batchResult.Error };
    //    }
    //    else
    //    {
    //        batchResult.Error = e.ToString();
    //        batchResult.Result = new { message = e.Message };
    //    }
    //    // Capture detailed HTTP response information
    //    batchResult.ReasonPhrase = e.Response.ReasonPhrase;
    //    batchResult.StatusCode = e.Response.StatusCode;
    //    var headers = e.Response.Headers.Select(o => new KeyValuePair<string, object>(o.Key, o.Value.FirstOrDefault()));
    //    batchResult.Headers = headers.ToList();
    //    batchResult.IsSuccessStatusCode = e.Response.IsSuccessStatusCode;
    //}
    catch (Exception e) // Handle general exceptions
    {
        // General exceptions cover other unexpected errors that may occur during execution.
        if (webFxOptions.SanitizeExceptions)
        {
            // Sanitize the error message if the option is enabled
            batchResult.Error = webFxOptions.DefaultApiExceptionMessage;
            batchResult.Result = new { message = batchResult.Error };
        }
        else
        {
            batchResult.Error = e.ToString();
            batchResult.Result = new { message = e.Message };
        }
        // For general exceptions, set the reason phrase and status code manually
        batchResult.ReasonPhrase = e.GetBaseException().Message;
        batchResult.StatusCode = HttpStatusCode.InternalServerError;
    }
}

Side note I am aware the new code is currently relying on .Net internal types and names (e.g. _result), but not sure how else to get the result information, other than possibly reading the response stream.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật