MVC 3 field validation keep focus on failure - asp.net-mvc-3

We are working on some accessibility standards for a basic website and need to have focus returned to a username field if it is has failed validation as defined in the model. I have read several posts that indicate this is the behavior as it exists. But, this is not the behavior we are seeing. I am open to mvc natice functionality (we are using mvc 3 with razor) or jquery)

I have read several posts that indicate this is the behavior as it exists
Weird, where did you those posts? I would recommend you notifying the author of such posts that this is not a default behavior.
And of course when something is not the default behavior, if you want to achieve it, you will have to implement it. For example you haven't exactly specified how it should behave if there are multiple errors: which field should be focused? The first? The third? The seventh?
Let's suppose that you want to focus the first. Assuming you are using jQuery you could add the following to your view:
#if (!ViewData.ModelState.IsValid)
{
var key = ViewData
.ModelState
.Where(x => x.Value.Errors.Count > 0)
.Select(x => x.Key)
.FirstOrDefault();
if (!string.IsNullOrEmpty(key))
{
<script type="text/javascript">
$(function () {
$(':input[name=#Html.Raw(Json.Encode(key))]').focus();
});
</script>
}
}
and you are pretty much done. Well, almost, a further improvement of this code would of course be to externalize this into a reusable HTML helper to avoid transforming your views into something absolutely horrible. For example you could have a custom helper which would implement this behavior and all you have to do is add the following to your _Layout:
#Html.FocusOnFirstError()
Could be implemented with something along the lines of:
public static class HtmlExtensions
{
public static IHtmlString FocusOnFirstError(this HtmlHelper htmlHelper)
{
if (htmlHelper.ViewData.ModelState.IsValid)
{
return MvcHtmlString.Empty;
}
var key = htmlHelper
.ViewData
.ModelState
.Where(x => x.Value.Errors.Count > 0)
.Select(x => x.Key)
.FirstOrDefault();
if (string.IsNullOrEmpty(key))
{
return MvcHtmlString.Empty;
}
var script = new TagBuilder("script");
script.Attributes["type"] = "text/javascript";
script.InnerHtml = string.Format(
"$(function() {{ $(':input[name={0}]').focus(); }});",
Json.Encode(key)
);
return new HtmlString(script.ToString(TagRenderMode.Normal));
}
}

Related

ASP.NET MVC Partial View Post-Back, Inconsistent Updating and Bizzare Behavior

