【问题标题】:Exception treatment (redirection) at viewmodel constructorviewmodel 构造函数中的异常处理(重定向)
【发布时间】:2015-12-10 12:11:12
【问题描述】:

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

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

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

  • index 方法以视图模型为参数。

  • 这是代码的草图:

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

【问题讨论】:

  • 您不应该将模型作为 GET 方法中的参数。以及为什么您要创建一个SelectList,然后从中创建另一个重复的。并且视图模型不应该有访问数据库的代码。
  • 有什么例外?
  • 致斯蒂芬:但现在我遇到了这个问题,你知道解决方法吗? to Niff:异常是打开数据库失败(“无法打开数据库等”)。
  • @galmeida,您的问题是您在这里所做的一切都是错误的。视图模型是一个只包含属性的哑类,不应该访问你的数据库——你甚至不能对这段代码进行单元测试。控制器负责调用您的服务并填充视图模型。并且 GET 方法不应该将您的模型作为参数 - 您在控制器中初始化模型
  • @StephenMuecke 感谢您的评论。请您指出一些使用 ViewModel 的指南吗?我之所以这样说是因为许多页面使用相同的下拉菜单,所以这个视图模型是其他视图模型的基类(我在这里简化了代码,它有 6 个字段,包括数组和另一个类的实例)。将“填充”移动到该视图模型上的方法或基本控制器是否不好?

标签: c# asp.net asp.net-mvc exception-handling lifecycle


【解决方案1】:

捕捉这种情况的最佳方法是创建一个 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());
}

【讨论】:

  • 这行得通,只需要添加 filterContext.ExceptionHandled = true;在 OnException() 结束时。
  • @galmeida 你能把答案标记为正确的吗?
【解决方案2】:

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

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

建议你阅读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&lt;SelectListItem&gt;,然后从第一个IEnumerable&lt;SelectListItem&gt; 创建第二个相同的IEnumerable&lt;SelectListItem&gt;(这只是不必要的额外开销)。

从您的 cmets 中,您已表明这将是一个基本视图模型,因此我建议您使用以下方法拥有一个 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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-08
    • 1970-01-01
    • 2020-12-04
    • 1970-01-01
    相关资源
    最近更新 更多