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

Exception treatment (redirection) at viewmodel constructor

系统采用 Asp.Net MVC 4,C#。

控制器方法执行前抛出异常。我不知道如何处理它 - 我想将用户重定向到错误页面,但我做不到。

我尝试在构造函数中放置一个 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 答案中得到了这个,但它不起作用。执行此块时,用户不会被重定向到错误页面。相反,MyControllers Index 方法继续执行。

捕获此问题的最佳方法是创建一个 ExceptionFilter

public class CustomExceptionFilter : IExceptionFilter
{    
        public void OnException(ExceptionContext filterContext)
        {

            if (filterContext.ExceptionHandled)
                return;    

            //Do yout logic here
        }
}

并在 FilterConfig.cs

的 RegisterGlobalFilters 中进行全局注册
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new CustomExceptionFilter());
}

虽然您可以使用 ExceptionFilter,但没有必要。这里真正的问题是您错误地使用了视图模型。视图模型应仅包含视图中 display/edit 所需的属性,不应访问数据库。这有两个原因

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

建议您阅读What is ViewModel in MVC?

中的答案

接下来,您的 GET 方法不应包含您模型的参数。这有两个原因

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

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

最后,模型构造函数中用于生成 SelectList 的代码正在生成一个 IEnumerable<SelectListItem>,然后从第一个 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 方法中需要的 SelectLists 和视图需要的 POST 方法被return编辑。