I'm developing an ASP.Net MVC application, and am running into a bizzare issue when trying to update data in my database via a partial postback. I'm still new when it comes to HTTP, AJAX, etc., so I'm hoping it's an obvious error.
Basically, when I try to update a table linking content areas to assessments, the update sometimes works, sometimes doesn't. What's bizzare is that after I post, I query the database directly from the MVC application just to make sure that the expected change was in fact made (that's what all the ViewBag.DebugInfo's are doing in the code below). In every case, the query returns what I hope to see. But then when I query the table via SSMS, I see that the change only sometimes goes through.
How is it that a direct query to a table from my MVC application is showing that an update went through, while I can clearly see that it didn't via SSMS? Is there a silent rollback or something? This is maddening and any help would be much appreciated.
A few bits of info:
When I run the "saveTheData" function from the assessmentContent class below outside of MVC, it is always successful.
The update is always successful on the first post.
The update is successful only about half the time on subsequent posts.
When the update is not successful, the the direct query checks from the MVC application nevertheless do seem to show that the update made it all the way to the table.
I can barely make out a pattern in the failures. Namely, it seems that whenever I try to update to a higher contentId value, it is successful, if I try to update to a lower contentId, it is not. For instance, updating from a value of 1 (Math) to 2 (Reading) will always go through, but the reverse will not. This pattern does not manifest if it's the first post from the parent view, or if it's updated via Linqpad.
I put insert, update, and delete triggers on the database table that write to a logging table to see if perhaps the changes were being rolled back. But no entries on the log table on failure. But I also don't know if rollbacks would undo the triggers as well.
I queried dbo.fn_dblog() filtered for Operation = 'LOP_ABORT_XACT', but nothing (though I don't have trained eyes for that difficult view).
Here's my class that fetches and updates the data:
public class assessmentContent {
public int? assessmentId { get; set; }
public List<short> baseline { get; set; } = new List<short>();
public List<short> comparison { get; set; } = new List<short>();
public assessmentContent() { if (assessmentId != null) refreshTheData(); }
public assessmentContent(int assessmentId) {
this.assessmentId = assessmentId;
refreshTheData();
}
public void saveTheData() {
List<short> upserts = comparison.Except(baseline).ToList();
List<short> deletes = baseline.Except(comparison).ToList();
foreach (var upsert in upserts)
reval.ach.addAssessmentContent(assessmentId, upsert);
foreach (var delete in deletes)
reval.ach.deleteAssessmentContent(assessmentId, delete);
refreshTheData();
}
void refreshTheData() {
baseline = reval.ach.assessmentContent(assessmentId).ToList();
comparison = reval.ach.assessmentContent(assessmentId).ToList();
}
}
The logic works fine when I use it outside of my MVC application. So, for instance, if I use it via linqpad, there are no issues. I should mention that assessmentContent() could be named 'getAssessmentContent()'.
Here's my Controller for the Partial View, and some related Code:
public class ContentsModel {
public int? assessmentId { get; set; }
public List<short> comparison { get; set; }
}
public class ContentsController : Controller {
public static string nl = System.Environment.NewLine;
public ActionResult ContentsView(int assessmentId) {
ViewBag.DebugInfo = new List<string>();
var vm = new ContentsModel();
vm.assessmentId = assessmentId;
vm.comparison = reval.ach.assessmentContent(assessmentId).ToList();
return View("~/Views/ach/Contents/ContentsView.cshtml", vm);
}
public ActionResult update(ContentsModel vm) {
ViewBag.DebugInfo = new List<string>();
sqlFetch();
ViewBag.DebugInfo.Add($"VM Pased In {vm.assessmentId} c{vm.comparison.intsJoin()}");
sqlFetch();
var crud = new crud.ach.assessmentContent((int)vm.assessmentId);
ViewBag.DebugInfo.Add($"newly fetched CRUD {crud.assessmentId} b{crud.baseline.intsJoin()} c{crud.comparison.intsJoin()}");
sqlFetch();
crud.comparison = vm.comparison;
ViewBag.DebugInfo.Add($"CRUD after crud_comparison = vm_comparison {crud.assessmentId} b{crud.baseline.intsJoin()} c{crud.comparison.intsJoin()}");
sqlFetch();
crud.saveTheData();
ViewBag.DebugInfo.Add($"CRUD after save {crud.assessmentId} b{crud.baseline.intsJoin()} c{crud.comparison.intsJoin()}");
sqlFetch();
vm.comparison = crud.comparison;
ViewBag.DebugInfo.Add($"VM after vm_comparison = crud_comparison {vm.assessmentId} c{vm.comparison.intsJoin()}");
sqlFetch();
return PartialView("~/Views/ach/Contents/ContentsView.cshtml", vm);
}
void sqlFetch() {
ViewBag.DebugInfo.Add(
"SQL Fetch " +
Sql.ExecuteOneColumn<short>("select contentId from ach.assessmentContent where assessmentId = 12", connections.research).intsJoin()
);
}
}
public static partial class extensions {
public static string intsJoin(this IEnumerable<short> ints) {
var strings = new List<string>();
foreach (int i in ints)
strings.Add(i.ToString());
return string.Join(",", strings);
}
}
I'm aware that I might not have the 3-tier architecture or the Model-View-Controller structure best implemented here.
You'll notice that, in my desperation, I put in a direct check to the database table at every point of change in models.
The Partial View:
#model reval.Views.ach.Contents.ContentsModel
#using reval
#{Layout = "";}
<div id="contentDiv">
<form id="contentForm">
#Html.HiddenFor(m => m.assessmentId)
#Html.ListBoxFor(
m => m.comparison,
new reval.ach.content()
.GetEnumInfo()
.toMultiSelectList(
v => v.Value,
d => d.DisplayName ?? d.Description ?? d.Name,
s => Model.comparison.Contains((short)s.Value)
),
new { id = "contentListBox" }
)
</form>
<br/>
#foreach(string di in ViewBag.DebugInfo) {
#Html.Label(di)
<br/>
}
</div>
<script>
$("#contentListBox").change(function () {
$.ajax({
url: "/Contents/update",
type: "get",
data: $("#contentForm").serialize(),
success: function (result) {
$("#contentDiv").html(result);
},
error: function (request, status, error) {
var wnd = window.open("about:blank", "", "_blank");
wnd.document.write(request.responseText);
}
});
})
</script>
And Finally, the call from the main View:
<div id="testDiv">
#if (Model.assessment != null && Model.assessment.assessmentId != null) {
Html.RenderAction("ContentsView", "Contents", new { assessmentId = Model.assessment.assessmentId });
}
</div>
Are you sure that the transaction you are making to the database is committed or finalized? There could be other transactions going on at same time that could roll back the ones you are making.
I hate it when I solve an issue without exactly pinning down what I did to solve it. But after working on some of the code above, I got it working correctly and consistently.
I'm almost certain, however, that the issue had to do with a misunderstanding of how asp.net mvc works when passing data from server to client and back. Namely, I had an idea that my C# viewmodels and controllers on the server were still alive when their data was being sent to html/asp on the client. I had a hunch that the client data was not the same as the C# objects, but I did feel that ASP.Net MVC was updating the C# object for any changes on postback. Now it is clear to me that in fact the C# objects are fully discarded and completely instantiated (with the constructors called and all related consequences) and repopulated with data from client. And this is true even when no changes are made at the client.
I think that updates were in fact being made to the database. No rollback was occurring. But something was happening upon re-instantiation that was causing a second call to the database and resetting their values. This would explain why it was working perfectly outside of ASP.net MVC. It would explain why I solved the issue after this realization.
I would call this response accurate, but not precise. By that I mean that I have confidence that the guidance resolves the issue, even if it doesn't pin down the exact lines of offending code above. Due to the accuracy, I'm considering it fair game to mark it as an answer. Due to the imprecision, I'm open to marking someone else's response as the answer if they can be more precise. However, since the code above is no longer in use, it is all just for learning purposes.

