While you could use an ExceptionFilter
, its is unnecessary. The real problem here is you incorrect use of a view model. A view model should contain only properties your need for display/edit in the view and should not access the database. Two reasons for this are
- You can not unit test the model or any component of your app,
including the controller, that uses the model. Even though its clear
your not yet into unit testing, you should at least design for it (I
guarantee that once you do, you it will become an integral part of
your development).
- Because your will be posting back your view model, it means that the
DefaultModelBinder
will initialize the model and call its
constructor which in turn calls the database to populate your
SelectList
. The only reason you should need the SelectList
in
the POST method is because ModelState
is invalid and you need to
return the view. If client side validation is enabled, this would be
rare, so you unnecessarily degrading performance by making database
calls for data that wont be used.
Suggest you read the answers in What is ViewModel in MVC?
Next, your GET method should not contain a parameter for you model. Two reasons for this are
- The
DefaultModelBinder
is initializing your model and it
adds the values of your model properties to ModelState
and if your
properties contain any validation attributes, then ModelState
will
be invalid. The side affect will be that any validation errors will
be displayed in the initial view, and any attempt to set the value
of your properties in the GET method will be ignored by HtmlHelper
methods because they use the values from ModelState
in preference
to the model properties. To overcome this, you would need to use the
ModelState.Clear()
hack, effectively undoing what the
ModelBinder
has just done. Again its just pointless extra
overhead.
- Because you cannot have the same signature for the GET and POST
method, you need to rename the POST method and use the overload of
BeginForm()
that specifies the action method name.
Instead, you should be initializing an instance of the view model inside the GET method.
Finally the code in your models constructor to generate the SelectList
is generating one IEnumerable<SelectListItem>
and then creating a second identical IEnumerable<SelectListItem>
from the first one (again its just unnecessary extra overhead).
From your comments, you have indicated that this will be a base view model, so I would suggest that you have a BaseController
with the following method
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 });
}
where BaseVM
is
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
}
and then in the concrete controller
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
}
Similarly you might have aprivate void ConfigureConcreteViewModel(yourConcreteModel model)
method in each concrete controller that assigns common values such as SelectLists
that are needed in the GET method and POST method if the view needs to be returned.