ViewBag In ASP.NET MVC - asp.net-mvc

I am passing my model errors via the ViewBag. In my controller I am passing a List<string> that does contain values (_modelErrors) to the ViewBag and then I am trying to display this in my view. When I debug in the view, my ViewBag does contain a _modelErrors dictionary with values, but my errors are not being rendered.
From my controller:
ViewBag._modelErrors = _modelErrors;
In my view I have tried all of these methods and yet no markup is rendered.
#try
{
foreach (string er in ViewBag._modelErrors)
{
Response.Write(er.ToString());
#Html.DisplayText(er)
#Html.DisplayText(er.ToString())
#er.GetType()
#er.ToString();
er.ToString();
}
}
catch(Exception e){}

That's a dirty code, dude! Take a look at #Html.ValidationSummary and #Html.ValidationMessageFor
Well, some codes here to clarify stuff:
[HttpPost]
public ActionResult MyAction(ModelClass inputModel)
{
if (ModelState.IsValid)
{
//for example we're trying to save our data to db
var result = _myRepository.saveStuff(inputModel);
if (result)
return RedirectToAction("someAction");
ModelState.AddModelError(string.Empty, "An error occured, check input data");
}
return View(inputModel);
}
Also you can specify key (which is your model field name) instead of string.Empty -> so you'l see error message under specified field (as default), but basically what people do is exluding property errors from validation summary (#Html.ValidationSummary(true)) - so you get just general errors in that field. Hope it'l help

Use this:
(string er in (List<string>)(ViewBag._modelErrors))
But was said this is bad practice. Also catching exceptions this way is so wrong...

Related

Prevent ModelState.IsValid from validating attached entities?

Is there a way to override ModelState.IsValid?
Some of the entities to be validated are just attached entities, so all the fields except the ID are not to be validate as the entity is in state Unchanged.
Is there a way to do this?
Has anyone faced this issue before?
Update
Say I have the following action:
[HttpPost]
public ActionResult SaveEntity(MyEntity entity)
{
var isValid = ModelState.IsValid; //false
}
Since the model validates all properties and all descendant properties of entity, there has to be a way to check on each entity of those descendants, whether it's attached to the context, and if it is, remove error from ModelState, something like the following:
public ActionResult TryValidateDetachedModel(MyEntity entity, DbContext context)
{
foreach (var ms in ModelState.Where(ms => ms.Value.Errors.Any()).ToArray())
// should iterate over something like GetAllEntityTypesMetadata()
{
var entity = GetEntityFromMetadata(ms);
if (context.Entry(entity).State == EntityState.Unchanged)
{
ms.Value.Errors.Clear();
}
}
}
What I'm trying to do in the above pseudo code is to check the entities in the validation chain, and if one of them is attached as Unchanged, skip validation / remove its errors.
Right now I have to do it hard-coded manually by checking ModelState.Key, I'm looking for a more generic and efficient way.
To clear all errors use next
ModelState.Clear();
regards
Here's what I do to ensure the validation only applies to the current entity:
foreach (var key in ModelState.Keys)
if (key.Split('.').Length > 2)
ModelState[key].Errors.Clear();
if (!ModelState.IsValid)
return BadRequest(ModelState);
The check for the occurrences of . means: if the modelstate key is something like currentDTO.relatedDTO.field then that validation error is ignored (cleared). If it's just id or currentDTO.validateThisField, then it doesn't get cleared.

Why does creating a new ViewModel return the same data as the old ViewModel?