Add custom data-* attributes to Kendo UI AutoComplete or ComboBox

Currently using the Kendo UI AutoCompleteFor() and ComboBoxFor() helper.
Noticing that they generate/render a bunch of <li>s.
How does one add additional custom data-* attributes to those <li>s?
Here's the current scenario:
The user starts typing stuff in the AutoCompleteFor
An ajax call is triggered to fetch some data related to what the
user has typed.
The obtained results are transformed into an
IEnumerable<SelectListItem>.
The result is then sent to Json. Json(result, JsonRequestBehavior.AllowGet)
My goal is to add one or more additional data-* attribute to each of these <li> generate lines so that I can fetch these data-* in the onChange() event.
How does one achieve this?
In addition, I'm aware that I could create my own .Template() and possibly achieve my task but I was curious if anyone knows of a different way to do this then having to create my own template.
Sincerely
Ok I've found a solution; I'll share it here in case anyone is interested.
Instead of transforming my obtained results into an IEnumerable<SelectListItem>, I simply transform this into an IEnumerable<CustomDTO>.
The CustomDTO class looks like this:
public class CustomDTO
{
public int Value { get; set; }
public string Text { get; set; }
public int Age { get; set; }
//Add whatever more properties you think you’ll need.
}
In my controller, I then do the following:
var result = _myService.GetData().ToList();
return Json(result, JsonRequestBehavior.AllowGet);
Where GetData() returns an IEnumerable<CustomDTO>.
Inside my View, I have an AutoCompleteFor() control to which I bind a client side
.Events(x => x.Select("onSelect") event handler.
The handler is defined like so:
function onSelect(e)
{
if (e.item == null) return;
var dataItem = this.dataItem(e.item.index());
var valueAttribute = dataItem.Value;
var textAttribute = dataItem.Text;
var ageAttribute = dataItem.Age; //This is how I get my additional value
//...code...
}
So that's it.

Restrict Kendo Grid to Current Route Id

I'm trying to include a Kendo UI ASP.NET MVC Grid on the Edit page of my MVC application and I would like to restrict that grid to only return values from the current route id. I've been researching ways to do this, but can't find anything that's exactly what I need, or I'm just too new at this to connect the dots.
My ideas so far are to either apply a filter to the DataSource or send a parameter to the controller and have it restrict the DataSourceResult.
For the DataSource filter in the view, I can do this:
.Filter(filters => { filters.Add(d => d.CompanyId).IsEqualTo(2); })
But I can't figure out how to replace the hardcoded 2 with the value from, say, #ViewContext.RouteData.Values["id"], or something.
Passing a parameter to the controller, I can get the following:
public ActionResult Divisions_Read([DataSourceRequest]DataSourceRequest request, int id)
{
using (var db = new AppContext())
{
IQueryable<Division> divisions = db.Divisions;
DataSourceResult result = divisions.ToDataSourceResult(request, division => new DivisionViewModel
{
DivisionId = division.DivisionId,
CompanyId = division.CompanyId,
CountryId = division.CountryId,
DivisionName = division.DivisionName
});
return Json(result);
}
}
But I have no idea how to use that id to basically add a "where CompanyId = Id" statement to the result.
Any ideas on what the best way to do this would be? Am I missing something really obvious?
Thanks!
ETA: Passing the parameter to the Controller through the Read action, as suggested by mmillican and other places in my research, and changing
DataSourceResult result = divisions.ToDataSourceResult(request, division => new DivisionViewModel
to
DataSourceResult result = divisions.Where(c => c.CompanyId == companyId).ToDataSourceResult(request, division => new DivisionViewModel
did the trick. That extra bit in the controller was what I was looking for, but couldn't seem to find anywhere.
Assuming you have a model for your page that looks like this:
public class MyViewModel
{
public int Id { get; set; } // assuming Id is what you want to filter by
// .. other VM properties
}
you can do something like this in your grid data binding
.DataSource(ds => ds
.Ajax()
.Model(mdl => mdl.Id(x => x.Id))
.Read("Divisions_Read", "Divisions", new { id = Model.Id })
)
in your controller, you would set the model's Id property to whatever ID that is in the route.

How to convert this asp.net usercontrol to work in MVC 3

I have a need to port one of my asp.net user controls over for use in MVC 3 but it looks like MVC isn't setup to support user controls.
I see posts claiming user controls are an anti-pattern of MVC. I'm not interested in a debate on that I just need to get this user control ported over regardless of what side of that supposed fence your on. I don't see this being a html helper either. Placing mountains of javascript inside double quotes as a parameter to a helper negates any benefit due to loss of readability, intelli-sense support, maintainability.
The control is called the ScriptCompressor. Its purpose is to take the place of all inline script tags on the page. During rendering all of the inline javascript that ultimately make up a call to a dynamic page are combined and placed into either one or two script "wrappers" at the bottom/top of the page. The whole point of this control is to reduce page size and improve load performance by minification and compression of all inline scripts. There is no view state involved it simply outputs a script tag and javascript.
How do I call my existing control inside an MVC 3 Razor page?
Example from ASP.net usage:
<uc:ScriptCompressor Compress="true">
var myGlobalVar = "something";
</uc:ScriptCompressor>
<uc:ScriptCompressor Compress="true" UseReadyWrapper="true">
var _registrationViewModel = new RegisterViewModel();
...
</uc:ScriptCompressor>
<uc:ScriptCompressor Compress="true" UseReadyWrapper="true">
var _addNewUserViewModel = new AddNewUserViewModel();
...
</uc:ScriptCompressor>
Uncompressed Output:
<script type="text/javascript">
var myGlobalVar = "something";
$(function () {
var _registrationViewModel = new RegisterViewModel();
var _addNewUserViewModel = new AddNewUserViewModel();
});
</script>
You could use a conjunction of 2 custom HTML helpers to replace your existing server side control:
public static class HtmlExtensions
{
private const string GlobalsQueueKey = "_globals_queue_";
private const string ReadyWrapperQueueKey = "_ready_wrapper_queue_";
[Obsolete("Don't use inline scripts, that's what javascript files are meant for. And yeah, there are pretty nifty javascript compressors out there. Not to mention that in ASP.NET MVC 4 there's the bundling and minification functionality built-in. So use this helper just for fun not in a real application that you intend to put in production.")]
public static IHtmlString RegisterInlineScript(this HtmlHelper htmlHelper, bool compress, bool useReadyWrapper, Func<object, HelperResult> action)
{
var queueKey = useReadyWrapper ? ReadyWrapperQueueKey : GlobalsQueueKey;
var queue = htmlHelper.ViewContext.HttpContext.Items[queueKey] as Queue<Func<object, HelperResult>>;
if (queue == null)
{
queue = new Queue<Func<object, HelperResult>>();
htmlHelper.ViewContext.HttpContext.Items[queueKey] = queue;
}
queue.Enqueue(action);
return MvcHtmlString.Empty;
}
[Obsolete("Don't use inline scripts, that's what javascript files are meant for. And yeah, there are pretty nifty javascript compressors out there. Not to mention that in ASP.NET MVC 4 there's the bundling and minification functionality built-in. So use this helper just for fun not in a real application that you intend to put in production.")]
public static IHtmlString InlineScripts(this HtmlHelper htmlHelper)
{
var globalsQueue = htmlHelper.ViewContext.HttpContext.Items[GlobalsQueueKey] as Queue<Func<object, HelperResult>> ?? new Queue<Func<object, HelperResult>>();
var readyWrapperQueue = htmlHelper.ViewContext.HttpContext.Items[ReadyWrapperQueueKey] as Queue<Func<object, HelperResult>> ?? new Queue<Func<object, HelperResult>>();
if (!globalsQueue.Any() && !readyWrapperQueue.Any())
{
// Nothing to compress, nothing to output
return MvcHtmlString.Empty;
}
var writer = htmlHelper.ViewContext.Writer;
writer.Write("<script type=\"text/javascript\">");
using (var globalsWriter = new StringWriter())
{
foreach (var item in globalsQueue)
{
item(null).WriteTo(globalsWriter);
}
var globals = globalsWriter.GetStringBuilder().ToString();
writer.Write(Compress(globals));
}
using (var readyWrapperWriter = new StringWriter())
{
foreach (var item in readyWrapperQueue)
{
item(null).WriteTo(readyWrapperWriter);
}
var readyWrapper = readyWrapperWriter.GetStringBuilder().ToString();
writer.Write(
string.Format("$(function() {{{0}}});", Compress(readyWrapper))
);
}
writer.Write("</script>");
return MvcHtmlString.Empty;
}
private static string Compress(string script)
{
// TODO: wheel reinvention code from your existing
// server side control to be put here to compress
return script;
}
}
and then you could have a view in which you register multiple inline scripts:
#Html.RegisterInlineScript(true, false,
#<text>
var myGlobalVar = "something";
</text>
)
#Html.RegisterInlineScript(true, true,
#<text>
var _registrationViewModel = new RegisterViewModel();
</text>
)
#Html.RegisterInlineScript(true, true,
#<text>
var _addNewUserViewModel = new AddNewUserViewModel();
</text>
)
and somewhere in your _Layout output them:
#Html.InlineScripts()
OK, now for your real application checkout the bundling and minification support in ASP.NET MVC 4: http://www.beletsky.net/2012/04/new-in-aspnet-mvc4-bundling-and.html

