I want to use the error message returned by a RuleViolation as the validationMessage of the Html.ValidationMessage(modelName, validationMessage).
Simple Example: Person.cs
public partial class Person
{
public bool IsValid
{
get { return (GetRuleViolations().Count() == 0); }
}
public IEnumerable<RuleViolation> GetRuleViolations()
{
if (String.IsNullOrEmpty(Name))
yield return new RuleViolation("Name required", "Name");
yield break;
}
}
adding errors to modelstate
foreach (RuleViolation issue in errors)
{
modelState.AddModelError(issue.PropertyName, issue.ErrorMessage);
}
asp page
/createPerson/
<%=Html.TextBox("Name",Model.Name)%>
<%=Html.ValidationMessage("Name", "Name required")%>
What I want to do is use the RuleViolation message instead of "Name required" above. Is this possible?
solution is (from Alexander Prokofyev ) use ValidationMessage with a single paramter
<%=Html.ValidationMessage("Name")%>
this is a very simple example, some of my business rules can throw different RuleViolations on an the input depending on the value.
Many thanks.
Simon
You should use ValidationMessage() helpers with only one parameter like
<%= Html.ValidationMessage("Name") %>
and call code
foreach (var ruleViolation in GetRuleViolations())
ModelState.AddModelError(ruleViolation.PropertyName,
ruleViolation.ErrorMessage);
in controller before returning a view (I suppose you have taken RuleViolation class definition from NerdDinner source).
Related
use .NET MVC and code-first EF to implement of requested functionality. Business objects are relatively complex and I use System.ComponentModel.DataAnnotations.IValidatableObject to validate business object.
Now I'm trying to find the way, how to show validation result from business object, using MVC ValidationSummary without using data annotations. For example (very simplified):
Business Object:
public class MyBusinessObject : BaseEntity, IValidatableObject
{
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
return Validate();
}
public IEnumerable<ValidationResult> Validate()
{
List<ValidationResult> results = new List<ValidationResult>();
if (DealType == DealTypes.NotSet)
{
results.Add(new ValidationResult("BO.DealType.NotSet", new[] { "DealType" }));
}
return results.Count > 0 ? results.AsEnumerable() : null;
}
}
Now in my MVC controller I have something like this:
public class MyController : Controller
{
[HttpPost]
public ActionResult New(MyModel myModel)
{
MyBusinessObject bo = GetBoFromModel(myModel);
IEnumerable<ValidationResult> result = bo.Validate();
if(result == null)
{
//Save bo, using my services layer
//return RedirectResult to success page
}
return View(myModel);
}
}
In view, I have Html.ValidationSummary();.
How I can pass IEnumerable<ValidationResult> to the ValidationSummary?
I tried to find an answer by googling, but all examples I found describes how to show validation summary using data annotations in Model and not in Business object.
Thanks
Add property, say BusinessError, in the model
in the View do the following
#Html.ValidationMessageFor(model => model.BusinessError)
Then in your controller whenever you have error do the following
ModelState.AddModelError("BussinessError", your error)
I would have a look at FluentValidation. It's a framework for validation without data annoations. I've used it with great success in some projects for complex validation, and it is also usable outside of the MVC-project.
Here is the sample code from their page:
using FluentValidation;
public class CustomerValidator: AbstractValidator<Customer> {
public CustomerValidator() {
RuleFor(customer => customer.Surname).NotEmpty();
RuleFor(customer => customer.Forename).NotEmpty().WithMessage("Please specify a first name");
RuleFor(customer => customer.Company).NotNull();
RuleFor(customer => customer.Discount).NotEqual(0).When(customer => customer.HasDiscount);
RuleFor(customer => customer.Address).Length(20, 250);
RuleFor(customer => customer.Postcode).Must(BeAValidPostcode).WithMessage("Please specify a valid postcode");
}
private bool BeAValidPostcode(string postcode) {
// custom postcode validating logic goes here
}
}
Customer customer = new Customer();
CustomerValidator validator = new CustomerValidator();
ValidationResult results = validator.Validate(customer);
bool validationSucceeded = results.IsValid;
IList<ValidationFailure> failures = results.Errors;
Entity Framework should throw a DbEntityValidationException if there are validation errors. You can then use the exception to add the errors to the ModelState.
try
{
SaveChanges();
}
catch (DbEntityValidationException ex)
{
AddDbErrorsToModelState(ex);
}
return View(myModel);
protected void AddDbErrorsToModelState(DbEntityValidationException ex)
{
foreach (var validationErrors in ex.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
ModelState.AddModelError(validationError.PropertyName, validationError.ErrorMessage);
}
}
}
One of the ways to pass the contents of IEnumerate and keep taking advantage of Html.ValidationSummary is by updating ModelState.
You can find a good discussion on how to update the ModelState here.
I followed Darin's post at
multi-step registration process issues in asp.net mvc (splitted viewmodels, single model)
Its a very elegant solution, however im having trouble seeing how you would populate the individual step viewmodels with data. Im trying to emulate amazons checkout step-system which starts with selecting an address, then shipping options, then payment information.
For my first viewmodel i require a list of addresses for my current logged in user which i poll the database for to display on screen
In my head, this is the viewmodel that makes sense to me.
[Serializable]
public class ShippingAddressViewModel : IStepViewModel
{
public List<AddressViewModel> Addresses { get; set; }
[Required(ErrorMessage="You must select a shipping address")]
public Int32? SelectedAddressId { get; set; }
#region IStepViewModel Members
private const Int32 stepNumber = 1;
public int GetStepNumber()
{
return stepNumber;
}
#endregion
}
However there seems to be no good way to populate the addresses from the controller.
public class WizardController : Controller
{
public ActionResult Index()
{
var wizard = new WizardViewModel();
wizard.Initialize();
return View(wizard);
}
[HttpPost]
public ActionResult Index(
[Deserialize] WizardViewModel wizard,
IStepViewModel step)
{
wizard.Steps[wizard.CurrentStepIndex] = step;
if (ModelState.IsValid)
{
if (!string.IsNullOrEmpty(Request["next"]))
{
wizard.CurrentStepIndex++;
}
else if (!string.IsNullOrEmpty(Request["prev"]))
{
wizard.CurrentStepIndex--;
}
else
{
// TODO: we have finished: all the step partial
// view models have passed validation => map them
// back to the domain model and do some processing with
// the results
return Content("thanks for filling this form", "text/plain");
}
}
else if (!string.IsNullOrEmpty(Request["prev"]))
{
// Even if validation failed we allow the user to
// navigate to previous steps
wizard.CurrentStepIndex--;
}
return View(wizard);
}
}
So i removed the list of address view models
[Serializable]
public class ShippingAddressViewModel : IStepViewModel
{
[Required(ErrorMessage="You must select a shipping address")]
public Int32? SelectedAddressId { get; set; }
#region IStepViewModel Members
private const Int32 stepNumber = 1;
public int GetStepNumber()
{
return stepNumber;
}
#endregion
}
This is what i came up with a custom editor template for the view model. It calls a Html.RenderAction which returns a partial view from my user controller of all the addresses and uses Jquery to populate a hidden input field for the view model's required SelectedAddressId property.
#model ViewModels.Checkout.ShippingAddressViewModel
<script src="../../Scripts/jquery-1.7.1.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function () {
//Check to see if the shipping id is already set
var shippingID = $("#SelectedAddressId").val();
if (shippingID != null) {
$("#address-id-" + shippingID.toString()).addClass("selected");
}
$(".address-id-link").click(function () {
var shipAddrId = $(this).attr("data-addressid").valueOf();
$("#SelectedAddressId").val(shipAddrId);
$(this).parent("", $("li")).addClass("selected").siblings().removeClass("selected");
});
});
</script>
<div>
#Html.ValidationMessageFor(m => m.SelectedAddressId)
#Html.HiddenFor(s => s.SelectedAddressId)
<div id="ship-to-container">
#{Html.RenderAction("UserAddresses", "User", null);}
</div>
</div>
And the users controller action
[ChildActionOnly]
public ActionResult UserAddresses()
{
var user = db.Users.Include("Addresses").FirstOrDefault(
u => u.UserID == WebSecurity.CurrentUserId);
if (user != null)
{
return PartialView("UserAddressesPartial",
Mapper.Map<List<AddressViewModel>>(user.Addresses));
}
return Content("An error occured");
}
The partial view
#model IEnumerable<AddressViewModel>
<ul>
#foreach (var item in Model)
{
<li id="address-id-#item.AddressID">
#Html.DisplayFor(c => item)
<a class="address-id-link" href="#" data-addressid="#item.AddressID">Ship To this Address
</a></li>
}
</ul>
My solution just seems super out of the way/sloppy to me, is there a better more concise way to populate the viewmodel than using partial views from a different controller for this?
There's nothing wrong with using a child action like this to populate the user's addresses. In fact, I think this is actually the optimal approach. You've got full separation of concerns and single responsibility in play. Just because something requires more "pieces" (extra action, views, etc.) doesn't make it sloppy or otherwise wrong.
The only other way to handle this would be with dependency injection. Namely, your ShippingAddressViewModel would need a dependency of the currently logged in user, so that it could populate the list of addresses from that in its constructor. However, since ShippingAddressViewModel is not exposed in your view, you would have to pass the dependency through Wizard which is a bit of code smell. Wizard is not dependent on a user, but it would have dependence forced upon it by virtue of having your view model abstracted away inside it.
Long and short, while there's a way you could do this without the child actions and partial views, it would actually be nastier and sloppier than with them.
If two of textboxes fail validation at once then the ValidationSummary displays the same message twice.
Am I doing something wrong? Or is there a setting I can change to hide duplicate messages?
I have broken it down to the simplest example:
View:
#model MyModel
#Html.ValidationSummary()
#Html.TextBoxFor(model => model.A)
#Html.TextBoxFor(model => model.B)
Model:
public class MyModel : IValidatableObject
{
public int A { get; set; }
public int B { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
//Some logic goes here.
yield return new ValidationResult("Validation failed", new[] { "A", "B" });
}
}
Result:
They are not duplicate from the point of view of ValidationSummary - you are assigning model state error to both fields A and B, so there must be 2 errors in validation summary. It doesnt "know" that they are the same.
Easy solutions :
assign model only to one of them
exclude property-assigned errors from summary - Html.ValidationSummary(true)
A little bit harder solution :
make your own ValidationSummary helper, call standard validation summary logic in it, and then filter the result in "select distinct" way (linq is your friend here).
EDIT:
something like this for example :
public static class ValidationExtensions
{
public static MvcHtmlString FilteredValidationSummary(this HtmlHelper html)
{
// do some filtering on html.ViewData.ModelState
return System.Web.Mvc.Html.ValidationExtensions.ValidationSummary(html);
}
}
Whack this is your View
<ul class="validation-summary-errors">
#{
string errorMessage = "", previousError = "";
foreach (ModelState modelState in (ViewContext.ViewData.ModelState.Values)){
foreach (ModelError modelError in modelState.Errors)
{
errorMessage = modelError.ErrorMessage;
if (errorMessage != previousError)
{
<li>#modelError.ErrorMessage</li>
previousError = modelError.ErrorMessage;
}
}
}
}
</ul>
You might be able to improve this as it only works when 2 consecutive errors are the same, if it jumbles the order this might not work, but this will start you off. I suppose you could build an array of error messages and check the error off it each run through, but this solution seems to work most of the time.
ValidationSummary method returns property-level and model-level errors. It just enumerates all validation messages if you don't specify any arguments.
You can:
1) Use different message for field A and B
// logic here
yield return new ValidationResult("Validation failed for left field", new[] { "A" });
// logic here
yield return new ValidationResult("Validation failed for right field", new[] { "B" });
or, in your view
2) Call ValidationSummary with excludePropertyErrors argument set to true - ValidationSummary(true). And place call Html.ValidationMessage[For] near each of your fields.
UPDT:
... and third case:
In your model add common message (model-level):
//logic here
yield return new ValidationResult("Validation failed");
yield return new ValidationResult("any text or empty string", new[] { "A", "B" });
In your view exclude property messages but don't add ValidationMessage for fields:
#model MyModel
#Html.ValidationSummary(true)
#Html.TextBoxFor(model => model.A)
#Html.TextBoxFor(model => model.B)
So you'll get single message and both red boxes.
I am working with ASP.NET MVC2 RC and can't figure out how to get the HTML helper, TextBoxfor to work with a ViewModel pattern. When used on an edit page the data is not saved when UpdateModel() is called in the controller. I have taken the following code examples from the NerdDinner application.
Edit.aspx
<%# Language="C#" Inherits="System.Web.Mvc.ViewUserControl<NerdDinner.Models.DinnerFormViewModel>" %>
...
<p>
// This works when saving in controller (MVC 1)
<label for="Title">Dinner Title:</label>
<%= Html.TextBox("Title", Model.Dinner.Title) %>
<%= Html.ValidationMessage("Title", "*") %>
</p>
<p>
// This does not work when saving in the controller (MVC 2)
<label for="Title">Dinner Title:</label>
<%= Html.TextBoxFor(model => model.Dinner.Title) %>
<%= Html.ValidationMessageFor(model=> model.Dinner.Title) %>
</p>
DinnerController
// POST: /Dinners/Edit/5
[HttpPost, Authorize]
public ActionResult Edit(int id, FormCollection collection) {
Dinner dinner = dinnerRepository.GetDinner(id);
if (!dinner.IsHostedBy(User.Identity.Name))
return View("InvalidOwner");
try {
UpdateModel(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new { id=dinner.DinnerID });
}
catch {
ModelState.AddModelErrors(dinner.GetRuleViolations());
return View(new DinnerFormViewModel(dinner));
}
}
When the original helper style is used (Http.TextBox) the UpdateModel(dinner) call works as expected and the new values are saved.
When the new (MVC2) helper style is used (Http.TextBoxFor) the UpdateModel(dinner) call does not update the values. Yes, the current values are loaded into the edit page on load.
Is there something else which I need to add to the controller code for it to work? The new helper works fine if I am just using a model and not a ViewModel pattern.
Thank you.
The issue here is your Edit form is using strongly typed helpers against a DinnerFormViewModel type, but you're calling UpdateModel on a Dinner type.
When you use the strongly typed helpers against the type, the helpers create form fields assuming that's the type you're posting to. When the types don't match up, there's a problem.
However, this is very easy to fix. You can provide a prefix to UpdateModel which indicates that you weren't trying to edit the whole model, you were trying to edit a property of the model, in this case a Dinner.
UpdateModel(dinner, "Dinner");
The other approach is to call UpdateModel on the actual ViewModel.
var viewModel = new DinnerFormViewModel();
viewModel.Dinner = repository.GetDinner(id);
UpdateModel(viewModel);
I think the first approach is much better.
On Page 90 in the "Wrox Professional ASP.NET MVC 2" book the code is listed as:
if (TryUpdateModel(dinner)) {
dinnerRepository.Save();
redirectToAction("Details", new { id=dinner.DinnerID });
But it should read:
if (TryUpdateModel(dinner, "Dinner")) {
dinnerRepository.Save();
redirectToAction("Details", new { id=dinner.DinnerID });
This method overload will try to update the specified model [Dinner], rather than the default [ViewModel], using the values from the controller's value provider. Basically all it does is add a prefix to all your values when looking them up in the provider.
So when the Model is looking to update its' Title property, it will look for Dinner.Title, instead of just Title in the controller's value provider.
While debugging, take a look in the Edit ActionResult method and inspect the FormCollection input param. When you dig down into it's entry array, you'll find Keys that all start with the prefix of the property object you referenced in your View, in your case the Edit View, like this:
<%: Html.TextBoxFor(model => model.Dinner.Title, new {size=50, #class="prettyForm" })%>
I'm not 100% sure, but it seems that strongly typed helper creates ids/names "Dinner.Title" instead of just "Title" and therefore - UpdateModel can't bind it.
Unfortunately - i haven't used UpdateModel method myself so i don't know the solution.
Could you add html that gets rendered for both approaches?
Playing around with reflector ATM.
This is what i found:
protected internal bool TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties, IValueProvider valueProvider) where TModel: class
{
if (model == null)
{
throw new ArgumentNullException("model");
}
if (valueProvider == null)
{
throw new ArgumentNullException("valueProvider");
}
Predicate<string> predicate = delegate (string propertyName) {
return BindAttribute.IsPropertyAllowed(propertyName, base.includeProperties, base.excludeProperties);
};
IModelBinder binder = this.Binders.GetBinder(typeof(TModel));
ModelBindingContext context2 = new ModelBindingContext();
context2.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(delegate {
return base.model;
}, typeof(TModel));
context2.ModelName = prefix;
context2.ModelState = this.ModelState;
context2.PropertyFilter = predicate;
context2.ValueProvider = valueProvider;
ModelBindingContext bindingContext = context2;
binder.BindModel(base.ControllerContext, bindingContext);
return this.ModelState.IsValid;
}
Parameters
- model The model instance to update.
- prefix The prefix to use when looking up values in the value provider.
So - you can try to use UpdateModel<T>(T model, string prefix) overload and pass "Dinner" or "Dinner." as prefix argument.
Maybe a simpler way to put this is as follows. If you are cutting and pasting code from the wrox download for the NerDDinner tutorial, you'll find there are some errors. Using a suggestion from above, I modified the example from 1-53.txt to get this to work. The change follows:
//
// POST: /Dinners/Edit/2
[HttpPost]
public ActionResult Edit(int id, FormCollection formValues)
{
// Retrieve existing dinner
Dinner dinner = dinnerRepository.GetDinner(id);
DinnerFormViewModel viewModel = new DinnerFormViewModel(dinner);
if (TryUpdateModel(viewModel))
{
// Persist changes back to database
dinnerRepository.Save();
// Perform HTTP redirect to details page for the saved Dinner
return RedirectToAction("Details", new { id = dinner.DinnerID });
}
else
{
return View(viewModel);
}
}
A simpler way is using the prefix as parameter name, just do like this:
public ActionResult Edit(Dinner Dinner, int DinnerID)
{
...
}
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.