I'm just learning MVC3 now and this is really confusing me.
I have a ViewModel that contains some child ViewModels. Each of the ChildViewModels get rendered with a different Partial View, and when submitting execute a different action on the Controller. All the ChildViewModels should perform some custom validation on their data, and if successful it should move on to the next page. If the validation fails, it should simply return to the ParentView and display the errors.
[HandleError]
public class MyController: Controller
{
public ActionResult Index()
{
var viewModel = new ParentViewModel();
return View("ParentView", viewModel);
}
[HttpPost]
public ActionResult ChildViewModelB_Action(ChildViewModelB viewModel)
{
if (ModelState.IsValid)
{
return View("ChildViewModelB_Page2", viewModel);
}
else
{
// I'm having trouble returning to the ParentView and
// simply displaying the ChildViewModel's errors, however
// discovered that creating a new copy of the VM and displaying
// the ParentView again shows the existing data and any errors
// But why??
var vm = new ParentViewModel();
return View("ParentView", vm);
}
}
}
For example,
The page loads with 3 options.
User selects option B and fills out a form.
Upon submit, the child ViewModel B gets validated and fails.
Page returns to ParentView, with ChildB all filled out, however ChildB errors are now also showing.
Why does creating a new copy of the ParentViewModel display the ParentView with the same data as the original ParentViewModel?
And is there a different way I should be returning to the ParentView after doing server-side validation?
You need to clear the modelstate if you intend to modify values in your POST action
else
{
ModelState.Clear();
var vm = new ParentViewModel();
return View("ParentView", vm);
}
The reason for that is because Html helper such as TextBoxFor will first look in the modelstate when binding their values and after that in the model. And since the modelstate already contains the POSTed values, that's what's used => the model is ignored. This is by design.
This being said the correct thing to do in your case is to simply redirect to the GET action which already blanks the model and respect the Redirect-After-Post pattern:
else
{
return RedirectToAction("Index");
}
Why does creating a new copy of the ParentViewModel display the
ParentView with the same data as the original ParentViewModel?
Because the values of the fields are retrieved from the POSTed form and not from the model. That makes sense right? We don't want the user to show a form filled with different values from what they submitted.

How can I return my MVC4 partial view as json and is it a good thing to do?

I am trying to clean up my code. I have a grid screen that gets refreshed with the following:
public ActionResult Details(string pk)
{
IEnumerable<ContentDetail> model = null;
try
{
model = _content.Details(pk);
if (model.Count() > 0)
{
return PartialView(getView(pk) + "Details", model);
}
}
catch (Exception e)
{
log(e);
}
return Content("No records found");
}
All the rest of my code uses json and I would like to return something like this:
public JsonResult JsonDetails(string pk)
But what should I do about the PartialView? I can't find anything about how to do this. Also is there any advantage / disadvantage to doing this? I was thinking if the code fails then I would return something like the following which the new ASP MVC4 code uses:
return Json(new { errors = GetErrorsFromModelState() });
Can someone help me with this? I'm looking to find any suggestions in particular for MVC4.
I've previously used the approach outlined in this answer, which was successful for me.
I can't think of any disadvantages of returning HTML within JSON, although the payload would likely be much larger than if you were returning data alone.
An alternative would be to return the model as JSON, and use a templating library, e.g. Handlebars.js, to generate the markup on the client. This is a common approach in single page applications.
Your idea around returning errors is good. GetErrorsFromModelState is only used where there are validation errors in the model state - in the example above, you're not performing any validation that would require you to use this method. So you'd probably want to output some friendly message within your catch-block, e.g.
try
{
...
}
catch (Exception e)
{
log(e);
return Json(new { errors = "An error occurred, please try again later" });
}
I've used the code from this answer before and it worked out great for me, I haven't tried returning specific errors, but it is possible to access controller.ModelState to check.

Can I add a ModelState.AddModelError from a Model class (rather than the controller)?

