Displaying modelstate error - asp.net-mvc-3

I am having the following code, However the errors causght not being displayed. What is wrong ?
public ActionResult DeleteRateGroup(int id)
{
try
{
RateGroup.Load(id).Delete();
RateGroupListModel list = new RateGroupListModel();
return GetIndexView(list);
}
catch (Exception e)
{
RateGroupListModel model = new RateGroupListModel();
if (e.InnerException != null)
{
if (e.InnerException.Message.Contains("REFERENCE constraint"))
ModelState.AddModelError("Error", "The user has related information and cannot be deleted.");
}
else
{
ModelState.AddModelError("Error", e.Message);
}
return RedirectToAction("RateGroup", model);
}
}
#model MvcUI.Models.RateGroupListModel
#{
View.Title = "RateGroup";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Rate Group</h2>
#Html.ValidationSummary()
#using (Html.BeginForm())
private ActionResult GetIndexView(RateGroupListModel model)
{
return View("RateGroup", model);
}
public ActionResult RateGroup(RateGroupListModel model)
{
return GetIndexView(model);
}

It looks like you're setting the ModelState error, then redirecting to another action. I'm pretty sure the ModelState gets lost when you do that.
Typically, you'd just render the RateGroup view directly from the DeleteRateGroup action, without the redirect, passing in your model if needed, like this:
return View("RateGroup", model);
If you want the ModelState to come along to the second action with you, take a look at MvcContrib's ModelStateToTempDataAttribute. Here's the attribute's description, from the MvcContrib source code's comments:
When a RedirectToRouteResult is returned from an action, anything in the ViewData.ModelState dictionary will be copied into TempData. When a ViewResultBase is returned from an action, any ModelState entries that were previously copied to TempData will be copied back to the ModelState dictionary.

Related

Post-Redirect-Get issue in MVC3

I need to use Post-Redirect-Get to show data, which I entered in "Information" page on "Index" page. I have the following methods, but it doesn't work. It doesn't even redirect me on submit. What am I doing wrong?
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
return View();
}
public ActionResult Information()
{
return View();
}
//Get info
[HttpGet]
public ActionResult Submit(Models.Information FirstName,
Models.Information LastName,
Models.Information DateOfBirth,
Models.Information HourOfBirth,
Models.Information NumberOfKids,
Models.Information Emso,
Models.Information Email,
Models.Information PlaceOfBirth)
{
if (ModelState.IsValid)
{
Models.Information info = new Models.Information();
info.FirstName = FirstName.ToString();
info.LastName = LastName.ToString();
info.DateOfBirth = Convert.ToDateTime(DateOfBirth);
info.HourOfBirth = Convert.ToDateTime(HourOfBirth);
info.NumberOfKids = Convert.ToInt32(NumberOfKids);
info.Emso = Emso.ToString();
info.Email = Email.ToString();
info.PlaceOfBirth = PlaceOfBirth.ToString();
TempData["info"] = info;
return RedirectToAction("Summary");
}
return View();
}
//Show info
[HttpPost]
public ActionResult Post(Models.Information info)
{
info.FirstName = ViewData["FirstName"].ToString();
info.LastName = ViewData["LastName"].ToString();
info.DateOfBirth = Convert.ToDateTime(ViewData["DateOfBirth"]);
info.HourOfBirth = Convert.ToDateTime(ViewData["HourOfBirth"]);
info.NumberOfKids = Convert.ToInt32(ViewData["NumberOfKids"]);
info.Emso = ViewData["Emso"].ToString();
info.Email = ViewData["Email"].ToString();
info.PlaceOfBirth = ViewData["PlaceOfBirth"].ToString();
return View();
}
}
I try to show data on Index page like this:
First name: <input type='text' runat="server" value="#ViewData["FirstName"]" /><br />
Your code looks sort of confusing to me and I am not sure what you are trying to accomplish. But first, let's use an example on what I think you are trying to accomplish.
User enters information on the information action/view
(POST)This is POSTed to a controller/action which then verifies the model and puts in into the ViewData (REDIRECT TO STEP 3)
(GET)You want to redisplay this data on a summary page.
Under those assumptions, lets clean up your code a bit, leverage the use of the ViewData and TempData objects and see if we can make this work.
Information Action
public ActionResult Information()
{
//In a strongly typed view, typically you would send an empty model
//so the model binder/Html.Input helpers have something to bind to
return View(new Models.Information());
}
Information POST to handle incoming data
[HttpPost]
public ActionResult Information(Models.Information info)
{
//Yup - you can use the same action name as the GET action above
//You can name this anything you want, but I typically keep the same names so
//I know where the data came from
//I think your assignments are backwards. info came into the controller action
//You want to save this as ViewData. Also, let's do that in one line of code
//And since we are redirecting, lets use the TempData dictionary
//Notice the method type decorator above [HttpPost]. This is the P in PRG (Post)
if(modelState.IsValid()){
TempData["info"] = info;
//Notice the name of the ActionMethod below. The is the R in PRG (Redirect)
return RedirectToAction("Summary");
}
//There were errors, lets send back to the Information view
return View(info);
}
Summary Action
public ActionResult Summary()
{
//We were redirected here, and this is a GET method. This is the G in PRG
//(GET)
//lets go ahead and set the TempData stuff to a model, it just looks nicer on
//the view
var model = TempData["info"];
return View(model);
}
As for the views, here is a snippet of what each might look olike
Information View
#model Models.Information
#{using(Html.BeginForm()){
#Html.LabelFor(x=>x.FirstName)<br/>
#Html.TextBoxFor(x=>x.Firstname)
//Repeat for each property
<input type="Submit" value="Submit"/>
}}
Summary View
#model Models.Information
#Html.LabelFor(x=>x.FirstName)<br/>
#Html.DisplayFor(x=>x.Firstname)
//Repeat for each property

