Why is ModelStateToTempData Attribute from MVCContrib not working? - asp.net-mvc

I'm simply trying to pass the ModelState from one action to another in the same controller, for validation purposes. However, the model state does not get updated. I see that TempData["____MvcContrib_ValidationFailures____"] contains the ModelStateDictionary from the forwarding Action, but I assumed this should get transfered into my current ModelState automatically? Where am I going wrong?
I'm using ASP.NET MVC2 and MVCContrib 2.0.36.0. I have also tried decorating the Controller with this attribute, but the results are the same.
Code:
[HttpGet]
[ModelStateToTempData]
public ActionResult NewsEventsSignup()
{
var newsEventsSignupDetails = this.TempData.GetItem<NewsEventsSignupDetails>();
var viewModel = _newsEventsSignupPageViewModelMapper.MapFrom(newsEventsSignupDetails);
return this.View(viewModel);
}
[HttpPost]
[ModelStateToTempData]
[ValidateAntiForgeryToken]
public ActionResult NewsEventsSignup(NewsEventsSignupFormViewModel newsEventsSignup)
{
ActionResult resultToReturn;
var newsEventsSignupDetails = _newsEventsSignupDetailsMapper.MapFrom(newsEventsSignup);
try
{
_newsEventsSignupTasks.SignupForNewsAndEvents(newsEventsSignupDetails);
resultToReturn = this.RedirectToAction(x => x.Index());
}
catch (RulesException e)
{
e.AddModelStateErrors(this.ModelState); // from xVal
this.TempData.AddItem(newsEventsSignupDetails); // for showing invalid input
resultToReturn = this.RedirectToAction(x => x.NewsEventsSignup());
}
return resultToReturn;
}

How do you check that ModelState is not filled? This is an OnActionExecuted filter, so it is only filled, when the Action finished. You can not check the value in the action.
The easiest way to validate that an ModelState realy has an error is to put a validation summary on the view.
To see that your error is not xval related I would try
ModelState.AddModelError("TestError", "This is an errortest");
In NewsEventsSignup before redirecting.
Also dont try to acces TempData in the debugger or in some debug-code. It is deleted the first time you access it.

Related

Validation between controllers

I have a HomeController with an Index.cshtml Razor view that uses an InitialChoicesViewModel with validation attributes. The Index view contains the following form:
#using (Html.BeginForm("CreateCharacter", "DistributePoints", FormMethod.Get))
This goes to a different controller (which is what I want):
public class DistributePointsController : Controller
{
public ActionResult CreateCharacter(/* my form parameters */)
// ...
}
How do I perform server-side validation on the form (such as checking ModelState.IsValid), returning my original Index view with a correct ValidationSummary on error? (On success I want to return the CreateCharacter view of the other controller.)
Based on John H's answer, I resolved this as follows:
#using (Html.BeginForm("CreateCharacter", "Home"))
HomeController:
[HttpPost]
// Only some of the model fields are posted, along with an additional name field.
public ActionResult CreateCharacter(InitialChoicesViewModel model, string name)
{
if (ModelState.IsValid)
{
return RedirectToAction("CreateCharacter", "DistributePoints",
new {name, model.Level, model.UseAdvancedPointSystem});
}
// Unsure how to post a collection - easier to reload from repository.
model.ListOfStuff = _repository.GetAll().ToList();
return View("Index", model);
}
I had to add a parameterless constructor to my view model, too.
[HttpPost]
public ActionResult CreateCharacter(InitialChoicesViewModel model)
{
if (ModelState.IsValid)
return RedirectToAction("SomeSuccessfulaction");
return View("~/Views/Home/Index.cshtml", model);
}
The ~/ denotes the relative root of your site.
The code above complies with the Post-Redirect-Get pattern, in order to prevent some types of duplicate form submission problems. It does that by redirecting to a separate action when the form submission is successful, and by returning the current view, complete with ModelState information, on error.
By default, ASP.NET MVC checks first in \Views\[Controller_Dir]\, but after that, if it doesn't find the view, it checks in \Views\Shared.
If you do return View("~/Views/Wherever/SomeDir/MyView.aspx") You can return any View you'd like.
But for now in your case, try the following
public ActionResult CreateCharacter(SomeModel model)
{
if(!ModelState.IsValid){
return View("~/Views/Home/Index.cshtml", model )
}
return View();
}
To check your ModelState just use an if statement in Controller:
if(ModelState.IsValid)
{
...
}
If there is any error add you can add an error message to the ModelState Dictionary like this:
ModelState.AddModelError("Somethings failed", ErrorCodeToString(e.StatusCode));
After that return your same View and pass it to your model
return View(model);
If you add "#Html.ValidationSummary()" in your View, it will get the errors from the ModelState Dictionary and display them.But if you show values yourself maybe with different styles you can do it manually, take a look at this question
And if there is no error you can return your CreateCharacter View like this, just redirect user to the appropriate action:
return RedirectToAction("CreateCharacter","DistributePoints");

