I am trying to get Fluent Validation to work correctly on my client side validation. I am using ASP.NET MVC 3.
I have a title that is required and it must be between 1 and 100 characters long. So while I am typing in the title an error message displays that is not in my ruleset. Here is my rule set:
RuleFor(x => x.Title)
.NotEmpty()
.WithMessage("Title is required")
.Length(1, 100)
.WithMessage("Title must be less than or equal to 100 characters");
Here is the error message that is displayed:
Please enter a value less than or equal to 100
I'm not sure what I am doing wrong. Here is my global.asax:
// FluentValidation
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(
new FluentValidationModelValidatorProvider(new AttributedValidatorFactory()));
ModelMetadataProviders.Current = new FluentValidationModelMetadataProvider(
new AttributedValidatorFactory());
Works fine for me. Here are the steps:
Create a new ASP.NET MVC 3 RTM project using the default Visual Studio Template
Download the latest FluentValidation.NET
Reference the FluentValidation.dll and FluentValidation.Mvc.dll assemblies (be careful there are two folders inside the .zip: MVC2 and MVC3 so make sure to pick the proper assembly)
Add a model:
[Validator(typeof(MyViewModelValidator))]
public class MyViewModel
{
public string Title { get; set; }
}
and a corresponding validator:
public class MyViewModelValidator : AbstractValidator<MyViewModel>
{
public MyViewModelValidator()
{
RuleFor(x => x.Title)
.NotEmpty()
.WithMessage("Title is required")
.Length(1, 5)
.WithMessage("Title must be less than or equal to 5 characters");
}
}
Add to Application_Start:
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(
new FluentValidationModelValidatorProvider(new AttributedValidatorFactory()));
ModelMetadataProviders.Current = new FluentValidationModelMetadataProvider(
new AttributedValidatorFactory());
Add a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new MyViewModel());
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
and the corresponding view:
#model SomeApp.Models.MyViewModel
#{
ViewBag.Title = "Home Page";
}
<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>
#using (Html.BeginForm())
{
#Html.TextBoxFor(x => x.Title)
#Html.ValidationMessageFor(x => x.Title)
<input type="submit" value="OK" />
}
Now try to submit the form leaving the Title input empty => client side validation kicks in and the Title is required message is shown. Now start typing some text => the error message disappears. Once you type more than 5 characters in the input box the Title must be less than or equal to 5 characters validation message appears. So everything seems to work as expected.
Related
I want to post a html with a wysiwyg editor so I used [AllowHtml] attribute on my property. It works, but when I use it with [Required] and [StringLength] attributes, it stops working. ModelState.IsValid returns true even if prop is empty.
Yes, I know I can manually control it if it is null and add an error to ModelState, but why?
Why this happens? Is there a better way to post some HTML code to back-end?
My dto:
public int Id { get; set; }
[Required(ErrorMessage = CErr.RequiredField)]
[StringLength(100, ErrorMessage = CErr.Min5Max100)]
[MinLength(5, ErrorMessage = CErr.Min5Max100)]
public string Name { get; set; }
[AllowHtml]
[Required(ErrorMessage = CErr.RequiredField)]
[StringLength(4000, ErrorMessage = CErr.TooLongValue)]
public string HtmlBody { get; set; }
My action:
[Route("new")]
[HttpPost]
public ActionResult NewMessageLayout(ManageMessageLayoutDto model)
{
if (ModelState.IsValid)
{
var response = Repositories.MessageLayoutRepository.SaveMessageLayout(model, CurrentUser.Id);
if (response.Status == ResultStatus.Success)
{
return RedirectToAction("MessageManagement");
}
else
{
ModelState.AddModelError("error", response.Message);
return View("ManageMessageLayout", model);
}
}
return View("ManageMessageLayout", model);
}
And some HTML:
#using (Html.BeginForm())
{
#Html.HiddenFor(m => m.Id)
<label>Name <span class="req">*</span></label>
#Html.TextBoxFor(m => m.Name, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Name, null, new { #class = "label label-danger" })
<label>Content <span class="req">*</span></label>
#Html.TextBoxFor(m => m.HtmlBody)
#Html.ValidationMessageFor(m => m.HtmlBody, null, new { #class = "label label-danger" })
<input class="btn btn-success btn-block btn-lg" type="submit" value="#(editing ? "Save" : "Add")" />
}
I am unable to replicate your error. However, I did notice a few things. First, according to the ckEditor documentation, textbox is not a valid attaching point for the editor. You should use textarea.
CkEditor Developer Documentation
At this point any textarea, p, or div element can be transformed into
a rich text editor by using the ckeditor() method.
Next, notice that I added AllowEmptyStrings=false to the [Required( attribute. This may be the important piece you are missing (will test without) - Testing seems to indicate that not setting AllowEmptyStrings does not impact the results of this test setup as the default value for this property is false. The model was still invalid.
Setup
VS 2015, MVC 5.2.2, .NET 4.5.1, jQuery 1.10.2, ckEditor 3.6.4
View Model
public class TestViewModel
{
[AllowHtml]
[Required(AllowEmptyStrings = false, ErrorMessage = "This should be filled out")]
[StringLength(4000, ErrorMessage="Its too big")]
public string HtmlBody { get; set; }
}
Controller Actions
public ActionResult About()
{
return View(new TestViewModel());
}
[HttpPost]
public ActionResult About(TestViewModel model)
{
if (!ModelState.IsValid) throw new Exception();
var test = model.HtmlBody;
return RedirectToAction("Contact");
}
View
#model WebApplication6.Models.TestViewModel
#{
ViewBag.Title = "About";
}
<h2>Test of HTML</h2>
#using (Html.BeginForm())
{
<label>Content <span class="req">*</span></label>
#Html.TextAreaFor(m => m.HtmlBody)
#Html.ValidationMessageFor(m => m.HtmlBody, null, new { #class = "label label-danger" })
<input type ="submit" value="test on server"/>
}
#section scripts
{
<script type="text/javascript">
$(function () {
$('#HtmlBody').ckeditor();
});
</script>
}
Results:
Basically, the exception was thrown because the model state was invalid
I found the problem.
My presentation layer was using 5.2.2.0 version of System.Web.Mvc but repository layer was using 5.2.3.0. I downgraded this to 5.2.2.0. And now it is working normally.
I found a lot of simulair questions but not a good clean solution that is working. I see a lot of custom code for getting this to work but why is that? Should this not working from the start?
What I think is strange, is that in IE9 it works but in Firefox and Chrome it is failing.
Everytime that im trying in Firefox or Chrome, I get the message "The field Birthday must be a date".
When I try the code below in a new MVC 4 RTM project, I can't get it to work. I see the DateTime.Now default as dd-MM-yyyy (Holland) in all the browsers but I can't submit it in Firefox and Chrome.
The globalization tag isn't set in web.config so it must be using the default. Im from Holland so it should get the client culture I guess.
public class RegisterModel
{
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
[Required]
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
//[DataType(DataType.Date)]
public DateTime Birthday { get; set; }
}
[AllowAnonymous]
public ActionResult Register()
{
RegisterModel vm = new RegisterModel()
{
Birthday = DateTime.Now
};
return View(vm);
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
try
{
//WebSecurity.CreateUserAndAccount(model.UserName, model.Password);
//WebSecurity.Login(model.UserName, model.Password);
return RedirectToAction("Index", "Home");
}
catch (MembershipCreateUserException e)
{
ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Markup
<!-- language: lang-none -->
#model DateTimeWithDatePicker.Models.RegisterModel
#{
ViewBag.Title = "Register";
}
<hgroup class="title">
<h1>#ViewBag.Title.</h1>
<h2>Create a new account.</h2>
</hgroup>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary()
<fieldset>
<legend>Registration Form</legend>
<ol>
<li>
#Html.LabelFor(m => m.UserName)
#Html.TextBoxFor(m => m.UserName)
</li>
<li>
#Html.LabelFor(m => m.Birthday)
#Html.EditorFor(m => m.Birthday)
</li>
</ol>
<input type="submit" value="Register" />
</fieldset>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
I was able to fix this by modifying the jQuery validator function for dates:
<script type="text/javascript">
$(function () {
var dateFormat="dd/mm/yy"; // en-gb date format, substitute your own
$("#Birthday").datepicker({
"dateFormat": dateFormat
});
jQuery.validator.addMethod(
'date',
function (value, element, params) {
if (this.optional(element)) {
return true;
};
var result = false;
try {
$.datepicker.parseDate(dateFormat, value);
result = true;
} catch (err) {
result = false;
}
return result;
},
''
);
});
The problem was jQuery validation and localization. It seems that there are localization files for messages and methods of the jQuery plugin. See my blog for a detail explaination of the problem and how I solved it.
http://www.locktar.nl/programming/mvc/localization-validation-in-mvc/
Edit:
I just released a new blog post with a refresh of all the localization problems and how to fix it for a DateTime property. See my new post MVC localization validation.
The globalization tag isn't set in web.config so it must be using the
default. Im from Holland so it should get the client culture I guess.
No, that's not correct. You could be perfectly fine from Holland and have configured your browser to use Chinese culture. The reason you cannot get this to work is probably because in FF and Chrome you don't have the correct culture.
Since you haven't specified a culture in your globalization element in web.config, ASP.NET MVC will use the one sent from the browser in the request header. For example if you configure your browser for en-US culture the following header will be set along each request:
Accept-Language:en-US,en;q=0.8
Here's how this is configured in Chrome:
So make sure you've put your desired language first in the list.
And if you want a reliable way to use the same syntax as the one defined in your DisplayFormat attribute during model binding you could write a custom model binder as shown here: https://stackoverflow.com/a/7836093/29407
I have the following View code:
#using (Html.BeginForm("Login", "Press", FormMethod.Post))
{
<fieldset>
<legend>User Registration</legend>
<div>
#Html.TextBoxFor(model => model.FullName)
#Html.ValidationMessageFor(model => model.FullName)
</div>
<div>
#Html.TextBoxFor(model => model.Company)
#Html.ValidationMessageFor(model => model.Company)
</div>
<div>
#Html.TextBoxFor(model => model.EmailAddress)
#Html.ValidationMessageFor(model => model.EmailAddress)
</div>
<div>
#Html.CheckBoxFor(model => model.JoinMailingList)
Please check this box to recieve a seasonal look book pdf and monthly newsletter
</div>
<p>
<input type="submit" value="Proceed" />
</p>
</fieldset>
}
And here is my Model:
public class UserViewModel
{
[Required(ErrorMessage = "Please enter your name.")]
[MaxLength(100)]
public string FullName { get; set; }
[Required(ErrorMessage = "Please enter the name of your company.")]
[MaxLength(50)]
public string Company { get; set; }
[Required(ErrorMessage = "Please enter your email.")]
[DataType(DataType.EmailAddress)]
[RegularExpression(#"^(([A-Za-z0-9]+_+)|([A-Za-z0-9]+\-+)|([A-Za-z0-9]+\.+)|([A-Za-z0-9]+\++))*[A-Za-z0-9]+#((\w+\-+)|(\w+\.))*\w{1,63}\.[a-zA-Z]{2,6}$", ErrorMessage = "Please enter a valid email address.")]
[MaxLength(255)]
public string EmailAddress { get; set; }
public bool JoinMailingList { get; set; }
}
The problem is that when I click on the 'Proceed' button, none of the validation occurs. It just posts the action with no validation performed on it? Do I have to perform this inside the Controller?
Here is my Controller code:
public class PressController : Controller
{
//
// GET: /Press
public ViewResult Index()
{
return View();
}
//
// GET: /Press/Login
public ViewResult Login()
{
return View();
}
[HttpPost]
public ActionResult Login(UserViewModel userViewModel)
{
return RedirectToAction("Index", "Press");
}
}
Make sure that the action you are posting to takes the view model as argument:
[HttpPost]
public ActionResult Press(UserViewModel model)
{
// at this stage the validation has been performed during
// the process of model binding and now you could look in the
// modelstate if the model is vaild:
if (!ModelState.IsValid)
{
// validation failed => redisplay the view so that the user
// can fix his errors.
// Note that calling ModelState.IsValid doesn't trigger any validation
return View(model);
}
// at this stage we know that validation passed => we could do some processing
// and redirect
return RedirectToAction("Success");
}
or some people also use the TryUpdateModel method which also allows you to perform model binding which triggers the validation:
[HttpPost]
public ActionResult Press()
{
var model = new UserViewModel();
// validation will be triggered at this stage and the method
// will return true or false based on the result of this validation
if (!TryUpdateModel(model))
{
// validation failed => redisplay the view so that the user
// can fix his errors.
return View(model);
}
// at this stage we know that validation passed => we could do some processing
// and redirect
return RedirectToAction("Success");
}
And if you want to enable client side validation, just make sure that the following 2 scripts are referenced in your page:
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
Do you have the client-validation enabled?
See "Step 3: Enabling Client-side Validation" in ScottGu's post
In the server side you must check is the model valid using
ModelState.IsValid
Using Visual Studio 2010, MVC project
When my form is submitted (currently via javascript, but same results with a submit button), the action is getting an empty model with both of the fields in it being zero instead of containing the value I entered into the textbox. The Request object does contain the correct name/value pair in the Form collection.
Model values going the other way work fine - so based on my [HttpGet] CallDisplayHome() action, the form loads with the textbox value being 1.
If anyone has a clue as to why it would not work coming back via POST, I would sure appreciate it.
Model being used:
namespace TCSWeb.Models
{
public class CallDisplayModel
{
public int SelectedRowIndex;
public int SelectedLineID;
}
}
View:
#model TCSWeb.Models.CallDisplayModel
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<body>
/*
There a Jscript datatable here and a bunch of scripts for working with it in the header I am skipping because I am hoping they are not relevant
*/
<div>
#using (Html.BeginForm("Testing", "CallDisplay", FormMethod.Post, new { name = "submitSelLine" }))
{
#Html.TextBoxFor(m => m.SelectedLineID)
<p>
<input type="submit" value="Log On" />
</p>
}
</div>
<button onclick="SubmitSelCallRecord()">#LangRes.Strings.calldisplay_opencallrecord</button>
My controller actions:
[HttpGet]
public ActionResult CallDisplayHome()
{
TCSWeb.Models.CallDisplayModel temper = new CallDisplayModel();
temper.SelectedLineID = 1;
temper.SelectedRowIndex = 1;
return View(temper);
}
[HttpPost]
public ActionResult Testing(TCSWeb.Models.CallDisplayModel cdmodel)
{
return RedirectToAction("CallDisplayHome"); //breaking here, cmodel has zero for selectedlineid
}
You need to declare your CallDisplayModel variables as properties:
public int SelectedRowIndex { get; set; }
[Required]
public int SelectedLineID { get; set; }
You can also add a little bit of validation to make sure that the user provides the correct information.
Change your post method to the following:
[HttpPost]
public ActionResult Testing(TCSWeb.Models.CallDisplayModel temper)
{
//check if valid
if(ModelState.IsValid)
{
//success!
return RedirectToAction("CallDisplayHome");
}
//update error! redisplay form
return View("CallDisplayHome", temper);
}
And display the errors in your view like so:
#Html.ValidationMessageFor(m => m.SelectedLineID)
#Html.TextBoxFor(m => m.SelectedLineID)
I'm unsure what your submitSelCallRecord button is doing, as it is referencing the javascript that was omitted.
I am currently facing a problem with validation after dynamically adding content.
I have a view strongly typed to a model (Order). This Order can have many items. The model looks something like the following:
public class Order
{
[Key]
[HiddenInput]
public int id { get; set; }
[Display(Name = "Order Number")]
public string number { get; set; }
[Display(Name = "Order Date")]
[DataType(DataType.Date)]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:MM/dd/yyyy}")]
public DateTime date { get; set; }
[Required(ErrorMessage = "Beneficiary is required.")]
[Display(Name = "Beneficiary")]
public int beneficiary_id { get; set; }
[Display(Name = "Beneficiary")]
public Beneficiary beneficiary { get; set; }
[Display(Name = "Items")]
public List<Item> items { get; set; }
[Display(Name = "Payment Method")]
public List<PaymentMethod> payment_methods { get; set; }
}
I enter the order information and also the items for that specific order. I tried a couple of ways to add content dynamically and finally went with Steven Sanderson's way.
In my view, I have the regular Order information and then the items, where my model looks something like this:
#model trackmeMvc.Models.Model.Order
#{
ViewBag.Title = "Create";
Html.EnableClientValidation();
Html.EnableUnobtrusiveJavaScript();
}
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/MicrosoftAjax.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/MicrosoftMvcAjax.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/MicrosoftMvcValidation.js")" type="text/javascript"></script>
#using (Html.BeginForm("Create", "Order", FormMethod.Post, new { #id = "create_order" }))
{
#Html.ValidationSummary(true, "Order creation was unsuccessful. Please correct the errors and try again.")
<div class="editor-label">
#Html.LabelFor(m => m.date)<req>*</req>
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.date, new { #id = "order_date" })<br />
#Html.ValidationMessageFor(m => m.date)
</div>
...
<script type="text/javascript">
$(document).ready(function () {
$("#addItem").click(function () {
var formData = $("#main_div").closest("form").serializeArray();
$.ajax({
url: "/IPO/BlankItemRow",
type: "POST",
//data: formData,
cache: false,
success: function (html) {
$("#editorRows").append(html);
//$.validator.uobtrusive.parseDynamicContent("form *");
//$("#editorRows").removeData("validator");
//$("#editorRows").removeData("unobtrusiveValidation");
//$.validator.unobtrusive.parse("#editorRows");
//$.validator.unobtrusive.parse("#create_ipo");
//$.validator.unobtrusive.parseDynamicContent($(this).first().closest("form"));
//$.validator.unobtrusive.parse($("#new_ipo_item"));
//$.validator.unobtrusive.parseElement($("#editorRows").find(".editRow:last").children().find("select"));
//$("#editorRows").find(".editRow:last").find("select").each(function () {
//alert($(this).attr("id"));
//$.validator.unobtrusive.parseElement($(this));
//$.validator.unobtrusive.parseDynamicContent($(this));
//$.validator.unobtrusive.parseDynamicContent($(this).attr("name"));
//});
//$("#editorRows").children().find(".editRows:last").find("*").each(function () {
// alert($(this).attr('id'));
//$.validator.unobtrusive.parseDynamicContent('input');
//});
//var form = $(this).closest("form").attr("id");
//$(form).removeData("validator");
//$(form).removeData("unobtrusiveValidation");
//$.validator.unobtrusive.parse(form);
}
});
return false;
});
});
</script>
Those are some of the things I tried, and nothing works.
I got the parseDynamicContent from Applying unobtrusive jquery validation to dynamic content in ASP.Net MVC. I tried it in every scenario I could think of, but still no luck.
I also tried the regular parse, removing validation from the form then applying it again, but still the newly added elements are not validated:
<div id="editorRows">
#foreach (var item in Model.items)
{
#Html.Partial("_NewItem", item)
}
</div>
... and my partial view would look something like this:
#model trackmeMvc.Models.Model.Item
#{
Layout = "";
Html.EnableClientValidation(true);
if (this.ViewContext.FormContext == null)
{
this.ViewContext.FormContext = new FormContext();
}
}
<div class="editRow">
#using (Html.BeginCollectionItem("order_items"))
{
#Html.DropDownListFor(m => m.item_id, #items, "None", new { #style = "width:205px;", #id = "ddlItems", #class="ddlItem", #name="ddlItemList" })
#Html.ValidationMessageFor(m => m.item_id)
...
}
</div>
So what's happening is, I have one empty item sent from the controller to the view by default, to show one empty row. That item is validated, but whatever comes after when I click add item, another row appears, from that partial, but I can't get it to validate. I tried to put the validation in the partial view, (before the document ready in the main form), and everything I read I applied, and it always ends up the same: validating the first row, and not the others. I tried the validation of Steven Sanderson done for that purpose - still no luck - even the validation for partials, found at this link
and the page that follows which is specific to partial validation...
What should I do to get this validation working?
Ok, I am going to start over with a new answer here.
Before you call $.validator.unobtrusive.parse, remove the original validator and unobtrusive validation from the form like so:
var form = $("#main_div").closest("form");
form.removeData('validator');
form.removeData('unobtrusiveValidation');
$.validator.unobtrusive.parse(form);
This same answer is documented here.
What worked for me was to re-apply the validator after the call to load the partial view. In my case, I'm using $.post().then() but you could do something similar with a .always() callback of an AJAX call.
$.post(url, model, function (data) {
//load the partial view
$("#Partial").html(data);
}).then(function () {
$("form").each(function () { $.data($(this)[0], 'validator', false); });
$.validator.unobtrusive.parse("form");
});