Edit: It seems as though no attributes work whatsoever, validation or not. For example, [DataType(DataType.Date)] doesn't change the markup in anyway either, while it should add 'type=date' to the element, the [Authorize] attribute lets even unauthorized users through etc. Anywone have any ideas about what could be the problem?
Edit 2: Added the following while trying to figure out what's wrong:
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(m => m.Name, Html.ViewData);
var val = metadata.GetValidators(new ControllerContext());
var attributes = metadata.ContainerType.GetProperties().Where(x => x.Name == "Name").FirstOrDefault().CustomAttributes;
The 'val' variable doesn't return any validators, while in a new project the same line works correctly and returns an IEnumerable of all the attributes the model property has. The 'attributes' variable, though, does find all the attributes the property has. So where do I go from here and find out what's wrong?
Edit 3: So I compared what the property 'ModelValidatorProviders.Providers' returns in Global.asax's 'Application_Start()' method between my project and a new project and it turns out that for some reason for my project it doesn't have a 'DataAnnotationsModelValidatorProvider'. So I did this at 'Application_Start()':
ModelValidatorProviders.Providers.Add(new DataAnnotationsModelValidatorProvider());
And now the validation works (except the [Authorize] attribute and others not related to models, it looks like they still dont work)! Not much of a fix, though, is it. Why isn't the provider in the ModelValidatorProviders property in the first place? Could it have anything to do with the fact that I have Autofac installed and maybe that messes with it somehow?
Edit 4: Fixed it, see answer.
Original post:
I'm pulling my hair out at this point, because I've read multiple articles about this and none of them help. The weird part is that the validation worked before and then it suddenly stopped working (I didn't change anything). I even restarted my PC, but that didn't really help.
Yes, both ClientValidationEnabled and UnobtrusiveJavaScriptEnabled are set to true in web.config
The required validation JavaScript files work fine too - they are imported correctly and load fine, but the problem doesn't even reach those, because the serverside isn't even generating the required HTML markup.
My ViewModel looks like this (the validation doesn't work anywhere on the site, so I could give you literally any viewmodel):
public class ChangePasswordViewModel
{
[Required]
[DataType(DataType.Password)]
[Display(Name = "Current password")]
public string OldPassword { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "New password")]
public string NewPassword { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm new password")]
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
And here's the view (pretty much the default MVC view as well, so there's nothing wrong with it):
#model AwenterWeb.Models.ChangePasswordViewModel
#{
ViewBag.Title = "Change Password";
}
<h2>#ViewBag.Title.</h2>
#using (Html.BeginForm("ChangePassword", "Manage", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<h4>Change Password Form</h4>
<hr />
#Html.ValidationSummary("", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(m => m.OldPassword, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.PasswordFor(m => m.OldPassword, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.NewPassword, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.PasswordFor(m => m.NewPassword, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.ConfirmPassword, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.PasswordFor(m => m.ConfirmPassword, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Change password" class="btn btn-default" />
</div>
</div>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
I put a breakpoint on Html.TextBoxFor and made sure that indeed the HTML that it returns does not have the necessary validation attributes.
What else could be broken? It is definitely not an issue with the Validation JS files, but rather Html.TextBoxFor and other helper classes for some reason can't tell that the fields have attributes on them!
After a whole day of trying to figure out what could possibly be wrong I decided to completely uninstall any traces of Autofac and Ninject I had (don't know why I had Ninject, don't remember ever installing it) and surprise, surprise things started working again. I then reinstalled the latest versions of the Autofac packages I had and attributes still worked, so maybe it had something to do with those rogue Ninject packages.
I still don't know why exactly Autofac or Ninject would mess with how attributes and validation work, without me explicitly telling them to do so, but at this point I don't even care.
Hopefully this helps someone!
Related
I am working with ASP.NET MVC 5 and trying to use the [Required] attribute to enable client side verification.
I get the following error:
Validation type names in unobtrusive client validation rules must be unique. The following validation type was seen more than once: required
I've searched for this error around here, but anything I tried didn't really help.
Index.cshtml view:
#{
ViewBag.Title = "Login";
}
#model CapitalWorkflowWebApplication.Models.LoginModel
<h2>Login</h2>
#using (Html.BeginForm("login-user", "Login", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="container">
<div class="row">
Login
</div>
<div class="row">
#Html.LabelFor(model => model.Username)
#Html.TextBoxFor(model => model.Username)
#Html.ValidationMessageFor(model => model.Username, "", new { #class = "text-danger" })
</div>
<div class="row">
#Html.LabelFor(model => model.Password)
#Html.TextBoxFor(model => model.Password)
#Html.ValidationMessageFor(model => model.Password, "", new { #class = "text-danger" })
</div>
<div class="row">
<input type="submit" value="Login" />
</div>
</div>
}
LoginModel:
using System.ComponentModel.DataAnnotations;
namespace CapitalWorkflowWebApplication.Models
{
public class LoginModel
{
[Required(ErrorMessage = "Le champ utilisateur est obligatoire")]
[Display(Name = "Utilisateur")]
public string Username { get; set; }
[Required(ErrorMessage = "Le champ mot de passe est obligatoire")]
[Display(Name = "Mot De Passe")]
[DataType(DataType.Password)]
public string Password { get; set; }
public string ErrorMessage { get; set; }
}
}
Following some of the comments/questions/answers here, I tried to set a breakpoint in my controller, and in the immediate window I ran this: ModelValidatorProviders.Providers and the result was:
[0]: {System.Web.Mvc.DataAnnotationsModelValidatorProvider}
[1]: {System.Web.Mvc.DataErrorInfoModelValidatorProvider}
[2]: {System.Web.Mvc.ClientDataTypeModelValidatorProvider}
I haven't used any custom validators.
In my web.config, I have these settings:
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
and in my layout I do have:
<script src="#Url.Content("~/Scripts/jquery-3.3.1.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
What I tried so far:
I removed the [Required] attribute from the LoginModel which removes the error, but also remove client verification, which is not the desired result.
I added DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false; to my application start code, but the error is always there.
I tried setting the ClientValidationEnabled key in web.config to false, but keeping the UnobtrusiveJavaScriptEnabled as true. It removes the error, but not client validation, duh.
I also tried setting the UnobtrusiveJavaScriptEnabled key as false, and keeping the ClientValidationEnabled as true. It also removes the error, but I don't see any client verification. It's always posting back to controller.
Note 1: I get the error while debugging in the view on the line of #Html.TextBoxFor(model=>model.Username);
Note 2: I am using Ninject as a Dependency resolver, if needed, I'll post my ninject config class, since I've seen some comments saying it could be the DI container causing this problem.
What could possibly be the problem I am facing, and how to solve it?
After some more digging and specially searching for problems related to Ninject, it turned out that Ninject was actually the source of the problem.
To fix this error, while creating the kernel, I added this:
kernel.Unbind<ModelValidatorProvider>();
After adding this, the error was gone and the client side validation was working as intended.
I'm trying to implement Remote Validation for a field in a view. Everything so far is working except the parameter in the validation controller method is null even though the field contains a value. What did I miss?
Validation Controller Method
public JsonResult IsVanityURL_Available(string VanityURL)
{
if (!_webSiteInfoRepository.GetVanityURL(VanityURL))
return Json(true, JsonRequestBehavior.AllowGet);
string suggestedUID = String.Format(CultureInfo.InvariantCulture,
"{0} is not available.", VanityURL);
for (int i = 1; i < 100; i++)
{
string altCandidate = VanityURL + i.ToString();
if (_webSiteInfoRepository.GetVanityURL(altCandidate)) continue;
suggestedUID = String.Format(CultureInfo.InvariantCulture,
"{0} is not available. Try {1}.", VanityURL, altCandidate);
break;
}
return Json(suggestedUID, JsonRequestBehavior.AllowGet);
}
Entity Property
[DisplayName("Vanity URL")]
[Remote("IsVanityURL_Available", "Validation")]
[RegularExpression(#"(\S)+", ErrorMessage = "White space is not allowed.")]
[Editable(true)]
public string VanityURL { get; set; }
View
<div class="row">
<div class="form-group col-md-12">
<div class="editor-label">
#Html.LabelFor(model => model.SelectedContact.WebSiteInfoes[0].VanityURL)
</div>
<div class="input-group margin-bottom-small">
<span class="input-group-addon"><i class="fa fa-external-link-square fa-fw"></i></span>
#Html.TextBoxFor(model => model.SelectedContact.WebSiteInfoes[0].VanityURL, new { #class = "form-control", #placeholder = "Enter Vanity URL" })
</div>
</div>
</div>
UPDATE
The answer in the duplicate post does fix the problem.
I found an alternate way to avoid changing the jquery.validate.js file. This involved setting the name of the TextBoxFor in the view like so...
#Html.TextBoxFor(model => model.SelectedContact.WebSiteInfoes[0].VanityURL, new { #class = "form-control", #placeholder = "Enter Vanity URL", #Name="VanityUrl })
I reverted my js file change, then added a combination of the view name attribute and the model remote AdditionalFields definition and it worked just fine.
This change caused some unforeseen problems as well. I finally did get a solution. I changed the GET to a POST and grabbed the values I needed from the FormsCollection. This link got me going in the right direction. This allowed me to completely bypass the Complex Data Object naming problem
change your entity
[DisplayName("Vanity URL")]
[Remote("IsVanityURL_Available", "Validation",AdditionalFields = "VanityURL")]
[RegularExpression(#"(\S)+", ErrorMessage = "White space is not allowed.")]
[Editable(true)]
public string VanityURL { get; set; }
and add this to your view
#{
var VanityURL=Model.SelectedContact.WebSiteInfoes[0].VanityURL
}
<div class="row">
<div class="form-group col-md-12">
<div class="editor-label">
#Html.LabelFor(model => model.SelectedContact.WebSiteInfoes[0].VanityURL)
</div>
<div class="input-group margin-bottom-small">
<span class="input-group-addon"><i class="fa fa-external-link-square fa-fw"></i></span>
#Html.TextBox("VanityURL",VanityURL,new { #class = "form-control", #placeholder = "Enter Vanity URL" })
</div>
</div>
</div>
Never had this problem before but it is happening on a single MVC action, all other model properties are being passed to the action method but ONE is omitted.
In the BilledItemViewModel model the property that exhibits the problem is declared as:
[Required]
[Display(Name="Factura")]
public int BillId { get; set; }
all other properties work fine but that one above does not.
The action method signature:
[HttpPost][ValidateAntiForgeryToken]
public ActionResult AddBillItem([Bind(Include = "BillId,ItemCodeId,Quantity,UnitPrice,Notes,TaxPercent,IsDebit,Description")] Models.BilledItemViewModel bitem)
The property is being displayed on the View as follows:
<div class="form-group">
#Html.LabelFor(model => model.BillId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<div class="col-sm-4 input-group">
<span class="input-group-addon">#</span>
#Html.EditorFor(model => model.BillId, new { htmlAttributes = new { #class = "form-control", disabled = "disabled" } })
#Html.ValidationMessageFor(model => model.BillId, "", new { #class = "text-danger" })
</div>
</div>
</div>
When debugging I set a breakpoint on the action and it hits, it also says Model State IS valid. When I examine the values of the passed object (bitem) I can see all properties have the values I entered in the form EXCEPT BillId which is ZERO rather than ONE.
The problem is that your BillId is disabled in your view. Disabled fields are not sent to the server. Either just enable it for editing or make it a hidden input.
#Html.HiddenFor(h => h.BillId)
Hidden input will not be visible for users but will be sent to the server as well as the other fields from your form.
You can mark it as readonly as well.
Readonly controls will be sent to server.
Where the heck are these things coming from? I like them, and I would like to leverage them elsewhere in my site. It appears they only show when I do regular expression validation in model:
[Display(Name = "Residential")]
[RegularExpression(#"[-+]?[0-9]*\.?[0-9]?[0-9]", ErrorMessage = "Must be a number")]
public Byte? residentialExperience { get; set; }
<div class="editor-label row">
#Html.LabelFor(model => model.residentialExperience)
</div>
<div class="editor-field row">
#Html.EditorFor(model => model.residentialExperience)
#Html.ValidationMessageFor(model => model.residentialExperience)
</div>
How can I use these validation tooltips elsewhere? Also, how can I turn them off?
Also: It's not displaying the same message as I have in my model. It says, "Please enter a number" whereas I have written "Must be a number."
This is because you are outputting a numeric field. If you look at your HTML you will see that you have something like this:
<input type="number" ... />
By defining the type as a numbber, the browser knows what to expect and it will give you a generic message. This is part of Html 5 spec.
If you want to override the default behavior you could do this:
#Html.TextBoxFor(model => model.residentialExperience, new { #type = "text" })
I have below view model
public class QuestionarrieAnswersViewModel
{
public long QuestionID { get; set; }
public string Question { get; set; }
[Required(ErrorMessage="required")]
[StringLength(255, ErrorMessage = "Maximum 255 characters are allowed.")]
public string Answer { get; set; }
}
and i am generating view in below way
#model List<BusinessLayer.Models.ViewModel.QuestionarrieAnswersViewModel>
#using (Ajax.BeginForm("SaveQuestionarrie", "Member", FormMethod.Post, new AjaxOptions { OnBegin = "OnBegin", OnComplete = "OnComplete" }, new { #class = "form-horizontal" }))
{
for(int i=0;i<Model.Count;i++)
{
<div class="control-group">
<div class="head_form">
<label class="control-label">#Model[i].Question</label>
<div class="controls">
#Html.TextAreaFor(m=>m[i].Answer)
#Html.ValidationMessageFor(m => m[i].Answer)
#Html.HiddenFor(m=>m[i].QuestionID)
</div>
</div>
</div>
}
<div class="control-group">
<div class="controls">
<button class="btn" type="submit">Save</button>
</div>
</div>
}
I have set dataannotation on Answer field in above model but its not applying in above view while it works if i generate view in below way
#model BusinessLayer.Models.ViewModel.QuestionarrieAnswersViewModel
#using (Ajax.BeginForm("SaveQuestionarrie", "Member", FormMethod.Post, new AjaxOptions { OnBegin = "OnBegin", OnComplete = "OnComplete" }, new { #class = "form-horizontal" }))
{
#Html.TextAreaFor(m => m.Answer)
#Html.TextAreaFor(m => m.QuestionID)
<div class="control-group">
<div class="controls">
<button class="btn" type="submit">Save</button>
</div>
</div>
}
What's going wrong here...
In order to fire those validation rules, you'll need to use an EditorFor instead of a TextAreaFor.
It's because there's an outstanding issue with validation of TextArea's, see here: http://aspnet.codeplex.com/workitem/8576.
This is due to a bug in the version of jquery.validate.unobtrusive.js that was released with ASP.NET MVC3. This answer is on the same bug, the solution to this is to upgrade to the latest version of jquery.validate.unobtrusive.js - either grab it from an MVC4 project or update using NuGet.
The jquery.validate.unobtrusive.js script doesn't seem to have a version number so if you search in the script for a function called escapeAttributeValue, then this is a version of the script that has this bug fix.
The problem that is addressed in the bug fix is how to handle markup generated having name attributes containing characters that need escaping in a jQuery selector. In this case
<textarea cols="20" name="[0].Answer" rows="2"></textarea>
needs this selector
$('[name=\\[0\\]\\.Answer]')
The client-side DataAnnotation (validation) does not work for the Html.TextAreaFor() helper.
To make it work, you have to decorate the 'Answer' property with the [DataType(DataType.MultilineText)] attribute. And in the view, use Html.EditorFor() helper instead of the Html.TextAreaFor() helper mehthod.
See similar SO answer asp.net mvc TextAreaFor is not getting validated as a required field.