Validate checkbox on the client with FluentValidation/MVC 3

I am trying to validate if a check box is checked on the client using FluentValidation. I can't figure it our for the life of me.
Can it be done using unobtrusive validation?
Let's assume that you have the following model:
[Validator(typeof(MyViewModelValidator))]
public class MyViewModel
{
public bool IsChecked { get; set; }
}
with the following validator:
public class MyViewModelValidator : AbstractValidator<MyViewModel>
{
public MyViewModelValidator()
{
RuleFor(x => x.IsChecked).Equal(true).WithMessage("Please check this checkbox");
}
}
and a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
with a corresponding view:
#model MyViewModel
#using (Html.BeginForm())
{
#Html.LabelFor(x => x.IsChecked)
#Html.CheckBoxFor(x => x.IsChecked)
#Html.ValidationMessageFor(x => x.IsChecked)
<button type="submit">OK</button>
}
and in Global.asax you have registered the fluent validation model validator provider:
FluentValidationModelValidatorProvider.Configure();
So far we have server side validation up and running fine. That's good. That's always the first part that we must setup. I have seen people focusing too much on doing client side validation that they forget doing server side validation and when you disable javascript (or even worse if you stumble upon a user with bad intentions), well, bad things happen.
So far we are confident because we know that even if something gets screwed up on the client our domain is protected with server side validation.
So let's now take care for the client validation. Out of the box FluentValidation.NET supports automatic client validation for the EqualTo validator but when comparing against another property value which is the equivalent of the [Compare] data annotation.
But in our case we are comparing against a fixed value. So we don't get client side vaildation out of the box. And when we don't get something out of the box, we need to put it in the box.
So we start by defining a custom FluentValidationPropertyValidator:
public class EqualToValueFluentValidationPropertyValidator : FluentValidationPropertyValidator
{
public EqualToValueFluentValidationPropertyValidator(ModelMetadata metadata, ControllerContext controllerContext, PropertyRule rule, IPropertyValidator validator)
: base(metadata, controllerContext, rule, validator)
{
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
if (!this.ShouldGenerateClientSideRules())
{
yield break;
}
var validator = (EqualValidator)Validator;
var errorMessage = new MessageFormatter()
.AppendPropertyName(Rule.GetDisplayName())
.AppendArgument("ValueToCompare", validator.ValueToCompare)
.BuildMessage(validator.ErrorMessageSource.GetString());
var rule = new ModelClientValidationRule();
rule.ErrorMessage = errorMessage;
rule.ValidationType = "equaltovalue";
rule.ValidationParameters["valuetocompare"] = validator.ValueToCompare;
yield return rule;
}
}
that we are going to register in Application_Start:
FluentValidationModelValidatorProvider.Configure(provider =>
{
provider.AddImplicitRequiredValidator = false;
provider.Add(typeof(EqualValidator), (metadata, context, description, validator) => new EqualToValueFluentValidationPropertyValidator(metadata, context, description, validator));
});
So far we have associated our custom FluentValidationPropertyValidator with the EqualValidator.
The last part is to write a custom adapter:
(function ($) {
$.validator.unobtrusive.adapters.add('equaltovalue', ['valuetocompare'], function (options) {
options.rules['equaltovalue'] = options.params;
if (options.message != null) {
options.messages['equaltovalue'] = options.message;
}
});
$.validator.addMethod('equaltovalue', function (value, element, params) {
if ($(element).is(':checkbox')) {
if ($(element).is(':checked')) {
return value.toLowerCase() === 'true';
} else {
return value.toLowerCase() === 'false';
}
}
return params.valuetocompare.toLowerCase() === value.toLowerCase();
});
})(jQuery);
And that's pretty much it. All that's left is to include the client scripts:
<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>
<script src="#Url.Content("~/Scripts/customadapter.js")" type="text/javascript"></script>
I like the Darin Dimitrov's answer, but if you want to do it quickly, here is my alternative way.
Create an additional property in your model, e.g.:
public bool ValidationTrue { get; set; }
and set its value to true in the model's contructor.
Use it in your view to save the value across the requests:
#Html.HiddenFor(x => x.ValidationTrue)
Now add a validation rule like this:
public class MyViewModelValidator : AbstractValidator<MyViewModel>
{
public MyViewModelValidator()
{
RuleFor(x => x.ValidationTrue)
.Equal(true); // check it for security reasons, if someone has edited it in the source of the page
RuleFor(x => x.HasToBeChecked)
.Equal(x => x.ValidationTrue) // HasToBeChecked has to have the same value as ValidationTrue (which is always true)
.WithMessage("Required");
}
}
That validation is supported by the unobtrusive validator out-of-the-box.
I am coding in ASP.NET MVC5 and Darin's code produces a javascript error on the lines that reference value.ToLowerCase() when a checkbox is involved. Another issue is that this code invalidates the client side equality comparison between two properties. It only seems to work when comparing against a literal value...That may have been his intent, but I need it to work for both situations:
Here's one possible workaround, that involves only two changes to Darin's answer:
First, I updated the javascript function with the following.
$.validator.addMethod('equaltovalue', function (value, element, params) {
if ($(element).is(':checkbox')) {
value = $(element).is(':checked') ? "true" : "false";
}
return params.valuetocompare.toLowerCase() === value.toLowerCase();
});
Secondly, I updated EqualToValueFluentValidationPropertyValidator with the following:
public class EqualToValueFluentValidationPropertyValidator : FluentValidationPropertyValidator
{
EqualValidator EqualValidator
{
get { return (EqualValidator)Validator; }
}
public EqualToValueFluentValidationPropertyValidator(ModelMetadata metadata, ControllerContext controllerContext, PropertyRule rule, IPropertyValidator validator) : base(metadata, controllerContext, rule, validator) {
ShouldValidate = false;
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules() {
if (!ShouldGenerateClientSideRules()) yield break;
var propertyToCompare = EqualValidator.MemberToCompare as PropertyInfo;
if(propertyToCompare != null) {
// If propertyToCompare is not null then we're comparing to another property.
// If propertyToCompare is null then we're either comparing against a literal value, a field or a method call.
// We only care about property comparisons in this case.
var comparisonDisplayName =
ValidatorOptions.DisplayNameResolver(Rule.TypeToValidate, propertyToCompare, null)
?? propertyToCompare.Name.SplitPascalCase();
var formatter = new MessageFormatter()
.AppendPropertyName(Rule.GetDisplayName())
.AppendArgument("ComparisonValue", comparisonDisplayName);
string message = formatter.BuildMessage(EqualValidator.ErrorMessageSource.GetString());
yield return new ModelClientValidationEqualToRule(message, CompareAttribute.FormatPropertyForClientValidation(propertyToCompare.Name)) ;
}
else
{
var validator = (EqualValidator)Validator;
var errorMessage = new MessageFormatter()
.AppendPropertyName(Rule.GetDisplayName())
.AppendArgument("ValueToCompare", validator.ValueToCompare)
.BuildMessage(validator.ErrorMessageSource.GetString());
var rule = new ModelClientValidationRule();
rule.ErrorMessage = errorMessage;
rule.ValidationType = "equaltovalue";
rule.ValidationParameters["valuetocompare"] = validator.ValueToCompare;
yield return rule;
}
}
}
This code was copied from the EqualToFluentValidationPropertyValidator internal class in the fluentvalidation source, and I added Darin's logic after the else. This allows the client-side validation to work for property comparisons as well as value comparisons...I'm not sure if this is a great approach since you're basically overriding the built-in equality validator and it may break in future releases of fluent validation....but Darin's answer has the same issue.
There might be better ways to handle this. If somebody knows of a way to directly include the logic from the internal EqualToFluentValidationPropertyValidator class, then I'd love to hear it.
it's based on #cryss answer
RuleFor(x => x.HasToBeChecked)
.Equal(x => true)
.WithMessage("Required");
you don't need to use additional property

Resources