Retornando uma visão com seu modelo de um ActionFilterAttribute

Ao implementar a manipulação de erros usando os auxiliares de validação incorporados em uma visualização fortemente tipada, você geralmente cria um bloco try / catch dentro do controlador e retorna uma visão com seu modelo correspondente como um parâmetro para o método View() :

O controlador

 public class MessageController : Controller { [AcceptVerbs(HttpVerbs.Post)] public ActionResult Create(Models.Entities.Message message) { try { // Insert model into database var dc = new DataContext(); dc.Messages.InsertOnSubmit(message); dc.SubmitChanges(); return RedirectToAction("List"); } catch { /* If insert fails, return a view with it's corresponding model to enable validation helpers */ return View(message); } } } 

A vista

 <%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %>   

Eu implementei um manipulador de erro simples na forma de um ActionFilterAttribute, que será capaz de redirect para uma exibição de erro genérica ou redirect para a exibição que lançou uma exceção e permitir que os ajudantes de validação ganhem vida.

Veja como meu ActionFilterAttribute é exibido:

 public class ErrorLoggingAttribute : ActionFilterAttribute, IExceptionFilter { private Boolean _onErrorRedirectToGenericErrorView; ///  /// True: redirect to a generic error view. /// False: redirect back the view which threw an exception ///  public ErrorLoggingAttribute(Boolean onErrorRedirectToGenericErrorView) { _onErrorRedirectToGenericErrorView = onErrorRedirectToGenericErrorView; } public void OnException(ExceptionContext ec) { if (_onErrorRedirectToGenericErrorView) { /* Redirect back to the view where the exception was thrown and include it's model so the validation helpers will work */ } else { // Redirect to a generic error view ec.Result = new RedirectToRouteResult(new RouteValueDictionary { {"controller", "Error"}, {"action", "Index"} }); ec.ExceptionHandled = true; } } } 

Redirecionando para a visão que lançou a exceção é bastante simples. Mas aqui está o kicker: Para que os ajudantes de validação funcionem, você precisa fornecer a visão com o seu modelo.

Como você retornaria a exibição que lançou uma exceção e forneceria a exibição com o modelo correspondente? (Neste caso Models.Entities.Message ).

Eu tenho que trabalhar!

Por alguma razão estranha, tudo que eu precisava fazer era passar o ViewData para um novo ResultView .

Aqui está o código completo:

 public class ErrorLoggingAttribute : ActionFilterAttribute, IExceptionFilter { private String _controllerName, _actionName; private Boolean _redirectToGenericView = false; public ErrorLoggingAttribute() { } public ErrorLoggingAttribute(String actionName, String controllerName) { _controllerName = controllerName; _actionName = actionName; _redirectToGenericView = true; } void IExceptionFilter.OnException(ExceptionContext ec) { // log error if (_redirectToGenericView) { ec.Result = new RedirectToRouteResult(new RouteValueDictionary { {"controller", _controllerName}, {"action", _actionName} }); } else { ec.Result = new ViewResult { ViewName = ((RouteData) ec.RouteData).Values["action"].ToString(), TempData = ec.Controller.TempData, ViewData = ec.Controller.ViewData }; } ec.ExceptionHandled = true; } } 

Uso

Veja como você usaria o atributo em um controller-action, para redirect para a mesma view ( com seu modelo associado) para permitir que os auxiliares de validação padrão entrem em ação, quando ocorrer uma exceção:

 [ErrorLogging] [AcceptVerbs(HttpVerbs.Post)] public ActionResult Create(Models.Entities.Message message) { var dc = new Models.DataContext(); dc.Messages.InsertOnSubmit(message); dc.SubmitChanges(); return RedirectToAction("List", new { id = message.MessageId }); } 

E aqui está como você usaria o atributo, para redirect para uma exibição genérica, quando ocorre uma exceção:

 [ErrorLogging("ControllerName", "ViewName")] [AcceptVerbs(HttpVerbs.Post)] public ActionResult Create(Models.Entities.Message message) 

Esta é uma separação completa da lógica. Nada no controle, mas o básico.

Desde que você herda de ActionFilterAttribute de OnActionExecuting, você pode pegar seu modelo.

  public override void OnActionExecuting(ActionExecutingContext filterContext) { var model = filterContext.Controller.ViewData.Model as YourModel; ... } 

Mas já existe HandleError definido no sistema MVC, por que você não usa este em vez de assar o seu próprio.

Eu sugiro que você leia este blog sobre esta questão.

Se sua ação lançar uma exceção, não há como passar o modelo para a visualização, pois o modelo provavelmente ainda não foi criado – ou não foi totalmente criado. É provavelmente por isso que o resultado é nulo. Você não pode confiar nos dados depois que a exceção foi lançada.

Mas você pode passar o modelo “padrão” para o seu filtro de ação como este:

 [ErrorLogging(new EmptyModel())] // or to create using Activator [ErrorLogging(typeof(EmptyModel))] // or even set view name to be displayed [ErrorLogging("modelerror", new EmptyModel())] 

Dessa forma, o filtro passará por esse “modelo de erro” que você definiu explicitamente para ser exibido quando ocorreu um erro.

 public class MessageController : Controller { public ActionResult Create() { return View(); } [AcceptVerbs(HttpVerbs.Post)] public ActionResult Create( Message message ) { try { // Exceptions for flow control are so .NET 1.0 =) // ... your save code here } catch { // Ugly catch all error handler - do you really know you can fix the problem? What id the database server is dead!?! return View(); } } } 

Os detalhes do modelo já estão presentes no modelstate. Quaisquer erros também devem estar presentes no modelstate. Seu manipulador de exceção só precisa lidar com o caso em que você deseja redirect para uma página de erro genérica. Melhor / mais óbvio é jogar o atributo fora e se você quiser redirect na captura, retorne um resultado de redirecionamento.