如何在ASP.NET MVC 6中使用RedirectToAction继续使用ModelState?

8

我有一个删除对象的方法。删除不拥有视图,并位于“EditReport”中的“Delete”按钮中。成功删除后将重定向到“Report”页面。

[HttpPost]
[Route("{reportId:int}")]
[ValidateAntiForgeryToken]
public IActionResult DeleteReport(int reportId)
{
    var success = _reportService.DeleteReportControl(reportId);
    if (success == false)
    {
        ModelState.AddModelError("Error", "Messages");
        return RedirectToAction("EditReport");
    }
    ModelState.AddModelError("OK", "Messages");
    return RedirectToAction("Report");
}

在ASP.NET MVC 5中,我使用以下属性在方法之间保存ModelState。我从这里获取了信息:https://dev59.com/n2445IYBdhLWcg3w6eNo#12024227
public class SetTempDataModelStateAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);         
        filterContext.Controller.TempData["ModelState"] = 
           filterContext.Controller.ViewData.ModelState;
    }
}

public class RestoreModelStateFromTempDataAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);
        if (filterContext.Controller.TempData.ContainsKey("ModelState"))
        {
            filterContext.Controller.ViewData.ModelState.Merge(
                (ModelStateDictionary)filterContext.Controller.TempData["ModelState"]);
        }
    }
}

在ASP.NET MVC 6 RC 1 (ASP.NET Core 1.0)中,这段代码无法正常工作。
filterContext.Controller中未定义TempData和ViewData。
2个回答

9

感谢答案,我意识到需要创建自己的ASP.NET Core 1.0代码(完整的.NET Framework 4.6.2)。

public class SetTempDataModelStateAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);

        var controller = filterContext.Controller as Controller;
        var modelState = controller?.ViewData.ModelState;
        if (modelState != null)
        {
            var listError = modelState.Where(x => x.Value.Errors.Any())
                .ToDictionary(m => m.Key, m => m.Value.Errors
                .Select(s => s.ErrorMessage)
                .FirstOrDefault(s => s != null));
            controller.TempData["ModelState"] = JsonConvert.SerializeObject(listError);
        }
    }
}
public class RestoreModelStateFromTempDataAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);

        var controller = filterContext.Controller as Controller;
        var tempData = controller?.TempData?.Keys;
        if (controller != null && tempData != null)
        {
            if (tempData.Contains("ModelState"))
            {
                var modelStateString = controller.TempData["ModelState"].ToString();
                var listError = JsonConvert.DeserializeObject<Dictionary<string, string>>(modelStateString);
                var modelState = new ModelStateDictionary();
                foreach (var item in listError)
                {
                    modelState.AddModelError(item.Key, item.Value ?? "");
                }

                controller.ViewData.ModelState.Merge(modelState);
            }
        }
    }
}

ASP.NET Core 1.0的异步版本(完整的.NET Framework 4.6.2)

public class SetTempDataModelStateAttribute : ActionFilterAttribute
    {
        public override async Task OnActionExecutionAsync(ActionExecutingContext filterContext, ActionExecutionDelegate next)
        {
            await base.OnActionExecutionAsync(filterContext, next);

            var controller = filterContext.Controller as Controller;
            var modelState = controller?.ViewData.ModelState;
            if (modelState != null)
            {
                var listError = modelState.Where(x => x.Value.Errors.Any())
                    .ToDictionary(m => m.Key, m => m.Value.Errors
                    .Select(s => s.ErrorMessage)
                    .FirstOrDefault(s => s != null));
                var listErrorJson = await Task.Run(() => JsonConvert.SerializeObject(listError));
                controller.TempData["ModelState"] = listErrorJson;
            }
            await next();
        }
    }
public class RestoreModelStateFromTempDataAttribute : ActionFilterAttribute
    {
        public override async Task OnActionExecutionAsync(ActionExecutingContext filterContext, ActionExecutionDelegate next)
        {
            await base.OnActionExecutionAsync(filterContext, next);

            var controller = filterContext.Controller as Controller;
            var tempData = controller?.TempData?.Keys;
            if (controller != null && tempData != null)
            {
                if (tempData.Contains("ModelState"))
                {
                    var modelStateString = controller.TempData["ModelState"].ToString();
                    var listError = await Task.Run(() => 
                        JsonConvert.DeserializeObject<Dictionary<string, string>>(modelStateString));
                    var modelState = new ModelStateDictionary();
                    foreach (var item in listError)
                    {
                        modelState.AddModelError(item.Key, item.Value ?? "");
                    }

                    controller.ViewData.ModelState.Merge(modelState);
                }
            }
            await next();
        }
    }

1
应该有一个检查来防止非错误显示在SetTempDataModelStateAttribute行中,var listError = modelState.ToDictionary(m => m.Key, m => m.Value.Errors 应该是 modelState.Where(x => x.Value.Errors.Any()).ToDictionary(m... - Serj Sagan
1
在这里使用await next();是不正确的,因为它会导致该方法被多次调用。 - Serj Sagan

3
下面是使代码编译的修复方法,但似乎ASP.NET Core不支持序列化模型状态(因为ModelStateEntry包含不可序列化的异常)。
因此,在TempData中无法序列化模型状态。正如这个GitHub问题所解释的那样,似乎没有计划改变这种行为。
在ASP.NET Core中,ActionExecutingContext中的Controller属性是object类型。这是因为ASP.NET Core中的控制器不需要继承自Controller,因此它们没有共同的基础类型。
为了访问TempData属性,你需要首先将其强制转换为Controller类型。然后你的属性可能如下所示:
public class SetTempDataModelStateAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);

        Controller controller = filterContext.Controller as Controller;
        if (controller != null)
        {
            controller.TempData["ModelState"] = controller.ViewData.ModelState;
        }
    }
}

public class RestoreModelStateFromTempDataAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);

        Controller controller = filterContext.Controller as Controller;
        if (controller != null & controller.TempData.ContainsKey("ModelState"))
        {
            controller.ViewData.ModelState.Merge(
                (ModelStateDictionary)controller.TempData["ModelState"]);
        }
    }
}

将其添加到Sessions项目中。虽然我无法解决错误问题:InvalidOperationException: The 'Microsoft.AspNet.Mvc.ViewFeatures.SessionStateTempDataProvider' cannot serialize an object of type 'Microsoft.AspNet.Mvc.ModelBinding.ModelStateDictionary' to session state - Kolya_Net
似乎ASP.NET Core不支持序列化模型状态,并且没有计划更改。我已经更新了我的答案以反映这一点。 - poke
正确的做法是将ModelState转换为字符串(json),并存储在TempData中吗? - Kolya_Net
你不能序列化模型状态,包括将其序列化为JSON。如果你仍然想这样做,你将不得不手动进行序列化。 - poke
谢谢提供信息。这是我的解决方案:https://dev59.com/IJXfa4cB1Zd3GeqPZAqb#35987804 - Kolya_Net
抱歉,评论已删除。 - Serj Sagan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接