视图模型构造函数中的异常处理(重定向)
本文关键字:重定向 异常处理 模型 构造函数 视图 | 更新日期: 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
,但它是不必要的。这里真正的问题是视图模型的使用不正确。视图模型应仅包含在视图中显示/编辑所需的属性,并且不应访问数据库。造成这种情况的两个原因是
- 不能对模型或应用的任何组件进行单元测试,包括使用该模型的控制器。即使它很清楚你还没有进入单元测试,你至少应该为它设计(我保证一旦你这样做,你将成为不可或缺的一部分您的发展(。
- 因为您将回发视图模型,这意味着
DefaultModelBinder
将初始化模型并调用其构造函数,它反过来调用数据库来填充您的SelectList
.您需要SelectList
的唯一原因POST 方法是因为ModelState
无效,您需要返回视图。如果启用了客户端验证,这将是很少见,因此通过制作数据库不必要地降低性能调用不会使用的数据。
建议您阅读 MVC 中的 ViewModel 是什么?
接下来,GET 方法不应包含模型的参数。造成这种情况的两个原因是
DefaultModelBinder
正在初始化您的模型,并且它将模型属性的值添加到ModelState
,并且如果属性包含任何验证属性,则ModelState
将无效。副作用将是任何验证错误都会显示在初始视图中,以及任何设置值的尝试的 GET 方法中的属性将被忽略HtmlHelper
方法,因为它们优先使用ModelState
中的值到模型属性。要克服此问题,您需要使用ModelState.Clear()
黑客,有效地撤消了什么ModelBinder
刚刚完成。再次它只是毫无意义的额外开销。- 因为 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
(如果需要返回视图(。