异步MVC控制器和HttpTaskAsyncHandler

时间:2016-05-28 17:29:37

标签: c# asp.net-mvc asp.net-mvc-5

我正在尝试为自定义内容管理解决方案实现自定义 HttpTaskAsyncHandler 。我的想法是将 / 100 / my-little-pony 路由到 / Details / my-little-pony 。我希望通过以下 HttpTaskAsyncHandler 实现此目的:

public override async Task ProcessRequestAsync(HttpContext context)
{
    try
    {
        var id = GetContentIdFromRouteData();

        // Retrieve the content identified by the specified ID
        var contentRepository = new ContentRepository();
        var content = await contentRepository.GetAsync(id);

        if (content == null)
            throw new ContentNotFoundException(id);

        // Initialize an instance of the content controller
        var factory = ControllerBuilder.Current.GetControllerFactory();
        var controller = (IContentController) factory.CreateController(_requestContext, content.ControllerName);
        if (controller == null)
            throw new ControllerNotFoundException(content.ControllerName);

        try
        {
            // Retrieve all content type values and pass them on the the method for index pages
            var action = _requestContext.RouteData.GetRequiredString("action");
            if (action == "Index")
            {
                ContentType data = null;
                if (controller.ContentType != null)
                {
                    data = BusinessHost.Resolve<ContentType>(controller.ContentType);
                    data.Values = content.Parameters.ToDictionary(p => p.Name, p => p.Value);
                }
                _requestContext.RouteData.Values.Add("data", data);
            }

            var values = _requestContext.RouteData.Values;
            values.Add("name", content.Name);
            values.Add("controllerId", id);
            values.Add("controller", content.ControllerName);

            controller.Execute(_requestContext);
        }
        finally
        {
            factory.ReleaseController(controller);
        }
    }
    catch (ContentNotFoundException ex)
    {
        Trace.TraceWarning($"404: {ex.Message}");
        _requestContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
    }
}

这对于同步请求非常有效,但是当我尝试调用异步方法时......

@using (Html.BeginForm("Save", Html.ControllerId(), FormMethod.Post, new { @class = "form-horizontal" }))

......这就是方法......

[HttpPost]
public async Task<ActionResult> Save(NewsViewModel model)
{ }

修改我已将方法的名称更改为保存,因为 Async 未被推断,我收到了新错误:

  

异步操作方法&#39;登录&#39;返回一个无法同步执行的任务。

2 个答案:

答案 0 :(得分:2)

操作名称为SaveAsync,但引用它的代码使用Save作为名称。任何动作都没有神奇的重命名,包括异步一次。

您的选择:

  • 使用SaveAsync来引用操作
  • 使用ActionName attribute重命名操作
  • 将方法重命名为Save(但这违反了所有async方法都有...Async后缀的惯例)

旁注:对于重定向,使用路由可能比某些自定义处理程序更好。

答案 1 :(得分:0)

MvcHandler 还远远超出了人们的视线,而且我已经意识到人们不能简单地希望以干净的良心来复制它。因此,我决定改变完全解决这个问题的方式:而不是尝试实现我自己的 MvcHandler ,而是扩展我的 IRouteHandler

这是我的解决方案:

private static ConcurrentDictionary<string, Type> _contentTypes = new ConcurrentDictionary<string, Type>();

public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
    // Retrieve the page ID from the route data
    var id = GetContentIdFromRouteData(requestContext);

    // Retrieve the content identified by the specified ID
    var contentRepository = new ContentRepository();
    var content = contentRepository.Get(id);

    if (content == null)
        throw new ContentNotFoundException(id);

    // Retrieve all content type values and pass them on the the method for index pages
    var action = requestContext.RouteData.GetRequiredString("action");
    if (action == "Index")
    {
        var data = CreateContentType(requestContext, content);
        requestContext.RouteData.Values.Add("data", data);
    }

    var values = requestContext.RouteData.Values;
    values.Add("name", content.Name);
    values.Add("controllerId", id);
    values.Add("controller", content.ControllerName);

    return new MvcHandler(requestContext);
}

private static int GetContentIdFromRouteData(RequestContext context)
{
    var idString = context.RouteData.GetRequiredString("id");
    int id;

    if (!int.TryParse(idString, out id))
        throw new ArgumentException("Content can't be loaded due to an invalid route parameter.", "id");

    return id;
}

private static ContentType CreateContentType(RequestContext context, Content content)
{
    Type type;
    if (!_contentTypes.ContainsKey(content.ControllerName) || 
        !_contentTypes.TryGetValue(content.ControllerName, out type))
    {
        var factory = ControllerBuilder.Current.GetControllerFactory();
        var controller = (IContentController)factory.CreateController(context, content.ControllerName);

        if (controller == null)
            throw new ControllerNotFoundException(content.ControllerName);

        type = controller.ContentType;
        factory.ReleaseController(controller);

        _contentTypes.TryAdd(content.ControllerName, type);                
    }

    ContentType data = null;
    if (type != null)
    {
        data = BusinessHost.Resolve<ContentType>(type);
        data.Values = content.Parameters.ToDictionary(p => p.Name, p => p.Value);
    }

    return data;
}