I want to display an error to the user in an ASP.MVC 3 input form using ModelState.AddModelError() so that it automatically highlights the right field and puts the error next to the particular field.
In most examples, I see ModelState.AddModelError() and if(ModelState.IsValid) placed right in the Controller. However, I would like to move/centralize that validation logic to the model class. Can I have the model class check for model errors and populate ModelState.AddModelError()?
Current Code:
// Controller
[HttpPost]
public ActionResult Foo(Bar bar)
{
// This model check is run here inside the controller.
if (bar.isOutsideServiceArea())
ModelState.AddModelError("Address", "Unfortunately, we cannot serve your address.");
// This is another model check run here inside the controller.
if (bar.isDuplicate())
ModelState.AddModelError("OrderNumber", "This appears to be a duplicate order");
if (ModelState.IsValid)
{
bar.Save();
return RedirectToAction("Index");
}
else
return View(bar)
}
Desired Code:
// Controller
[HttpPost]
public ActionResult Foo(Bar bar)
{
// something here to invoke all tests on bar within the model class
if (ModelState.IsValid)
{
bar.Save();
return RedirectToAction("Index");
}
else
return View(bar)
}
...
// Inside the relevant Model class
if (bar.isOutsideServiceArea())
ModelState.AddModelError("Address", "Unfortunately, we cannot serve your address.");
if (bar.isDuplicate())
ModelState.AddModelError("OrderNumber", "This appears to be a duplicate order");
If you are using MVC 3, you should checkout IValidatableObject, it's what you're after.
Scott Gu mentions it in his MVC3 Intro blog posting.
You can do something like this using custom data annotations or using RuleViolations like what they did in the NerdDinner example.
Perhaps you could create an error interface like IErrorHandler and pass that into a public method called Validate on you model class assuming its a partial class and you can seperate your data model from your rules.
With the interface you could create a class in your contoller that wraps the ModelState error handler. So the inteface might have AddError and in that metghod tou just delegate to your local modelstate.
So your method might be something like:
IErrorHandler errorHandler = CreateErrorHandler();
model.Validate(errorHandler);
if(errorHandler.IsValid())
... do something
You would use custom validation via the IValidatableObject interface on your model.
Custom validation example
If you're after least amount of code, a hacky way of doing it is to stuff that message into the Session from your View Model, then add that message from your Controller, for example:
// In Model (or elsewhere deep inside your app):
string propertyName = "Email";
string errorMessage = "This is the error message, you should fix it";
Session["DeepModelError"] = new KeyValuePair<string, string>(propertyName, errorMessage);
// In Controller
var model = new LoginModel();
if (Session["DeepModelError"] != null)
{
var deepError = (KeyValuePair<string, string>)Session["DeepModelError"];
if (deepError.Key == "Email" || deepError.Key == "Password")
{
ModelState.AddModelError(deepError.Key, deepError.Value);
Session["DeepModelError"] = null;
}
}
Let me reiterate that I realize that this is kind of hacky, but it's simple and it gets the job done...

How to modify posted form data within controller action before sending to view?

I want to render the same view after a successful action (rather than use RedirectToAction), but I need to modify the model data that is rendered to that view. The following is a contrived example that demonstrates two methods that that do not work:
[AcceptVerbs("POST")]
public ActionResult EditProduct(int id, [Bind(Include="UnitPrice, ProductName")]Product product) {
NORTHWNDEntities entities = new NORTHWNDEntities();
if (ModelState.IsValid) {
var dbProduct = entities.ProductSet.First(p => p.ProductID == id);
dbProduct.ProductName = product.ProductName;
dbProduct.UnitPrice = product.UnitPrice;
entities.SaveChanges();
}
/* Neither of these work */
product.ProductName = "This has no effect";
ViewData["ProductName"] = "This has no effect either";
return View(product);
}
Does anyone know what the correct method is for accomplishing this?
After researching this further, I have an explanation why the following code has no effect in the Action:
product.ProductName = "This has no effect";
ViewData["ProductName"] = "This has no effect either";
My View uses HTML Helpers:
<% Html.EditorFor(x => x.ProductName);
HTML Helpers uses the following order precedence when attempting lookup of the key:
ViewData.ModelState dictionary entry
Model property (if a strongly typed view. This property is a shortcut to View.ViewData.Model)
ViewData dictionary entry
For HTTP Post Actions, ModelState is always populated, so modifying the Model (product.ProductName) or ViewData directly (ViewData["ProductName"]) has no effect.
If you do need to modify ModelState directly, the syntax to do so is:
ModelState.SetModelValue("ProductName", new ValueProviderResult("Your new value", "", CultureInfo.InvariantCulture));
Or, to clear the ModelState value:
ModelState.SetModelValue("ProductName", null);
You can create an extension method to simplify the syntax:
public static class ModelStateDictionaryExtensions {
public static void SetModelValue(this ModelStateDictionary modelState, string key, object rawValue) {
modelState.SetModelValue(key, new ValueProviderResult(rawValue, String.Empty, CultureInfo.InvariantCulture));
}
}
Then you can simply write:
ModelState.SetModelValue("ProductName", "Your new value");
For more details, see Consumption of Data in MVC2 Views.
The values are stored in ModelState.
This should do what you want:
ModelState.SetModelValue("ProductName", "The new value");
I wouldn't suggest doing that though... the correct method would be to follow the PRG (Post/Redirect/Get) pattern.
HTHs,
Charles
EDIT: Updated to reflect the better was of setting the ModelState value as found by #Gary
This will trigger the model to re-evaluate under simple conditions:
ModelState.Clear();
model.Property = "new value";
TryValidateModel(model);
Perform ModelState.Clear() before you change the model.
...
ModelState.Clear()
dbProduct.ProductName = product.ProductName;
dbProduct.UnitPrice = product.UnitPrice;
...

Resources