TextBoxFor on a boolean field renders the same value even if it was modified on controller side

I have a simple form with a textbox (and a model editor I want to render in specific cases)
#using (Html.BeginForm("Import", "Flow"))
{
#Html.TextBoxFor(model => model.IsConfirmed)
#if (Model.IsConfirmed)
{
#Html.EditorFor(m => m.Preview)
}
}
The model used in this view is the following
public class ImportViewModel
{
public Boolean IsConfirmed { get; set; }
public PreviewViewModel Preview { get; set; }
public ImportViewModel()
{
this.IsConfirmed = false;
}
}
The form posts on the following controller
public class FlowController
{
[HttpPost]
public ActionResult Import(ImportViewModel model)
{
try
{
if (ModelState.IsValid)
{
if (model.IsConfirmed)
{
// do something else
}
else
{
model.Preview = Preview(model.strCA, model.SelectedAccount);
model.IsConfirmed = true;
return View(model);
}
}
}
catch (Exception ex)
{
throw new Exception("arf", ex);
}
return RedirectToAction("Index", "Home");
}
}
On first load, the textbox contains "false"
When posted, the property IsConfirmed of the model is set to "true" and this model is passed to the same view.
I expect the textbox to be "true" but it is still "false"... moreover the Preview property is correctly rendered, so it means Model.IsConfirmed is indeed true...
Am I missing something ?
Thanks
Make sure you remove the value from the ModelState if you intend to modify it:
ModelState.Remove("IsConfirmed");
model.IsConfirmed = true;
The reason you need to do that is because, by design, all Html helpers (such as TextBoxFor) will first look for a value in the ModelState when binding and only not found they will use the value on your model. And since there's a value with the same name in the ModelState (coming from the POST request), that's what's being used.

Partial View Validation Without JavaScript

I have a partial view in which there is a form. I POST this form using the PRG pattern. I am using the AjaxHelper to create my form. I also need this form to work without javascript. The problem is that when model validation fails, it always changes the url to my partial view.
public ActionResult PostForm(PostFormModel postFormModel)
{
if (ModelState.IsValid)
{
return RedirectToAction("SomewhereElse");
}
else
{
if (Request.IsAjaxRequest())
{
return PartialView("_PostForm")
}
else
{
// What do I do here?
}
}
}
Here's what I have tried:
return PartialView("_PostForm", postFormModel);
This just renders the partial view and doesn't contain any of the parent stuff.
return View("Index", new ParentModel() { PostFormModel = postFormModel });
This actually produces the correct result. It displays the parent view, but the URL is that of the partial http://localhost:22485/Controller/PostForm! I feel like this is really close to the solution. What now?
If you want to change url, you should redirect to another action (using PRG pattern). Insert next code instead of '// What do I do here?':
postModelService.Save(postFormModel); //to Session or to DB
return RedirectToAction("Parent");
New action should look like this:
public ActionResult Parent()
{
var postFormModel = postModelService.Load();
return View("Index", new ParentModel() { PostFormModel = postFormModel });
}
Hope it helps.

Invalid ModelState with PartialViews