Passing ViewModel Object From ActionMethod to Another

I have a registration form in the side bar of my web application. When the user submits the entered data, the user should be redirected to another page with a more complete registration form when he can fill the rest of the data. The data that was entered by the user in the first form should be already there in the second form, but that's not happening... I checked to see the value of the view model I'm passing to the second action method and it was null and in the browser's address bar I get:
http://localhost:2732/User/RegisterPage?model=Sharwe.MVC.ViewModels.RegisterPageViewModel
Here's the code:
public ActionResult Register()
{
return PartialView(new RegisterViewModel());
}
[HttpPost]
public ActionResult Register(RegisterViewModel dto)
{
var model = Mapper.Map<RegisterViewModel, RegisterPageViewModel>(dto);
return RedirectToAction("RegisterPage", "User", new { viewModel = model });
}
public ActionResult RegisterPage(RegisterPageViewModel viewModel)
{
return View(viewModel);
}
Isn't that the way to do this? Or am I missing something here...?
The Dictionary passed to RedirectToAction() is the Route Value not the View Model. And RedirectToAction() is basically telling the browser to go to a certain URL. Browser by default makes the GET request and obviously you lose your data.
For this, you need to use TempData dictionary. You can store view model in TempData and then RedirectToAction() to RegisterPage. TempData saves the data for only 1 request span and would delete it automatically. It's ideal for this scenario.
See this for more details > The value for a object inside a viewmodel lost on redirect to action in asp.net mvc 2.0?
In this particular case you don't need to use RedirectToAction, you can simply call the RegisterPage action directly:
[HttpPost]
public ActionResult Register(RegisterViewModel dto)
{
var model = Mapper.Map<RegisterViewModel, RegisterPageViewModel>(dto);
return RegisterPage(model);
}
public ActionResult RegisterPage(RegisterPageViewModel viewModel)
{
return View(viewModel);
}

MVC 3 RTM allowHtml doesn't work when using FormCollection

