Reputation: 1297
I've been developing with MVC for a few years now and one think I was originally taught was in a HttpPost
action, the first thing you always do is perform a check on ModelState.IsValid, so...
[HttpPost]
public ActionResult Edit(ViewModel form) {
if(ModelState.IsValid) {
// Do post-processing here
}
}
I've come to an issue now where I have a hashed ID passed through the form. If it's 0, its a new record, if its above 0 it means I'm editing. Should my ModelState.IsValid ever return false, I need to setup some dropdown list data again before returning to the Edit view with the same model. To set some of these items that are returned to the form after failure, I need to know the unhashed number, but unhashing it inside the ModelState.IsValid would make it not available in the else statement.
So, is it acceptable to do the following:-
[HttpPost]
public ActionResult Edit(ViewModel form) {
int myID = 0;
if(/** unhashing is successful...**/)
{
if(ModelState.IsValid) {
// Do post-processing here
}
else
{
// Setup dropdowns using unhashed ID and return original view...
}
}
}
Note that ModelState.IsValid is not the first test inside the HttpPost. Is that acceptable? If not, is there a more appropriate way of doing such logic?
Thanks!
Upvotes: 0
Views: 1007
Reputation: 1039238
In your source you seem to have written some comment about unhashing but such term doesn't exist. The purpose of a hash function is to be irreversible. I think that on the other hand you have meant decrypting the query string value. Ideally this decryption should happen in a custom model binder for your view model, setting the ModelState.IsValid
value to false for this parameter. So that inside your controller action all you need to check is this boolean parameter. A controller action should not be decrypting any query string or whatever parameters. This should be done much earlier in the MVC execution pipeline. A custom model binder or even a custom authorization filter would be a much better fit for this scenario.
So let's take an example:
public class ViewModel
{
public int Id { get; set; }
... some other stuff around
}
Now you could write a custom model binder for this view model:
public class MyDecryptingBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.Name == "Id")
{
var idParam = bindingContext.ValueProvider.GetValue("Id");
if (idParam != null)
{
string rawValue = idParam.RawValue as string;
int value;
if (this.DecryptId(rawValue, out value))
{
propertyDescriptor.SetValue(bindingContext.Model, value);
return;
}
}
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
private bool DecryptId(string raw, out int id)
{
// TODO: you know what to do here: decrypt the raw value and
// set it to the id parameter, or just return false if decryption fails
}
}
Now you can register this custom model binder with your view model at bootstrapping time (Application_Start
):
ModelBinders.Binders.Add(typeof(ViewModel), new MyDecryptingBinder());
and then your controller action will be as simple as:
[HttpPost]
public ActionResult Index(ViewModel model)
{
if (ModelState.IsValid)
{
// The decryption of the id parameter has succeeded, you can use model.Id here
}
else
{
// Decryption failed or the id parameter was not provided: act accordingly
}
}
Upvotes: 1