开发者

how can i keep my url when my validation fail in asp.net mvc controller action

if i start off on a Detail page:

http:\\www.my开发者_运维百科site.com\App\Detail

i have a controller action called Update which normally will call redirectToAction back to the detail page. but i have an error that is caught in validation and i need to return before redirect (to avoid losing all of my ModelState). Here is my controller code:

 public override ActionResult Update(Application entity)
    {
        base.Update(entity);
        if (!ModelState.IsValid)
        {
            return View("Detail", GetAppViewModel(entity.Id));
        }
      return RedirectToAction("Detail", new { id = entity.Id }) 

but now I see the view with the validation error messages (as i am using HTML.ValidationSummary() ) but the url looks like this:

http:\\www.mysite.com\App\Update

is there anyway i can avoid the URL from changing without some hack of putting modelstate into some temp variables? Is there a best practice here as the only examples i have seen have been putting ModelState in some tempdata between calling redirectToAction.


As of ASP.NET MVC 2, there isn't any such API call that maintains the URL of the original action method when return View() is called from another action method.

Therefore as such, the recommended solution and a generally accepted convention in ASP.NET MVC is to have a corresponding, similarly named action method that only accepts a HTTP POST verb. So in your case, having another action method named Detail like so should solve your problem of having a different URL when validation fails.

[HttpPost]
public ActionResult Detail(Application entity)
{
    base.Update(entity);
    if (ModelState.IsValid)
    {
        //Save the entity here
    }
   return View("Detail", new { id = entity.Id });
}  

This solution is in line with ASP.NET MVC best practices and also avoids having to fiddle around with modestate and tempdate.

In addition, if you haven't explored this option already then client side validation in asp.net mvc might also provide for some solution with regards to your URL problem. I emphasize some since this approach won't work when javascript is disabled on the browser.

So, the best solution would be have an action method named Detail but accepting only HTTP POST verb.


The problem here is actually caused by your implementation. This doesn't answer your question, but it describes where you've gone wrong in the first place.

If you want a page that is used to update or edit an item, the URL should reflect this. For example.

You visit http:\www.mysite.com\App\Detail and it displays some information about something. That is what the URL describes it is going to do. In your controller, the Detail() method would return the Detail.aspx view.

To edit an item, you would visit http:\www.mysite.com\App\Edit and change the information you wish to update, the form would post back to the same URL - you can handle this in the controller with these methods:

[HttpGet]
public ActionResult Edit() {
    MyModel model = new MyModel();
    ...
    return View(model);
}

[HttpPost]
public ActionResult Edit(MyModel model) {
    ...
    if (ModelState.IsValid) {
        // Save and redirect
        ...
        return RedirectToAction("Detail");
    }
    return View(model);
}

If you ever find yourself doing this...

return View("SomeView", model);

You are making your own life harder (as well as breaking the principles behind URLs).

If you want to re-use part of a view, make it a partial view and render it inside of the view that is named after the method on the controller.

I apologise that this potentially isn't very helpful, but you are falling into an MVC anti-pattern trap by displaying the same view from a differently named method.


As @Malcolm sais, best practice is to put ModelState in TempData, but don't do it manually! If you'd do this manually in every controller action where it's relevant, you would introduce immense amounts of repeated code, and increase the maintenance cost vastly.

Instead, implement a couple of attributes that do the job for you. Kazi Manzur has an approach (scroll down to the end of the post) that has been widely spread, and Evan Nagle shows an implementation with tests that is essentially the same as Kazi's, but with different names. Since he also provides unit tests that make sure the attributes work, implementing them in your code will mean little or no maintenance cost. The only thing you'll have to keep track of is that the controller actions are decorated with the appropriate attributes, which can also be tested.

When you have the attributes in place, your controller might look something like this (I deliberately simplified, because I don't know the class you inherit from):

[HttpPost, PassState]
public ActionResult Update(EntityType entity)
{
    // Only update if the model is valid
    if (ModelState.IsValid) {
        base.Update(entity);
    }
    // Always redirect to Detail view.
    // Any errors will be passed along automagically, thanks to the attribute.
    return RedirectToAction("Detail", new { id = entity.Id });
}

[HttpGet, GetState]
public ActionResult Detail(int id)
{
    // Get stuff from the database and show the view
    // The model state, if there is any, will be imported by the attribute.
}

You mention that you feel putting ModelState in TempData feels like a "hack" - why? I agree with you that doing it with repeated code in every single controller action seems hacky, but that's not what we're doing here. In fact, this is exactly what TempData is for. And I don't think the above code looks hacky... do you?

Although there are solutions to this problem that might appear simpler, such as just renaming the action method to preserve the URL, I would strongly advise against that approach. It does solve this problem, but introduces a couple of others - for example, you'll still have no protection against double form submission, and you'll have pretty confusing action names (where a call to Detail actually changes stuff on the server).


The best practice you ask for is actually what you explained not to do: putting modelstate into tempdata. Tempdata is meant for it, that's why I would not call it a hack.

If this is to much repetitive code you could use the attribute modeldatatotempdata of MVCContrib. But the store is still TempData.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