MVC 3 RTM. Have a model that has an attribute with AllowHtml. In my controller action, if the action has FormCollection as parameter, it throws the exception:
[HttpPost]
public ActionResult Edit(FormCollection collection, int id)
{
var myEntity = _myRepo.Get(id);
TryUpdateModel(myEntity);
return DoSave(myEntity);
}
A potentially dangerous Request.Form
value was detected from the client
However if my controller action uses an object instead of FormCollection it doesn't throw the exception.
[HttpPost]
public ActionResult Edit(MyEntity postedEntity, int id)
{
var myEntity = _myRepo.Get(id);
TryUpdateModel(myEntity);
return DoSave(myEntity);
}
I've already setup
httpRuntime
requestValidationMode="2.0"
Why does it fail when using FormCollection?
You can't use AllowHtml with FormCollection. You could use the [ValidateInput] attribute but obviously this disabled validation for all values:
[HttpPost]
[ValidateInput(false)]
public ActionResult Edit(FormCollection collection, int id)
{
var myEntity = _myRepo.Get(id);
TryUpdateModel(objective);
return DoSave(objective);
}
This being said I would use the following:
[HttpPost]
public ActionResult Edit(MyEntity entity)
{
if (ModelState.IsValid)
{
_myRepo.Save(entity);
return RedirectToAction("Success");
}
return View(entity);
}
For security-reasons, simply disabling validation is not a good solution, as you're inadvertently disabling security for that action-method entirely.
When you need just one GET or POST value, this is extremely annoying - for example, Request.Params["xyz"] will throw if there's an HTML-value anywhere in your GET/POST data, even if the "xyz" value you posted does not contain HTML.
(This is true as of the latest MVC 3.1 release.)
To get around this issue, I extended my Controller base-class with the following method:
/// <summary>
/// Gets a value from the current Controller's ValueProvider, bypassing post-data validation.
/// </summary>
public string GetUnvalidatedValue(string key)
{
ValueProviderResult result;
if (ValueProvider is IUnvalidatedValueProvider)
{
result = ((IUnvalidatedValueProvider)ValueProvider)
.GetValue(key, skipValidation: true);
}
else
{
result = ValueProvider.GetValue(key);
}
return result == null ? null : result.AttemptedValue;
}
This effectively allows you to get an individual GET/POST value while bypassing the validation.
I believe this is better, safer and more correct than turning off validation altogether - your application does benefit from the added security, even if the way it's implemented gets in the way, and apparently is pretty painful to get around.
(I don't think this is by design, or at least not by very good design...)

Updating my model then re-evaluate IsValid?

I'm passing in some values to my controller action and everything is binding up fine. There will be two properties missing from the form POST by design.
I am then setting the missing values but then I want to validate the model and it is still saying false since it looks like the ModelState hasn't caught up with my changes.
[HttpPost, Authorize]
public ActionResult Thread(int id, string groupSlug, Comment comment, string submitButton)
{
comment.UserID = UserService.UID;
comment.IP = Request.UserHostAddress;
UpdateModel(comment); //throws invalidoperationexception
if (ModelState.IsValid) // returns false if i skip last line
{
//save and stuff
//redirect
}
//return view
}
What is the cleanest way to pat the ModelState on the head and tell it that everything will be okay whilst still validating everything else that was bound from the user's POST
If the missing Values are required for your model but will not be provided until after binding you may need to clear the errors caused by those two values from the ModelState.
[HttpPost, Authorize]
public ActionResult Thread(int id, string groupSlug, Comment comment, string submitButton)
{
comment.UserID = UserService.UID;
comment.IP = Request.UserHostAddress;
//add these two lines
ModelState["comment.UserID"].Errors.Clear();
ModelState["comment.IP"].Errors.Clear();
UpdateModel(comment); //throws invalidoperationexception
if (ModelState.IsValid) // returns false if i skip last line
{
//save and stuff
//redirect
}
//return view
}
I'm using the ASP.NET Core 1.0.0 and async binding and for me the solution was to use ModelState.Remove and pass the property name (without object name).
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Submit([Bind("AerodromeID,ObservationTimestamp,RawObservation")] WeatherObservation weatherObservation)
{
weatherObservation.SubmitterID = this.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
weatherObservation.RecordTimestamp = DateTime.Now;
ModelState.Remove("SubmitterID");
if (ModelState.IsValid)
{
_context.Add(weatherObservation);
await _context.SaveChangesAsync();
return RedirectToAction("Index", "Aerodrome");
}
return View(weatherObservation);
}
Before .NET Core you can use Validate(TEntity entity) function of controller. But first you have to clear existing ModelState errors.
Suppose you set a missing required property to entity.
ModelState.Clear();
Validate(entity);
if (!ModelState.IsValid) {}
With Core you can use TryValidateModel instead of ModelState.IsValid
see thiis: https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-5.0

ASP.Net MvC framework, Html.ValidationMessage tag

In this code in an Edit view, the correct vendor name text appears but it is not validated when I blank its textbox and push Save. The Vendor is a property of the Order model, and VendorName is a property in the Vendor model. They relate referentially. My form does not all input into a single table, but on satellite tables as well.
<%= Html.TextBox("Vendor.VendorName")%>
<%= Html.ValidationMessage("Vendor.VendorName")%>
Why is validation not occuring?
This seems to work, but it seems like a hack to me:
using M = HelloUranus.Models
//...
namespace HelloUranus.Controllers
{
public class OrderDetailController : Controller
{
//...
private M.DBProxy db = new M.DBProxy();
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection collection)
{
//...
var orderDetail = db.GetOrderDetail(id);
//...
try
{
if (string.IsNullOrEmpty(Request.Form["Vendor.VendorName"]))
{
throw new Exception();
}
UpdateModel(orderDetail);
db.Save();
return RedirectToAction("Details", new {id = orderDetail.odID } );
}
catch
{
ModelState.AddRuleViolations(orderDetail.GetRuleViolations());
return View(orderDetail);
}
//...
}
//...
}
Did you write any validation code? You have to manually validate it in your controller. If you:
ModelState.IsValid = false;
in the controller, for example, you will see some validation. That will trigger the ValidationSummary on the View to be shown. To actually add a validation to a single form element, use:
ModelState.AddModelError("Vendor.VendorName", string.Format("Vendor name must be at least {0} characters.",10));
Note that this will also set the ModelState to an invalid state and thus trigger the ValidationSummary as well.

Resources