I've got FluentValidation set up in my ASP.Net MVC 3 project. I have a form which has two inputs. Either can be blank, but not both.
Here's what I have:
RuleFor(x => x.username)
.NotEmpty()
.When(x => string.IsNullOrEmpty(x.email) == true)
.WithMessage("Please enter either a username or email address")
This correctly puts the error directly above my username field. However, when both fields are left blank I'd prefer the Validation Summary to show the message
Is there a way to do this? I've been thinking I could create an unused field in the model and put the error on that (if the other two are blank),
RuleFor(x => x.unused_field)
.NotEmpty()
.When(x => string.IsNullOrEmpty(x.email) == true && string.IsNullOrEmpty(x.username) == true)
.WithMessage("Please enter either a username or email address")
but this feels like an awkward way to do it. Is there a way I can just add a message to the validation summary?
The only reference I could find was this.
Now if your model is that simple and this rule is the only one then this rule should be enough:
RuleFor(x => x)
.Must(x => !string.IsNullOrWhiteSpace(x.Email) || !string.IsNullOrWhiteSpace(x.UserName))
.WithName(".") // This adds error message to MVC validation summary
.WithMessage("Please enter either a username or email address");
Just add #Html.ValidationSummary() to your view and you're good to go.
But if you're going to put more rules on your model then to my knowledge there is only one 'hacky' way I could think of:
in your controller action add this:
if (!ModelState.IsValid)
{
if (ModelState["."].Errors.Any())
{
ModelState.AddModelError(string.Empty, ModelState["."].Errors.First().ErrorMessage);
}
// ...
}
This will add first error message from "." property to model property (adapt it to your needs). Also you will have to do a #Html.ValidationSummary(true) to show only model level errors in validation summary.
Third option: add rule(s) to unused_property and use #Html.ValidationSummaryFor(x => x.unused_property) as a validation summary
Your issue can be solved with FluentValidation AbstractValidator.Custom:
Custom(m => String.IsNullOrEmpty(m.Username) && String.IsNullOrEmpty(m.Email)
? new ValidationFailure("", "Enter either a username or email address.")
: null);
Empty string in ValidationFailure constructor assures validation message is not bound to any input field so it appears in validation summary like
ModelState.AddModelError("", "Enter either a username or email address.")
would.
Related
#Html.RequiredLabelFor(x => x.FirstName)
#Html.TextBoxFor(model => model.FirstName, new { Name = "1.first_name", tabindex = "1" })
#Html.ValidationMessageFor(model => model.FirstName)
Is there a reason why when passing second parameter to #Html.TextBoxFor the field is being validated but "validation message" does not appear?
#Html.RequiredLabelFor(x => x.FirstName)
#Html.TextBoxFor(model => model.FirstName)
#Html.ValidationMessageFor(model => model.FirstName)
When using an overload that accepts only one argument (lambda expression) "validation message" is being displayed correctly.
In my understanding the actual property is not being recognized?
Backing property:
[StringLength(100, ErrorMessage = "Max length 100 characters")]
[Required(ErrorMessage = "This field is required")]
[Display(Name = "First name")]
public string FirstName { get; set; }
The unobtrusive validation library identifies 'things to display validation for' using the name attribute. It's not that specifying extra properties stops it from working, but you have changed the name property and not reflected the new name in your validation helper.
You can either stop changing the name attribute in your textbox helper (necessary if you use the default model binder to return your view), or you can use
#Html.ValidationMessage("1.first_name")
The above method is not a good idea unless you're using JS/JQuery to validate and submit your form data (via AJAX), for the reasons given by Stephen in comment to this answer.
You have changed the name attribute, so not only will it not post back and bind to your model, jquery.validate.unobtrusive can now no longer match up the controls since the input has a different name than the associated validation message
<input name="1.first_name" ... />
<span .. data-valmsg-for="FirstName" ..></span> // There is no control named FirstName
I have taken a look at but it did not help me out
GetFullHtmlFieldId returning incorrect id attribute value
ASP.NET GetFullHtmlFieldId not returning valid id
Problem
Basically I have the following problem:
I have a custom validation attribute which requires to get the fieldId of the control
public class MyValidationAttribute : ValidationAttribute, IClientValidatable
{
//...... Collapsed code
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
ViewContext vwContext = context as ViewContext;
var fieldId = vwContext.ViewData.TemplateInfo.GetFullHtmlFieldId(metadata.PropertyName);
//...... Collapsed code
yield return clientValidationRule;
}
//...... Collapsed code
}
The result of GetFullHtmlFieldId depends on how I build my asp.net mvc page:
// Edit.cshtml or Create.cshtml
#Html.EditorFor(model => model.MyBoolProperty)
// Shared/EditorTemplates/Boolean.cshtml
#Html.CheckBoxFor(model => model)
result of GetFullHtmlFieldId incorrect: MyBoolProperty_MyBoolProperty
// Edit.cshtml or Create.cshtml
#Html.CheckBoxFor(model => model.MyBoolProperty)
result of GetFullHtmlFieldId correct: MyBoolProperty
Even with more complex editors I see this incorrect behavior
// Edit.cshtml or Create.cshtml
#Html.EditorFor(model => model.JustAnArray[1].ComplexProperty.MyBooleanProperty)
// Shared/EditorTemplates/Boolean.cshtml
#Html.CheckBoxFor(model => model)
result of GetFullHtmlFieldId incorrect: JustAnArray_1__ComplexProperty_MyBoolProperty_MyBoolProperty
// Edit.cshtml or Create.cshtml
#Html.CheckBoxFor(model => model.JustAnArray[1].ComplexProperty.MyBooleanProperty)
result of GetFullHtmlFieldId correct: JustAnArray_1__ComplexProperty_MyBoolProperty
Also this is returning correct value
// Edit.cshtml or Create.cshtml
#Html.EditorFor(model => model.JustAnArray[1])
// Shared/EditorTemplates/ComplexProperty.cshtml
#Html.CheckBoxFor(model => model.MyBooleanProperty)
result of GetFullHtmlFieldId correct: JustAnArray_1__ComplexProperty_MyBoolProperty
It looks like that using #Html.CheckBoxFor(model => model), it gives incorrect results but when using #Html.CheckBoxFor(model => model.MyBoolProperty) it is working as expected
I have the same issue with other controls (like TextBoxFor)
Question
How can I get the proper fieldId of my control in my validation attribute, independent of how you build the page.
I'd rather use already existing methods (maybe the same methods as which are used by TextBoxFor and CheckBoxFor and other controls) than mimic this already existing functionality. If I mimic the generation of the fieldId, I have a change I don't take care of all situations where the ASP.NET controls take care of.
It seems that the valid prefix depends on the expressions used to build the page, and that isn't available to the GetClientValidationRules method. It would be great if someone has a solution for it, but as the method GetClientValidationRules is trying to initialize the data needed for the Javascript validation, you could try to resolve this on the client side.
On some of the default validation attributes like the [Compare], which depends on a second field, they set a parameter *.propertyName and in the unobtrusive adapter they replace the *. part with the valid prefix retrieved from the input name attribute. You could try a similar approach for the id.
However this would be needed if you were interested in another field. In this case it seems you are interested in the id of the very same input field that you are validating. You could then retrieve it from the input element itself. It will be available in both the unobtrusive adapter or the method itself:
//You could get it in the adapter and pass it to the validator in the "param"
adapters.add("dummy", function (options) {
var fullFieldId = options.element.id;
if (console) console.log("Full field id: " + fullFieldId);
setValidationValues(options, "dummy", fullFieldId);
});
$jQval.addMethod("dummy", function (value, element, param) {
var fullFieldId = param;
//do whatever validation logic
return true;
});
//You could also retrieve it directly in the validator method
$jQval.addMethod("dummy", function (value, element, param) {
var fullFieldId = element.id;
//do whatever validation logic
return true;
});
adapters.add("dummy", function (options) {
setValidationValues(options, "dummy", {});
});
Hope it helps!
I I need to validate some thing in mvc in my controller I have to
#Html.ValidationMessageFor(model => model.somestring)// in the view
if (model.string = some condition)
ModelState.AddModelError("somestring", "String cannot be empty");// in the controller
but if in my view I have a custom object like
#Html.ValidationMessageFor(model => model.someobject.somestring)// in the view
how do I validate it? Is the following syntax correct?
if (model.someobject.somestring = some condition)
ModelState.AddModelError("somestring", "String cannot be empty");// in the controller
You need to make sure the full path to your property is used when specifying your key:
ModelState.AddModelError("someobject.somestring", "String cannot be empty);
I have made both Required and Regular Expression validation working.
the only problem is i want to show them both on different location within the page.
just like the required validation message will be shown before the textbox. the regular expression validation message will be shown after the textbox. How can i do it?
Here is my model code
[Required(ErrorMessage = "*")]
[RegularExpression(#"^[\w-]+(?:\.[\w-]+)*#(?:[\w-]+\.)+[a-zA-Z]{2,7}$", ErrorMessage = "Invalid Email")]
public string Email { get; set; }
Here is my View code
#Html.ValidationMessageFor(p => p.Email)
#Html.TextBoxFor(p => p.Email)
#Html.LabelFor(p => p.Email, "Email")
On the above code, both error messages will show before the textbox, i want to make something like this
#Html.ValidationMessageFor(p => p.Email) - required validation message which is "*"
#Html.TextBoxFor(p => p.Email)
#Html.LabelFor(p => p.Email, "Email")
#Html.ValidationMessageFor(p => p.Email) - regular expression validation message which is "Invalid Email"
There is nothing out of the box that would give you fine grain control over individual validation errors for a single control. You would need to manually parse the individual errors from the ModelState - see this example.
Say I have a model:
public class HashTable {
public string sKey { get;set; }
public string sValue { get:set; }
}
Then I render it in a view:
<div>Please enter the key/value pair:</div>
#Html.LabelFor(h => h.sKey, "Key");
#Html.TextBoxFor(h => h.sKey);
#Html.LabelFor(h => h.sValue, "Value");
#Html.TextBoxFor(h => h.sValue);
Then I save it in the controller:
db.HashTable.Add(themodel);
Then I recall it in a view, but I only want to change the value:
<div>Please enter the value:</div>
Key: #Model.sKey
#Html.LabelFor(h => h.sValue, "Value");
#Html.TextBoxFor(h => h.sValue, Model.sValue);
Then I submit that to the controller. The problem is if I do:
db.Entry(oldmodel).CurrentValues.SetValues(themodel);
It nulls the "key" field because there's no element on the view for it.
This is a very simple example (the code might not all be exact) of a very complex system where certain form fields may or may not be displayed in the view based on the state of the ticket. What I really need is a way to make it so that if the form field is not displayed in the view, it is not updated in the database. Any ideas? Thanks in advance!
Include your Key also in the form. So that when posted back, It will be available in the model/viewmodel. You can use Html.HiddenFor helper method for this.
<div>Please enter the value:</div>
Key: #Model.sKey
#Html.LabelFor(h => h.sValue);
#Html.TextBoxFor(h => h.sValue);
#Html.HiddenFor(x=>x.sKey)
EDIT: If there are so many fields which you dont want to show in the view, but still want to save with valid existing data, I would have the ID of that entry ( Ex : ProductId) in my view and use that Id to build the existing Product object and apply the new changes to that and save back. some thing like this
<div>Please enter the value:</div>
Key: #Model.sKey
#Html.LabelFor(h => h.ProductPrice);
#Html.TextBoxFor(h => ProductPrice);
#Html.HiddenFor(x=>x.ProductId)
and the action method will looks like
[HttpPost]
public ActionResult Edit (ProductViewModel newModel)
{
var oldProduct=repositary.GetProduct(newModel.ProductID);
oldProduct.ProductPrice=newModel.ProductPrice;
//Now save the oldProdcut
}
First off, you'll need to render the primary key(s) as a hidden element.
<div>Please enter the value:</div>
Key: #Model.sKey
#Html.HiddenFor(h => h.sKey)
#Html.LabelFor(h => h.sValue);
#Html.TextBoxFor(h => h.sValue);
Then use the UpdateModel() method in the Edit action of your controller. This helper function copies the values from the form collection to the model. There's no need to figure out which fields were editable, as UpdateModel does this for you.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues)
{
HashTable hash = repository.GetHashTable(id);
UpdateModel(hash);
repository.Save();
return RedirectToAction("Details", new { id = hash.DinnerID });
}