Как продолжать использовать ModelState с RedirectToAction в ASP.NET MVC 6?

У меня есть метод для удаления объекта. Удаление не имеет собственного вида, а представляет собой кнопку "Удалить" в "Редактировать отчет". При успешном удалении перенаправления на "Отчет".

[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 между методами. Я взял отсюда: /questions/24448413/aspnet-mvc-kak-sohranit-oshibki-modelstate-cherez-redirecttoaction/24448444#24448444

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 ответа

Решение

Благодаря ответу я понял, что нужно создать собственный код ASP.NET Core 1.0 (Full .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 (Full .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();
        }
    }

Исправление для компиляции кода приведено ниже, но кажется, что ASP.NET Core не поддерживает сериализацию состояния модели (из-за ModelStateEntry содержащие исключения, которые никогда не сериализуются).

Таким образом, вы не можете сериализовать состояние модели в TempData, И, как объясняется в этой проблеме GitHub, похоже, что нет планов изменить это поведение.


Controller недвижимость в ActionExecutingContext имеет тип 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"]);
        }
    }
}
Другие вопросы по тегам