视图模型构造函数中的异常处理(重定向)

本文关键字:重定向 异常处理 模型 构造函数 视图 | 更新日期: 2023-09-27 18:26:21

系统位于 MVC 4 Asp.Net C# 中。

如果在执行控制器方法之前引发异常。我不知道如何处理它 - 我想将用户重定向到错误页面,但我不能。

  • 我有一个基本的 ViewModel 类,其中包含要在下拉列表中使用的 SelectList。在其构造函数中,ViewModel 从数据库中获取其 SelectListItems。这是异常的来源。

  • 索引方法将视图模型作为参数。

  • 这是代码的草图:

    class MyViewModel{
      public SelectList SelectListModel { get; set; }
      public MyViewModel()
      {
          List<X> xs = GetItemsFromDB(); // <= Exception thrown here
          List<SelectListItem> SelectListContent = new List<SelectListItem>();
          foreach(X x in xs)
          {
               SelectListContent.Add(new SelectListItem( Value = x.value,Text=x.text); 
          } 
          SelectListModel = new SelectList(SelectListContent , "Value", "Text"); 
      }  
    }
     public class MyController : Controller
    {
       public ActionResult Index(MyViewModel model) //<< Exception thrown before entering method
       { 
        //do something
       }
    }
    

我试图在构造器中放置一个 try-catch,并在 catch 中使用以下代码:

            var context = new HttpContextWrapper(HttpContext.Current);
            var rc = new RequestContext(context, new RouteData());
            var urlHelper = new UrlHelper(rc);
            context.Response.Redirect(urlHelper.Action("Index", "Error", new { messagem = x.Message }), false);
            HttpContext.Current.ApplicationInstance.CompleteRequest();

我从其他 SO 答案中获取了这个,但它不起作用。执行此块时,用户不会重定向到错误页面。相反,我的控制器索引方法继续执行。

视图模型构造函数中的异常处理(重定向)

捕获此问题的最佳方法是创建一个异常筛选器

public class CustomExceptionFilter : IExceptionFilter
{    
        public void OnException(ExceptionContext filterContext)
        {
            if (filterContext.ExceptionHandled)
                return;    
            //Do yout logic here
        }
}

并将其全局注册,在 FilterConfig 的 RegisterGlobalFilters 中.cs

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new CustomExceptionFilter());
}

虽然你可以使用ExceptionFilter,但它是不必要的。这里真正的问题是视图模型的使用不正确。视图模型应仅包含在视图中显示/编辑所需的属性,并且不应访问数据库。造成这种情况的两个原因是

  1. 不能对模型或应用的任何组件进行单元测试,包括使用该模型的控制器。即使它很清楚你还没有进入单元测试,你至少应该为它设计(我保证一旦你这样做,你将成为不可或缺的一部分您的发展(。
  2. 因为您将回发视图模型,这意味着 DefaultModelBinder将初始化模型并调用其构造函数,它反过来调用数据库来填充您的 SelectList .您需要SelectList的唯一原因POST 方法是因为ModelState无效,您需要返回视图。如果启用了客户端验证,这将是很少见,因此通过制作数据库不必要地降低性能调用不会使用的数据。

建议您阅读 MVC 中的 ViewModel 是什么?

接下来,GET 方法不应包含模型的参数。造成这种情况的两个原因是

  1. DefaultModelBinder正在初始化您的模型,并且它将模型属性的值添加到ModelState,并且如果属性包含任何验证属性,则ModelState将无效。副作用将是任何验证错误都会显示在初始视图中,以及任何设置值的尝试的 GET 方法中的属性将被忽略HtmlHelper方法,因为它们优先使用ModelState中的值到模型属性。要克服此问题,您需要使用 ModelState.Clear()黑客,有效地撤消了什么 ModelBinder刚刚完成。再次它只是毫无意义的额外开销。
  2. 因为 GET 和 POST 不能使用相同的签名方法,则需要重命名 POST 方法并使用重载 BeginForm()指定操作方法名称。

相反,您应该在 GET 方法中初始化视图模型的实例。

最后,模型构造函数中用于生成SelectList的代码生成一个IEnumerable<SelectListItem>,然后从第一个创建第二个相同的IEnumerable<SelectListItem>(同样,它只是不必要的额外开销(。

从您的评论中,您已指出这将是一个基本视图模型,因此我建议您使用以下方法进行BaseController

protected void ConfigureBaseViewModel(BaseVM model)
{
  List<X> xs = GetItemsFromDB();
  model.SelectListModel = new SelectList(xs, "value", "text");
  // or model.SelectListModel = xs.Select(x => new SelectListItem{ Value = x.value, Text=x.text });
}

BaseVM在哪里

public abstract class BaseVM
{
  [Required(ErrorMessage = "Please select an item")] // add other display and validation attributes as necessary
  public int SelectedItem { get; set; } // or string?
  public IEnumerable<SelectListItem> SelectListModel { get; set; }
  .... // other common properties
}

然后在混凝土控制器中

public ActionResult Index()
{
  var model = new yourConcreteModel();
  ConfigureBaseViewModel(model);
  return View(model);
}
[HttpPost]
public ActionResult Index(yourConcreteModel model)
{
  if (!ModelState.IsValid)
  {
    ConfigureBaseViewModel(model);
    return View(model);
  }
  // save and redirect
}

同样,您可能在每个具体控制器中都有一个private void ConfigureConcreteViewModel(yourConcreteModel model)方法,该方法分配公共值,例如 GET 方法和 POST 方法中所需的SelectLists(如果需要返回视图(。