I have the following action
[GET("Foo")]
public virtual ActionResult Foo()
{
return View(new FooViewModel());
}
the view for this action calls this partial view
#{ Html.RenderAction(MVC.FooBar.AddFoo()); }
with controller actions
[ChildActionOnly]
[GET("Foo/Add")]
public virtual ActionResult AddFoo()
{
var viewModel = new AddFooViewModel();
return PartialView(viewModel);
}
[POST("Foo/Add")]
public virtual ActionResult AddFooPost(AddFooViewModel viewModel)
{
// If ModelState is invalid, how do I redirect back to /Foo
// with the AddFooViewModel ModelState intact??
if (!ModelState.IsValid)
return MVC.FooBar.Foo();
// ... persist changes and redirect
return RedirectToAction(MVC.FooBar.Foo());
}
If somebody submits the AddFoo form with ModelState errors, I want the POST action to redirect back to /Foo and show the AddFoo partial view with the ModelState errors. What's the best approach to handle this?
I think you could achieve that in 2 ways:
Using session state
Passing your data using querystring params
I prefer the second option.
I ended up putting the viewmodel into TempData like this with the ModelStateToTempData attribute on the controller
[ChildActionOnly]
[GET("Foo/Add")]
public virtual ActionResult AddFoo()
{
var viewModel = TempData["AddFooViewModel"] as AddFooViewModel ?? new AddFooViewModel();
return PartialView(viewModel);
}
[POST("Foo/Add")]
public virtual ActionResult AddFooPost(AddFooViewModel viewModel)
{
// If ModelState is invalid, how do I redirect back to /Foo
// with the AddFooViewModel ModelState intact??
if (!ModelState.IsValid)
{
TempData["AddFooViewModel"] = viewModel;
return RedirectToAction(MVC.FooBar.Foo());
}
// ... persist changes and redirect
return RedirectToAction(MVC.FooBar.Foo());
}

RedirectToAction after validation errors

If I have the usual Edit actions, one for GET to retrieve an object by it's ID and to display it in an edit form. The next for POST to take the values in the ViewModel and update the object in the database.
public virtual ActionResult Edit(int id)
[HttpPost]
public ActionResult Edit(VehicleVariantEditSaveViewModel viewModel)
If an error occurs during model binding in the POST action, I understand I can RedirectToAction back to the GET action and preserve the ModelState validation errors by copying it to TempData and retrieving it after the redirect in the GET action.
if (TempData["ViewData"] != null)
{
ViewData = (ViewDataDictionary)TempData["ViewData"];
}
How do I then convert that ViewData, which includes the previous invalid ModelState, into a new model to send to the view so the user sees their invalid input with validation warnings? Oddly enough if I pass in a new instance of my ViewModel retrieved from the database (with the original valid data) to the View() this is ignored and the (invalid) data in the ViewData is displayed!
Thanks
I had a similar problem and decided to use the following pattern:
public ActionResult PersonalRecord(Guid id)
{
if (TempData["Model"] == null)
{
var personalRecord = _context.PersonalRecords.Single(p => p.UserId == id);
var model = personalRecord.ToPersonalRecordModel();
return View(model);
}
else
{
ViewData = (ViewDataDictionary) TempData["ViewData"];
return View(TempData["Model"]);
}
}
[HttpPost]
public ActionResult PersonalRecord(PersonalRecordModel model)
{
try
{
if (ModelState.IsValid)
{
var personalRecord = _context.PersonalRecords.Single(u => u.UserId == model.UserId);
personalRecord.Email = model.Email;
personalRecord.DOB = model.DOB;
personalRecord.PrimaryPhone = model.PrimaryPhone;
_context.Update(personalRecord);
_context.SaveChanges();
return RedirectToAction("PersonalRecord");
}
}
catch (DbEntityValidationException ex)
{
var errors = ex.EntityValidationErrors.First();
foreach (var propertyError in errors.ValidationErrors)
{
ModelState.AddModelError(propertyError.PropertyName, propertyError.ErrorMessage);
}
}
TempData["Model"] = model;
TempData["ViewData"] = ViewData;
return RedirectToAction("PersonalRecord", new { id = model.UserId });
}
Hope this helps.
I noticed that the Model is included in ViewData so you don't need to pass it in addition to the ViewData, what I don't understand is how you get at it to then return it to the view.
public ViewResult Edit(int id)
{
// Check if we have ViewData in the session from a previous attempt which failed validation
if (TempData["ViewData"] != null)
{
ViewData = (ViewDataDictionary)TempData["ViewData"];
}
VehicleVariantEditViewModel viewModel = new VehicleVariantControllerViewModelBuilder()
.BuildForEdit(id);
return View(viewModel);
}
The above works but obviously it's making an unnecessary call to the database to build a new Model (which gets automagically overwritten with the invalid values from the Model in the passed ViewData)
Confusing.

Resources