ASP.NET MVC 4 Custom Validation not working - asp.net-mvc
I am trying to use a custom validation but its been ignored. I do not get any errors or anything. It simply does nothing. What am i doing wrong? Thank you for reading.
---ModelMetadata.cs----
using System.ComponentModel.DataAnnotations;
using myproject.Common;
namespace myproject.Models
{
[MetadataType(typeof(ModelMetaData))]
public partial class Reference
{
}
public class ModelMetaData {
[Required]
[Display(Name = "Full Name *")]
[ExcludeCharacters("/.,!##$%")] // <<<====== This is being ignored.
public string Name { get; set; }
}
}
--Create.cshtml ----
<script src="~/Scripts/jquery-1.8.2.min.js"></script>
<script src="~/Scripts/jquery.validate.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
#using (Html.BeginForm()){
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<div class="editor-field">
#Html.EditorFor(model => model.Name) // <<<====== field
#Html.ValidationMessageFor(model => model.Name)
</div>
--ExcludeCharacters.cs ---
namespace myproject.Common
{
public class ExcludeCharacters : ValidationAttribute
{
private readonly string _chars;
public ExcludeCharacters(string chars) : base("{0} contains invalid characters")
{
_chars = chars;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
for (int i = 0; i < _chars.Length; i++)
{
var valueAsString = value.ToString();
if (valueAsString.Contains(_chars[i].ToString()))
{
var errorMessage = FormatErrorMessage(validationContext.DisplayName);
return new ValidationResult(errorMessage);
}
}
}
return ValidationResult.Success;
}
}
}
Please check the action method in your controller - make sure the model type is used by a parameter, e.g.
public ActionResult Foo(ModelMetaData model)
i had exactly same problem, and was using same codes as well. i simply shutdown my application and restarted again. it worked for me. try this
Your code generally looks about right. This would validate, however, at the server side, the way you generally have it and if it was working. Moreover, I'm unsure of why some examples are only overriding the ValidationResult version of the IsValid method, and yet other examples are only overriding the bool variant.
If you want client side validation, you need to implement IClientValidatable , and register the adapter on the client side.
Below is some code I was able to make work. I needed to include my js file after my validation inclusion, and since I was using DevExpress with include client libraries, I needed to run it after them as well.
You'll also (for client side to work), want to use EditorFor or TextBoxFor type methods to add the fields, but this appears to be something you are already doing. Do, however, check your html and look for said validation proprieties being added.
I've tested this with my MM/dd/yyyy fields.
RangeSmallDateTime.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace YourNamespaceHere.Filters
{
public class RangeSmallDateTime : ValidationAttribute, IClientValidatable
{
private static DateTime _minValue = new DateTime(1900, 1, 1);
private static DateTime _maxValue = new DateTime(2079, 6, 6);
public override bool IsValid(object value)
{
DateTime? dateValue = value as DateTime?;
if (!dateValue.HasValue)
{
return true;
}
else
{
return (dateValue.Value >= _minValue && dateValue.Value <= _maxValue);
}
}
public override string FormatErrorMessage(string name)
{
return string.Format("The '{0}' field has an invalid value.", name);
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
ModelClientValidationRule rule = new ModelClientValidationRule();
rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
rule.ValidationType = "rangesdt";
yield return rule;
}
}
}
SmallDateTimeValidator.js:
$.validator.unobtrusive.adapters.addBool("rangesdt");
$.validator.addMethod("rangesdt", function (value, element) {
if (value == null) {
return true;
}
var date = new Date(value); // MM/dd/yyyy
var minDate = new Date(1900,1,1);
var maxDate = new Date(2079, 6, 6);
if (!dates.inRange(date, minDate, maxDate))
return false;
return true;
});
// Source: http://stackoverflow.com/questions/497790
var dates = {
convert: function (d) {
// Converts the date in d to a date-object. The input can be:
// a date object: returned without modification
// an array : Interpreted as [year,month,day]. NOTE: month is 0-11.
// a number : Interpreted as number of milliseconds
// since 1 Jan 1970 (a timestamp)
// a string : Any format supported by the javascript engine, like
// "YYYY/MM/DD", "MM/DD/YYYY", "Jan 31 2009" etc.
// an object : Interpreted as an object with year, month and date
// attributes. **NOTE** month is 0-11.
return (
d.constructor === Date ? d :
d.constructor === Array ? new Date(d[0], d[1], d[2]) :
d.constructor === Number ? new Date(d) :
d.constructor === String ? new Date(d) :
typeof d === "object" ? new Date(d.year, d.month, d.date) :
NaN
);
},
compare: function (a, b) {
// Compare two dates (could be of any type supported by the convert
// function above) and returns:
// -1 : if a < b
// 0 : if a = b
// 1 : if a > b
// NaN : if a or b is an illegal date
// NOTE: The code inside isFinite does an assignment (=).
return (
isFinite(a = this.convert(a).valueOf()) &&
isFinite(b = this.convert(b).valueOf()) ?
(a > b) - (a < b) :
NaN
);
},
inRange: function (d, start, end) {
// Checks if date in d is between dates in start and end.
// Returns a boolean or NaN:
// true : if d is between start and end (inclusive)
// false : if d is before start or after end
// NaN : if one or more of the dates is illegal.
// NOTE: The code inside isFinite does an assignment (=).
return (
isFinite(d = this.convert(d).valueOf()) &&
isFinite(start = this.convert(start).valueOf()) &&
isFinite(end = this.convert(end).valueOf()) ?
start <= d && d <= end :
NaN
);
}
}
Related
Common validation for multiple inputs MVC 3 [duplicate]
I have a view model that has year/month/day properties for someone's date of birth. All of these fields are required. Right now, if someone doesn't enter anything for the date of birth they get 3 separate error messages. What I want to do is somehow group those error messages together into 1 message that just says 'Date of birth is required'. So if 1 or more of those fields are blank, they will always just get the 1 validation message. I NEED this to work on client-side validation via jquery validate and unobtrusive validate. I know this is possible with the jquery validate plugin by looking at this question. But I don't know how to achieve this with asp.net mvc using validation attributes on my model and unobtrusive validation. Hopefully there's some built in way to group properties for validation purposes, but if not can this be done with a custom validation attribute? Here's what my existing model and view looks like: The Model: public class MyModel { [Required(ErrorMessage = "Year is required")] public int Year { get; set; } [Required(ErrorMessage = "Month is required")] public int Month { get; set; } [Required(ErrorMessage = "Day is required")] public int Day { get; set; } } The View: <div> <label>Date of birth: <span style="color:red;">*</span></label> <div>#Html.DropDownListFor(m => m.Year, ApplicationModel.GetSelectListForDateRange(DateTime.Today.Year - 16, DateTime.Today.Year - 10), "", new{data_description="birthDate"})#Html.LabelFor(m => m.StudentBirthYear)</div> <div>#Html.DropDownListFor(m => m.Month, ApplicationModel.GetSelectListForDateRange(1, 12, true), "", new{data_description="birthDate"})#Html.LabelFor(m => m.StudentBirthMonth)</div> <div>#Html.DropDownListFor(m => m.Day, ApplicationModel.GetSelectListForDateRange(1, 31), "", new{data_description="birthDate"})#Html.LabelFor(m => m.StudentBirthDay)</div> </div> <div class="error-container">#Html.ValidationMessageFor(m => m.Year)</div> <div class="error-container">#Html.ValidationMessageFor(m => m.Month)</div> <div class="error-container">#Html.ValidationMessageFor(m => m.Day)</div>
I am somewhat late to the party (only couple of years) still... Most appropriate solution is indeed creating a CustomAttribute but instead of giving you good advice an leaving to die I will show you how. Custom Attribute: public class GroupRequiredAttribute : ValidationAttribute, IClientValidatable { private readonly string[] _serverSideProperties; public GroupRequiredAttribute(params string[] serverSideProperties) { _serverSideProperties = serverSideProperties; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { if (_serverSideProperties == null || _serverSideProperties.Length < 1) { return null; } foreach (var input in _serverSideProperties) { var propertyInfo = validationContext.ObjectType.GetProperty(input); if (propertyInfo == null) { return new ValidationResult(string.Format("unknown property {0}", input)); } var propertyValue = propertyInfo.GetValue(validationContext.ObjectInstance, null); if (propertyValue is string && !string.IsNullOrEmpty(propertyValue as string)) { return null; } if (propertyValue != null) { return null; } } return new ValidationResult(FormatErrorMessage(validationContext.DisplayName)); } public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { var rule = new ModelClientValidationRule { ErrorMessage = ErrorMessage, ValidationType = "grouprequired" }; rule.ValidationParameters["grouprequiredinputs"] = string.Join(",", this._serverSideProperties); yield return rule; } } ViewModel: Decorate only one field on your viewModel like following: [GroupRequired("Year", "Month", "Day", ErrorMessage = "Please enter your date of birth")] public int? Year { get; set; } public int? Month { get; set; } public int? Day { get; set; } Jquery: You will need to add adapters in my case it's jquery.validate.unobtrusive.customadapters.js or wherever you register your adapters (you might put this on the page just do it after unobtrusive validation runs). (function ($) { jQuery.validator.unobtrusive.adapters.add('grouprequired', ['grouprequiredinputs'], function (options) { options.rules['grouprequired'] = options.params; options.messages['grouprequired'] = options.message; }); }(jQuery)); jQuery.validator.addMethod('grouprequired', function (value, element, params) { var inputs = params.grouprequiredinputs.split(','); var values = $.map(inputs, function (input, index) { var val = $('#' + input).val(); return val != '' ? input : null; }); return values.length == inputs.length; }); and that should do it. For those who are interested what this does: In C# land it grabs the ids of fields glues them with , and puts into custom attribute on Year field. HTML should look something like this (If it doesn't debug C# attribute): <input class="tooltip form-control input dob--input-long" data-val="true" data-val-grouprequired="Please enter your date of birth" data-val-grouprequired-grouprequiredinputs="Year,Month,Day" name="Year" placeholder="YYYY" tabindex="" type="text" value=""> Then Jquery validation splits them back into id's and checks if all of them are not empty and that's pretty much it. You will want to mark fields as invalid somehow (now it would only mark the field attribute is sitting on) most appropriate solution IMHO is to wrap all fields in container with class field-error-wrapper and then add following to your page after Jquery validation is loaded: $.validator.setDefaults({ highlight: function (element) { $(element).closest(".field-error-wrapper").addClass("input-validation-error"); }, unhighlight: function (element) { $(element).closest(".field-error-wrapper").removeClass("input-validation-error"); } }); instead of marking field it will mark container and then you can write your css in a way that if container is marked with .input-validation-error then all fields inside turn red. I think my job here is done. EDIT: Ok so there appears to be one more issue where fields get unmarked because validator thinks that day and month are valid and it needs to remove invalid class from parent, validator first marks invalid fields then unmarks valid which causes validation not to get highlighted, so I changed the sequence in which validation happens, I wouldn't recommend overriding this globally (cause I am not sure on what catastrophic side affects it might have) just paste it on the page where you have birthdate fields. $(function () { $.data($('form')[0], 'validator').settings.showErrors = function () { if (this.settings.unhighlight) { for (var i = 0, elements = this.validElements() ; elements[i]; i++) { this.settings.unhighlight.call(this, elements[i], this.settings.errorClass, this.settings.validClass); } } this.hideErrors(); for (var i = 0; this.errorList[i]; i++) { var error = this.errorList[i]; this.settings.highlight && this.settings.highlight.call(this, error.element, this.settings.errorClass, this.settings.validClass); this.showLabel(error.element, error.message); } if (this.errorList.length) { this.toShow = this.toShow.add(this.containers); } if (this.settings.success) { for (var i = 0; this.successList[i]; i++) { this.showLabel(this.successList[i]); } } this.toHide = this.toHide.not(this.toShow); this.addWrapper(this.toShow).show(); }; }); Hope this saves you some time.
You could do that simply using CustomAttribute. Just put this attribute on your model [CustomValidation(typeof(MyModel), "ValidateRelatedObject")] and then simply define the rules to validate the values in the following method: public static ValidationResult ValidateRelatedObject(object value, ValidationContext context) { var context = new ValidationContext(value, validationContext.ServiceContainer, validationContext.Items); var results = new List<ValidationResult>(); Validator.TryValidateObject(value, context, results); // TODO: Wrap or parse multiple ValidationResult's into one ValidationResult return result; } For more information, you could visit this link.
You should implement IValidatableObject and take of the Require. Then the validation on the server side will do the job, something like: public class MyModel : IValidatableObject { public int Year { get; set; } public int Month { get; set; } public int Day { get; set; } public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { if (/*Validate properties here*/) yield return new ValidationResult("Invalid Date!", new[] { "valideDate" }); } } For client side validation you need to implement your own function, and prompt the error to the user somehow. EDIT: Given that you still need client side validation, you should do something like this: $("form").validate({ rules: { Day: { required: true }, Month : { required: true }, Year : { required: true } }, groups: { Date: "Day Month Year" }, errorPlacement: function(error, element) { if (element.attr("id") == "Day" || element.attr("id") == "Month" || element.attr("id") == "Year") error.insertAfter("#Day"); else error.insertAfter(element); } });
Hi I hope this fulfill your requirement //--------------------------HTML Code----------------------------- <form id="myform"> <select name="day"> <option value="">select</option> <option value="1">1</option> <select> <select name="mnth"> <option value="">select</option> <option value="Jan">Jan</option> <select> <select name="yr"> <option value="">select</option> <option value="2015">2015</option> <select> <br/> <input type="submit" /> <br/> <div id="msg"></div> </form> //-------------------------------JS Code to validate------------- $(document).ready(function() { $('#myform').validate({ rules: { day: { required: true }, mnth: { required: true }, yr: { required: true } }, errorPlacement: function (error, element) { var name = $(element).attr("name"); error.appendTo($("#msg")); }, messages: { day: { required: "Date of birth is required" }, mnth: { required: "Date of birth is required" }, yr: { required: "Date of birth is required" } }, groups: { p: "day mnth yr" }, submitHandler: function(form) { // for demo alert('valid form'); return false; } }); }); Here is Running Example
MVC Custom Validation for List on Client Side
I'm trying to write a custom validator that works on the client side that validates that all checkboxes have been ticked. Here's the declaration on the model: [DeclarationsAccepted(ErrorMessage = "You must tick all declarations")] public IList<DeclarationQuestion> DeclarationQuestions { get; set; } And here's the attribute: public class DeclarationsAccepted : ValidationAttribute, IClientValidatable { protected override ValidationResult IsValid(object value, ValidationContext validationContext) { var questions = value as IList<DeclarationQuestion>; if (questions != null && questions.All(c => c.Answer)) { return ValidationResult.Success; } return new ValidationResult("You must accepted all declarations to continue"); } public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { var modelClientValidationRule = new ModelClientValidationRule { ValidationType = "declarationsaccepted", ErrorMessage = FormatErrorMessage(metadata.DisplayName) }; yield return modelClientValidationRule; } } So far so good, works server side. For the client I'm wiring this up as follows: jQuery.validator.addMethod('declarationsaccepted', function (value, element, params) { //Implement logic here to check all boxes are ticked console.log(value); return false; }, ''); jQuery.validator.unobtrusive.adapters.add('declarationsaccepted', {}, function (options) { options.rules['declarationsaccepted'] = true; options.messages['declarationsaccepted'] = options.message; }); I'm displaying the checkboxes like this: #{ var questionIndex = 0; } #foreach (var question in Model.DeclarationQuestions) { #Html.CheckBoxFor(model => Model.DeclarationQuestions[questionIndex].Answer, new { id = "DeclarationQuestions" + questionIndex}) questionIndex++; } And then displaying the validation message using this: #Html.ValidationMessageFor(c => c.DeclarationQuestions) When I submit the form the message is displayed but only after a post back to the server. Is there any way to get this to work on the client side?
The reason you will not get client side validation is because the html helpers generate data-val-* attributes for controls associated with properties. jquery.validate.unobtrusive reads those attributes when the form is parsed and using rules, displays an error message in the appropriate element generated by ValidationMessageFor() associated with that control (it does this by matching up the id attributes of the elements - the error message is generated in a span with <span for="TheIdOfTheAssociatedControl" ...>). You don't (and cant) generate a control for property DeclarationQuestions (only for properties of each item in DeclarationQuestions so there is nothing that can be matched up. You could handle this by including your own error message placeholder and intercepting the .submit event html (add css to style #conditions-error as display:none;) <span id="delarations-error" class="field-validation-error"> <span>You must accept all declarations to continue.</span> </span> Script var declarationsError = $('#delarations-error'); $('form').submit(function() { var isValid = $('.yourCheckBoxClass').not(':checked') == 0; if(!isValid) { declarationsError.show(); // display error message return false; // prevent submit } }); $('.yourCheckBoxClass').change(function() { if($(this).is(':checked')) { declarationsError.hide(); // hide error message } }); Side note: You loop for generating the checkboxes should be for (int i = 0; i < Model.DeclarationQuestions.Count; i++) { #Html.CheckBoxFor(m => m.DeclarationQuestions[i].Answer, new { id = "DeclarationQuestions" + i}) }
JSON collection of objects isn't binding correctly
I have a website where I am uploading an Excel document to the server through AJAX (Telerik control). When the document is uploaded it will return a JSON object collection of currency exchange rates. I then save that collection using the jQuery $.data() method. Then I call the grid to bind and pass my JSON object to my controller to bind the data to the grid. Everything seems to be working up to the point where the Action on the controller binds to the collection. What am I missing, notice how the response from SelectImportedCurrencyRates returns '1/1/1' for dates, country code is 'null' and the exchange rate is '0'. When I Debug and check the bound object ICollection<CurrencyExchangeRate> currencyRatesImported, its bound with those values as '0', '1/1/1' and null Controller [HttpPost] public ActionResult UploadRates(HttpPostedFileBase importRateDocument) { var rates = CurrencyExchangeRateRepository.ReadExcelRates(importRateDocument.InputStream, importRateDocument.FileName); return Json(rates); } [HttpPost] [GridAction] public ActionResult SelectImportedCurrencyRates([Bind(Prefix = "CurrencyRatesImported")] ICollection<CurrencyExchangeRate> currencyRatesImported) { return View(currencyRatesImported != null ? new GridModel(currencyRatesImported) : new GridModel(new List<CurrencyExchangeRate>())); } Javascript <script type="text/javascript"> function onUploadRatesSuccess(e) { $('body').data('CurrencyRatesImported', e.response); var grid = $('#CurrencyExchangeRates').data('tGrid'); grid.ajaxRequest(); } function onCurrencyRatesImportedDataBinding(args) { var currencyRatesImported = $('body').data('CurrencyRatesImported'); console.log(currencyRatesImported); args.data = $.extend(args.data, { CurrencyRatesImported: currencyRatesImported }); args.data = args.data; } e.Response passed to onUploadRatesSuccess (result from ActionResult UploadRates() [{"CountryCode":"CAD","Date":"\/Date(1325656800000)\/","ExchangeRate":1.0145},{"CountryCode":"CAD","Date":"\/Date(1325743200000)\/","ExchangeRate":1.0212},{"CountryCode":"CAD","Date":"\/Date(1325829600000)\/","ExchangeRate":1.0241},{"CountryCode":"CAD","Date":"\/Date(1326088800000)\/","ExchangeRate":1.0265},{"CountryCode":"CAD","Date":"\/Date(1326175200000)\/","ExchangeRate":1.0187},{"CountryCode":"CAD","Date":"\/Date(1326261600000)\/","ExchangeRate":1.0209},{"CountryCode":"CAD","Date":"\/Date(1326348000000)\/","ExchangeRate":1.0207},{"CountryCode":"CAD","Date":"\/Date(1326434400000)\/","ExchangeRate":1.0245},{"CountryCode":"CAD","Date":"\/Date(1326693600000)\/","ExchangeRate":1.0182},{"CountryCode":"CAD","Date":"\/Date(1326780000000)\/","ExchangeRate":1.0143},{"CountryCode":"CAD","Date":"\/Date(1326866400000)\/","ExchangeRate":1.0139},{"CountryCode":"CAD","Date":"\/Date(1326952800000)\/","ExchangeRate":1.0091},{"CountryCode":"CAD","Date":"\/Date(1327039200000)\/","ExchangeRate":1.014},{"CountryCode":"CAD","Date":"\/Date(1327298400000)\/","ExchangeRate":1.0066},{"CountryCode":"CAD","Date":"\/Date(1327384800000)\/","ExchangeRate":1.0108},{"CountryCode":"CAD","Date":"\/Date(1327471200000)\/","ExchangeRate":1.0121},{"CountryCode":"CAD","Date":"\/Date(1327557600000)\/","ExchangeRate":0.9997},{"CountryCode":"CAD","Date":"\/Date(1327644000000)\/","ExchangeRate":1.0015},{"CountryCode":"CAD","Date":"\/Date(1327903200000)\/","ExchangeRate":1.0046},{"CountryCode":"CAD","Date":"\/Date(1328162400000)\/","ExchangeRate":0.9978},{"CountryCode":"CAD","Date":"\/Date(1328248800000)\/","ExchangeRate":0.9986},{"CountryCode":"CAD","Date":"\/Date(1328508000000)\/","ExchangeRate":0.9957},{"CountryCode":"CAD","Date":"\/Date(1328594400000)\/","ExchangeRate":0.9949},{"CountryCode":"CAD","Date":"\/Date(1328680800000)\/","ExchangeRate":0.9953},{"CountryCode":"CAD","Date":"\/Date(1328767200000)\/","ExchangeRate":0.994},{"CountryCode":"CAD","Date":"\/Date(1328853600000)\/","ExchangeRate":0.9956},{"CountryCode":"CAD","Date":"\/Date(1329112800000)\/","ExchangeRate":0.9998},{"CountryCode":"CAD","Date":"\/Date(1329199200000)\/","ExchangeRate":1.0007},{"CountryCode":"CAD","Date":"\/Date(1329285600000)\/","ExchangeRate":0.9976},{"CountryCode":"CAD","Date":"\/Date(1329372000000)\/","ExchangeRate":1.0003},{"CountryCode":"CAD","Date":"\/Date(1329458400000)\/","ExchangeRate":0.9969},{"CountryCode":"CAD","Date":"\/Date(1329717600000)\/","ExchangeRate":0.9924},{"CountryCode":"CAD","Date":"\/Date(1329804000000)\/","ExchangeRate":0.9957},{"CountryCode":"CAD","Date":"\/Date(1329890400000)\/","ExchangeRate":0.9996},{"CountryCode":"CAD","Date":"\/Date(1329976800000)\/","ExchangeRate":0.9973},{"CountryCode":"CAD","Date":"\/Date(1330063200000)\/","ExchangeRate":0.9987},{"CountryCode":"CAD","Date":"\/Date(1330322400000)\/","ExchangeRate":1.0002},{"CountryCode":"CAD","Date":"\/Date(1330408800000)\/","ExchangeRate":0.9958},{"CountryCode":"CAD","Date":"\/Date(1330581600000)\/","ExchangeRate":0.9958},{"CountryCode":"CAD","Date":"\/Date(1330668000000)\/","ExchangeRate":0.9878},{"CountryCode":"CAD","Date":"\/Date(1330927200000)\/","ExchangeRate":0.9937},{"CountryCode":"CAD","Date":"\/Date(1331013600000)\/","ExchangeRate":1.0017},{"CountryCode":"CAD","Date":"\/Date(1331100000000)\/","ExchangeRate":1.0023},{"CountryCode":"CAD","Date":"\/Date(1331186400000)\/","ExchangeRate":0.9934},{"CountryCode":"CAD","Date":"\/Date(1331272800000)\/","ExchangeRate":0.9889},{"CountryCode":"CAD","Date":"\/Date(1331528400000)\/","ExchangeRate":0.9935},{"CountryCode":"CAD","Date":"\/Date(1331614800000)\/","ExchangeRate":0.9906},{"CountryCode":"CAD","Date":"\/Date(1331701200000)\/","ExchangeRate":0.9915},{"CountryCode":"CAD","Date":"\/Date(1331787600000)\/","ExchangeRate":0.9929},{"CountryCode":"CAD","Date":"\/Date(1331874000000)\/","ExchangeRate":0.9913},{"CountryCode":"CAD","Date":"\/Date(1332133200000)\/","ExchangeRate":0.988},{"CountryCode":"CAD","Date":"\/Date(1332219600000)\/","ExchangeRate":0.9934},{"CountryCode":"CAD","Date":"\/Date(1332306000000)\/","ExchangeRate":0.9931},{"CountryCode":"CAD","Date":"\/Date(1332392400000)\/","ExchangeRate":1.0003},{"CountryCode":"CAD","Date":"\/Date(1332478800000)\/","ExchangeRate":0.9984},{"CountryCode":"CAD","Date":"\/Date(1332738000000)\/","ExchangeRate":0.9915},{"CountryCode":"CAD","Date":"\/Date(1332824400000)\/","ExchangeRate":0.9925},{"CountryCode":"CAD","Date":"\/Date(1332910800000)\/","ExchangeRate":0.9965},{"CountryCode":"CAD","Date":"\/Date(1332997200000)\/","ExchangeRate":0.9992},{"CountryCode":"CAD","Date":"\/Date(1333429200000)\/","ExchangeRate":0.9896},{"CountryCode":"CAD","Date":"\/Date(1333515600000)\/","ExchangeRate":0.9971},{"CountryCode":"CAD","Date":"\/Date(1333602000000)\/","ExchangeRate":0.99385},{"CountryCode":"CAD","Date":"\/Date(1333688400000)\/","ExchangeRate":0.99565},{"CountryCode":"CAD","Date":"\/Date(1333947600000)\/","ExchangeRate":0.99761},{"CountryCode":"CAD","Date":"\/Date(1334034000000)\/","ExchangeRate":1.0027},{"CountryCode":"CAD","Date":"\/Date(1334120400000)\/","ExchangeRate":1.002},{"CountryCode":"CAD","Date":"\/Date(1334206800000)\/","ExchangeRate":0.9966},{"CountryCode":"CAD","Date":"\/Date(1334293200000)\/","ExchangeRate":0.9975},{"CountryCode":"CAD","Date":"\/Date(1334552400000)\/","ExchangeRate":1.0016},{"CountryCode":"CAD","Date":"\/Date(1334638800000)\/","ExchangeRate":0.9876},{"CountryCode":"CAD","Date":"\/Date(1334725200000)\/","ExchangeRate":0.9958},{"CountryCode":"CAD","Date":"\/Date(1334811600000)\/","ExchangeRate":0.99},{"CountryCode":"CAD","Date":"\/Date(1334898000000)\/","ExchangeRate":0.9909},{"CountryCode":"CAD","Date":"\/Date(1335157200000)\/","ExchangeRate":0.995},{"CountryCode":"CAD","Date":"\/Date(1335243600000)\/","ExchangeRate":0.9885},{"CountryCode":"CAD","Date":"\/Date(1335330000000)\/","ExchangeRate":0.9853},{"CountryCode":"CAD","Date":"\/Date(1335416400000)\/","ExchangeRate":0.9841},{"CountryCode":"CAD","Date":"\/Date(1335502800000)\/","ExchangeRate":0.981},{"CountryCode":"CAD","Date":"\/Date(1335848400000)\/","ExchangeRate":0.981},{"CountryCode":"CAD","Date":"\/Date(1335934800000)\/","ExchangeRate":0.9891},{"CountryCode":"CAD","Date":"\/Date(1336021200000)\/","ExchangeRate":0.9867},{"CountryCode":"CAD","Date":"\/Date(1336107600000)\/","ExchangeRate":0.9863},{"CountryCode":"CAD","Date":"\/Date(1336366800000)\/","ExchangeRate":0.99636},{"CountryCode":"CAD","Date":"\/Date(1336453200000)\/","ExchangeRate":0.996},{"CountryCode":"CAD","Date":"\/Date(1336539600000)\/","ExchangeRate":1.0044},{"CountryCode":"CAD","Date":"\/Date(1336626000000)\/","ExchangeRate":1.0022},{"CountryCode":"CAD","Date":"\/Date(1336712400000)\/","ExchangeRate":0.9968},{"CountryCode":"CAD","Date":"\/Date(1336971600000)\/","ExchangeRate":1.0024},{"CountryCode":"CAD","Date":"\/Date(1337058000000)\/","ExchangeRate":1.0038},{"CountryCode":"CAD","Date":"\/Date(1337144400000)\/","ExchangeRate":1.0088},{"CountryCode":"CAD","Date":"\/Date(1337230800000)\/","ExchangeRate":1.0163},{"CountryCode":"CAD","Date":"\/Date(1337317200000)\/","ExchangeRate":1.0179},{"CountryCode":"CAD","Date":"\/Date(1337576400000)\/","ExchangeRate":1.0218},{"CountryCode":"CAD","Date":"\/Date(1337662800000)\/","ExchangeRate":1.0172},{"CountryCode":"CAD","Date":"\/Date(1337749200000)\/","ExchangeRate":1.0283},{"CountryCode":"CAD","Date":"\/Date(1337835600000)\/","ExchangeRate":1.0262},{"CountryCode":"CAD","Date":"\/Date(1337922000000)\/","ExchangeRate":1.0289},{"CountryCode":"CAD","Date":"\/Date(1338181200000)\/","ExchangeRate":1.0253},{"CountryCode":"CAD","Date":"\/Date(1338267600000)\/","ExchangeRate":1.0222},{"CountryCode":"CAD","Date":"\/Date(1338354000000)\/","ExchangeRate":1.0297},{"CountryCode":"CAD","Date":"\/Date(1338526800000)\/","ExchangeRate":1.0297},{"CountryCode":"CAD","Date":"\/Date(1338786000000)\/","ExchangeRate":1.04141},{"CountryCode":"CAD","Date":"\/Date(1338872400000)\/","ExchangeRate":1.04154},{"CountryCode":"CAD","Date":"\/Date(1338958800000)\/","ExchangeRate":1.0333},{"CountryCode":"CAD","Date":"\/Date(1339045200000)\/","ExchangeRate":1.0242},{"CountryCode":"CAD","Date":"\/Date(1339131600000)\/","ExchangeRate":1.0314},{"CountryCode":"CAD","Date":"\/Date(1339390800000)\/","ExchangeRate":1.0289},{"CountryCode":"CAD","Date":"\/Date(1339477200000)\/","ExchangeRate":1.0293},{"CountryCode":"CAD","Date":"\/Date(1339563600000)\/","ExchangeRate":1.025},{"CountryCode":"CAD","Date":"\/Date(1339650000000)\/","ExchangeRate":1.0252},{"CountryCode":"CAD","Date":"\/Date(1339736400000)\/","ExchangeRate":1.0236},{"CountryCode":"CAD","Date":"\/Date(1339995600000)\/","ExchangeRate":1.0254},{"CountryCode":"CAD","Date":"\/Date(1340082000000)\/","ExchangeRate":1.018},{"CountryCode":"CAD","Date":"\/Date(1340168400000)\/","ExchangeRate":1.0202},{"CountryCode":"CAD","Date":"\/Date(1340254800000)\/","ExchangeRate":1.0231},{"CountryCode":"CAD","Date":"\/Date(1340341200000)\/","ExchangeRate":1.0272},{"CountryCode":"CAD","Date":"\/Date(1340600400000)\/","ExchangeRate":1.0304},{"CountryCode":"CAD","Date":"\/Date(1340773200000)\/","ExchangeRate":1.0283},{"CountryCode":"CAD","Date":"\/Date(1340859600000)\/","ExchangeRate":1.0339},{"CountryCode":"CAD","Date":"\/Date(1341205200000)\/","ExchangeRate":1.0261},{"CountryCode":"CAD","Date":"\/Date(1341291600000)\/","ExchangeRate":1.0132},{"CountryCode":"CAD","Date":"\/Date(1341378000000)\/","ExchangeRate":1.0126},{"CountryCode":"CAD","Date":"\/Date(1341464400000)\/","ExchangeRate":1.0139},{"CountryCode":"CAD","Date":"\/Date(1341550800000)\/","ExchangeRate":1.0188},{"CountryCode":"CAD","Date":"\/Date(1341810000000)\/","ExchangeRate":1.0199},{"CountryCode":"CAD","Date":"\/Date(1341896400000)\/","ExchangeRate":1.0209},{"CountryCode":"CAD","Date":"\/Date(1341982800000)\/","ExchangeRate":1.0206},{"CountryCode":"CAD","Date":"\/Date(1342069200000)\/","ExchangeRate":1.0248},{"CountryCode":"CAD","Date":"\/Date(1342155600000)\/","ExchangeRate":1.014},{"CountryCode":"CAD","Date":"\/Date(1342414800000)\/","ExchangeRate":1.0159},{"CountryCode":"CAD","Date":"\/Date(1342501200000)\/","ExchangeRate":1.0166},{"CountryCode":"CAD","Date":"\/Date(1342587600000)\/","ExchangeRate":1.0109},{"CountryCode":"CAD","Date":"\/Date(1342674000000)\/","ExchangeRate":1.0073},{"CountryCode":"CAD","Date":"\/Date(1342760400000)\/","ExchangeRate":1.0102},{"CountryCode":"CAD","Date":"\/Date(1343019600000)\/","ExchangeRate":1.0176},{"CountryCode":"CAD","Date":"\/Date(1343106000000)\/","ExchangeRate":1.0198},{"CountryCode":"CAD","Date":"\/Date(1343192400000)\/","ExchangeRate":1.0195},{"CountryCode":"CAD","Date":"\/Date(1343278800000)\/","ExchangeRate":1.0097},{"CountryCode":"CAD","Date":"\/Date(1343365200000)\/","ExchangeRate":1.0058},{"CountryCode":"CAD","Date":"\/Date(1343624400000)\/","ExchangeRate":1.0027},{"CountryCode":"CAD","Date":"\/Date(1343797200000)\/","ExchangeRate":1.0027},{"CountryCode":"CAD","Date":"\/Date(1343883600000)\/","ExchangeRate":1.0041},{"CountryCode":"CAD","Date":"\/Date(1343970000000)\/","ExchangeRate":0.9997},{"CountryCode":"CAD","Date":"\/Date(1344229200000)\/","ExchangeRate":0.9995},{"CountryCode":"CAD","Date":"\/Date(1344315600000)\/","ExchangeRate":0.9967},{"CountryCode":"CAD","Date":"\/Date(1344402000000)\/","ExchangeRate":0.995},{"CountryCode":"CAD","Date":"\/Date(1344488400000)\/","ExchangeRate":0.9917},{"CountryCode":"CAD","Date":"\/Date(1344574800000)\/","ExchangeRate":0.991},{"CountryCode":"CAD","Date":"\/Date(1344834000000)\/","ExchangeRate":0.9932},{"CountryCode":"CAD","Date":"\/Date(1344920400000)\/","ExchangeRate":0.9919},{"CountryCode":"CAD","Date":"\/Date(1345006800000)\/","ExchangeRate":0.9897},{"CountryCode":"CAD","Date":"\/Date(1345093200000)\/","ExchangeRate":0.9883},{"CountryCode":"CAD","Date":"\/Date(1345179600000)\/","ExchangeRate":0.9882},{"CountryCode":"CAD","Date":"\/Date(1345438800000)\/","ExchangeRate":0.9894},{"CountryCode":"CAD","Date":"\/Date(1345525200000)\/","ExchangeRate":0.9855},{"CountryCode":"CAD","Date":"\/Date(1345611600000)\/","ExchangeRate":0.993},{"CountryCode":"CAD","Date":"\/Date(1345698000000)\/","ExchangeRate":0.9916},{"CountryCode":"CAD","Date":"\/Date(1345784400000)\/","ExchangeRate":0.99121},{"CountryCode":"CAD","Date":"\/Date(1346043600000)\/","ExchangeRate":0.99234},{"CountryCode":"CAD","Date":"\/Date(1346130000000)\/","ExchangeRate":0.9855},{"CountryCode":"CAD","Date":"\/Date(1346216400000)\/","ExchangeRate":0.9885},{"CountryCode":"CAD","Date":"\/Date(1346302800000)\/","ExchangeRate":0.9923},{"CountryCode":"CAD","Date":"\/Date(1346648400000)\/","ExchangeRate":0.9923},{"CountryCode":"CAD","Date":"\/Date(1346734800000)\/","ExchangeRate":0.9865},{"CountryCode":"CAD","Date":"\/Date(1346821200000)\/","ExchangeRate":0.9917},{"CountryCode":"CAD","Date":"\/Date(1346907600000)\/","ExchangeRate":0.9819},{"CountryCode":"CAD","Date":"\/Date(1346994000000)\/","ExchangeRate":0.9771},{"CountryCode":"CAD","Date":"\/Date(1347253200000)\/","ExchangeRate":0.9757},{"CountryCode":"CAD","Date":"\/Date(1347339600000)\/","ExchangeRate":0.9727},{"CountryCode":"CAD","Date":"\/Date(1347426000000)\/","ExchangeRate":0.9751},{"CountryCode":"CAD","Date":"\/Date(1347512400000)\/","ExchangeRate":0.9751},{"CountryCode":"CAD","Date":"\/Date(1347598800000)\/","ExchangeRate":0.9687},{"CountryCode":"CAD","Date":"\/Date(1347858000000)\/","ExchangeRate":0.9719},{"CountryCode":"CAD","Date":"\/Date(1347944400000)\/","ExchangeRate":0.9743},{"CountryCode":"CAD","Date":"\/Date(1348030800000)\/","ExchangeRate":0.9761},{"CountryCode":"CAD","Date":"\/Date(1348117200000)\/","ExchangeRate":0.9777},{"CountryCode":"CAD","Date":"\/Date(1348203600000)\/","ExchangeRate":0.9763},{"CountryCode":"CAD","Date":"\/Date(1348462800000)\/","ExchangeRate":0.9795},{"CountryCode":"CAD","Date":"\/Date(1348549200000)\/","ExchangeRate":0.9769},{"CountryCode":"CAD","Date":"\/Date(1348635600000)\/","ExchangeRate":0.9855},{"CountryCode":"CAD","Date":"\/Date(1348722000000)\/","ExchangeRate":0.984},{"CountryCode":"CAD","Date":"\/Date(1349067600000)\/","ExchangeRate":0},{"CountryCode":"CAD","Date":"\/Date(1349154000000)\/","ExchangeRate":0.9832},{"CountryCode":"CAD","Date":"\/Date(1349240400000)\/","ExchangeRate":0.988},{"CountryCode":"CAD","Date":"\/Date(1349326800000)\/","ExchangeRate":0.9816},{"CountryCode":"CAD","Date":"\/Date(1349413200000)\/","ExchangeRate":0.9756},{"CountryCode":"CAD","Date":"\/Date(1349672400000)\/","ExchangeRate":0.9761},{"CountryCode":"CAD","Date":"\/Date(1349758800000)\/","ExchangeRate":0.9783},{"CountryCode":"CAD","Date":"\/Date(1349845200000)\/","ExchangeRate":0.9787},{"CountryCode":"CAD","Date":"\/Date(1349931600000)\/","ExchangeRate":0.977},{"CountryCode":"CAD","Date":"\/Date(1350018000000)\/","ExchangeRate":0.9801},{"CountryCode":"CAD","Date":"\/Date(1350277200000)\/","ExchangeRate":0.9805},{"CountryCode":"CAD","Date":"\/Date(1350363600000)\/","ExchangeRate":0.9861},{"CountryCode":"CAD","Date":"\/Date(1350450000000)\/","ExchangeRate":0.9818},{"CountryCode":"CAD","Date":"\/Date(1350536400000)\/","ExchangeRate":0.9823},{"CountryCode":"CAD","Date":"\/Date(1350622800000)\/","ExchangeRate":0.991},{"CountryCode":"CAD","Date":"\/Date(1350882000000)\/","ExchangeRate":0.9933},{"CountryCode":"CAD","Date":"\/Date(1350968400000)\/","ExchangeRate":0.9926},{"CountryCode":"CAD","Date":"\/Date(1351054800000)\/","ExchangeRate":0.9916},{"CountryCode":"CAD","Date":"\/Date(1351141200000)\/","ExchangeRate":0.9938},{"CountryCode":"CAD","Date":"\/Date(1351227600000)\/","ExchangeRate":0.9976},{"CountryCode":"CAD","Date":"\/Date(1351486800000)\/","ExchangeRate":1.0004},{"CountryCode":"CAD","Date":"\/Date(1351573200000)\/","ExchangeRate":0.9993},{"CountryCode":"CAD","Date":"\/Date(1351746000000)\/","ExchangeRate":0.9993},{"CountryCode":"CAD","Date":"\/Date(1351832400000)\/","ExchangeRate":0.99925},{"CountryCode":"CAD","Date":"\/Date(1352095200000)\/","ExchangeRate":0.9966},{"CountryCode":"CAD","Date":"\/Date(1352181600000)\/","ExchangeRate":0.9948},{"CountryCode":"CAD","Date":"\/Date(1352268000000)\/","ExchangeRate":0.9959},{"CountryCode":"CAD","Date":"\/Date(1352354400000)\/","ExchangeRate":0.9979},{"CountryCode":"CAD","Date":"\/Date(1352440800000)\/","ExchangeRate":1.0017},{"CountryCode":"CAD","Date":"\/Date(1352700000000)\/","ExchangeRate":1.0004},{"CountryCode":"CAD","Date":"\/Date(1352786400000)\/","ExchangeRate":1.0013},{"CountryCode":"CAD","Date":"\/Date(1352872800000)\/","ExchangeRate":1.0032},{"CountryCode":"CAD","Date":"\/Date(1352959200000)\/","ExchangeRate":1.0027},{"CountryCode":"CAD","Date":"\/Date(1353045600000)\/","ExchangeRate":1.0034},{"CountryCode":"CAD","Date":"\/Date(1353304800000)\/","ExchangeRate":0.9956},{"CountryCode":"CAD","Date":"\/Date(1353391200000)\/","ExchangeRate":0.9979},{"CountryCode":"CAD","Date":"\/Date(1353477600000)\/","ExchangeRate":0.9977},{"CountryCode":"CAD","Date":"\/Date(1353564000000)\/","ExchangeRate":0.9981},{"CountryCode":"CAD","Date":"\/Date(1353650400000)\/","ExchangeRate":0.9928},{"CountryCode":"CAD","Date":"\/Date(1353909600000)\/","ExchangeRate":0.9951},{"CountryCode":"CAD","Date":"\/Date(1353996000000)\/","ExchangeRate":0.9943},{"CountryCode":"CAD","Date":"\/Date(1354082400000)\/","ExchangeRate":0.9936},{"CountryCode":"CAD","Date":"\/Date(1354168800000)\/","ExchangeRate":0.9917},{"CountryCode":"CAD","Date":"\/Date(1354514400000)\/","ExchangeRate":0.9917},{"CountryCode":"CAD","Date":"\/Date(1354600800000)\/","ExchangeRate":0.9927},{"CountryCode":"CAD","Date":"\/Date(1354687200000)\/","ExchangeRate":0.9929},{"CountryCode":"CAD","Date":"\/Date(1354773600000)\/","ExchangeRate":0.9895},{"CountryCode":"CAD","Date":"\/Date(1354860000000)\/","ExchangeRate":0.9887},{"CountryCode":"CAD","Date":"\/Date(1355119200000)\/","ExchangeRate":0.9872},{"CountryCode":"CAD","Date":"\/Date(1355205600000)\/","ExchangeRate":0.9868},{"CountryCode":"CAD","Date":"\/Date(1355292000000)\/","ExchangeRate":0.9852},{"CountryCode":"CAD","Date":"\/Date(1355378400000)\/","ExchangeRate":0.9841},{"CountryCode":"CAD","Date":"\/Date(1355464800000)\/","ExchangeRate":0.9866},{"CountryCode":"CAD","Date":"\/Date(1355724000000)\/","ExchangeRate":0.9844},{"CountryCode":"CAD","Date":"\/Date(1355810400000)\/","ExchangeRate":0.9846},{"CountryCode":"CAD","Date":"\/Date(1355896800000)\/","ExchangeRate":0.9869},{"CountryCode":"CAD","Date":"\/Date(1355983200000)\/","ExchangeRate":0.9887},{"CountryCode":"CAD","Date":"\/Date(1356069600000)\/","ExchangeRate":0.9947},{"CountryCode":"CAD","Date":"\/Date(1356328800000)\/","ExchangeRate":0.99358},{"CountryCode":"CAD","Date":"\/Date(1356415200000)\/","ExchangeRate":0.99304},{"CountryCode":"CAD","Date":"\/Date(1356501600000)\/","ExchangeRate":0.99125},{"CountryCode":"CAD","Date":"\/Date(1356588000000)\/","ExchangeRate":0.9936},{"CountryCode":"CAD","Date":"\/Date(1356674400000)\/","ExchangeRate":0.9956},{"CountryCode":"CAD","Date":"\/Date(1357106400000)\/","ExchangeRate":0.9837},{"CountryCode":"CAD","Date":"\/Date(1357192800000)\/","ExchangeRate":0.9851},{"CountryCode":"CAD","Date":"\/Date(1357279200000)\/","ExchangeRate":0.9857},{"CountryCode":"CAD","Date":"\/Date(1357538400000)\/","ExchangeRate":0.9859},{"CountryCode":"CAD","Date":"\/Date(1357624800000)\/","ExchangeRate":0.9871},{"CountryCode":"CAD","Date":"\/Date(1357711200000)\/","ExchangeRate":0.9874},{"CountryCode":"CAD","Date":"\/Date(1357797600000)\/","ExchangeRate":0.9866},{"CountryCode":"CAD","Date":"\/Date(1357884000000)\/","ExchangeRate":0.9837},{"CountryCode":"CAD","Date":"\/Date(1358143200000)\/","ExchangeRate":0.9857},{"CountryCode":"CAD","Date":"\/Date(1358229600000)\/","ExchangeRate":0.9837},{"CountryCode":"CAD","Date":"\/Date(1358316000000)\/","ExchangeRate":0.9858},{"CountryCode":"CAD","Date":"\/Date(1358402400000)\/","ExchangeRate":0.9854},{"CountryCode":"CAD","Date":"\/Date(1358488800000)\/","ExchangeRate":0.9938},{"CountryCode":"CAD","Date":"\/Date(1358748000000)\/","ExchangeRate":0.9939},{"CountryCode":"CAD","Date":"\/Date(1358834400000)\/","ExchangeRate":0.9935},{"CountryCode":"CAD","Date":"\/Date(1358920800000)\/","ExchangeRate":0.9986},{"CountryCode":"CAD","Date":"\/Date(1359007200000)\/","ExchangeRate":1.0032},{"CountryCode":"CAD","Date":"\/Date(1359093600000)\/","ExchangeRate":1.0087},{"CountryCode":"CAD","Date":"\/Date(1359352800000)\/","ExchangeRate":1.0092},{"CountryCode":"CAD","Date":"\/Date(1359439200000)\/","ExchangeRate":1.0042},{"CountryCode":"CAD","Date":"\/Date(1359525600000)\/","ExchangeRate":1.0029}] Binding call to SelectImportedCurrencyRates Response from SelectImportedCurrencyRates CurrencyExchangeRate class [Serializable()] public class CurrencyExchangeRate { public string CountryCode { get; set; } public System.DateTime Date { get; set; } public double ExchangeRate { get; set; } }
The problem was in the serialization of the collection I was sending up. It was passing an array up, instead of a serialized JSON object. Here is what the serialization method should look like. function serialize(prefix, data) { var result = {}, dateRegex = /^\/Date\((.*?)\)\/$/; for (var i = 0; i < data.length; i++) { for (var field in data[i]) { var value = data[i][field]; if (typeof value === "string") { var date = dateRegex.exec(value); if (date) { value = $.telerik.formatString("{0:d}", new Date(parseInt(date[1]))); } } result[prefix + "[" + i + "]." + field] = value; } } return result; } So then when I call onCurrencyRatesImportedDataBinding it will serialize my object of currency exchange rates and pass them up properly. function onCurrencyRatesImportedDataBinding(args) { var currencyRatesImported = $('body').data('CurrencyRatesImported'); console.log(currencyRatesImported); args.data = $.extend(args.data, serialize("currencyExchangeRates", currencyRatesImported)); args.data = args.data; }
I believe the issue is the format of the dates being sent across the wire. Unfortunately the DefaultModelBinder in MVC doesn't know how to bind these dates. You'll need to extend the DefaultModelBinder. See e.g. http://taintedsanctity.tumblr.com/post/6425063095/asp-mvc-model-binding-json-dates-serialized-in https://stackoverflow.com/a/7849322/206297
Expression Equality or Equivalence from Multiple Projects
It is understood that: Expression<Func<string, bool>> first = x => x.Length == 4; Expression<Func<string, bool>> second = x => x.Length == 4; Console.WriteLine(first.Equals(second)); // Output is "False" However, examining the strings of each expression does show equality: Expression<Func<string, bool>> first = x => x.Length == 4; Expression<Func<string, bool>> second = x => x.Length == 4; Console.WriteLine(first.ToString().Equals(second.ToString())); // Output is "True" This idea was a culmination of different posts... http://www.codethinked.com/Comparing-Simple-Lambda-Expressions-With-Moq Moq'ing methods where Expression<Func<T, bool>> are passed in as parameters Verify method was called with certain linq expression (moq) The intent: I am writing an MVC application using the repository pattern such that public class MyController : Controller { public Repository.IRepository Repository { get; set; } public MyController() { this.Repository = new Repository.CommonRepository(); } public MyController(Repository.IRepository repository) { this.Repository = repository; } [HttpPost] public ActionResult Create(Domain.Common.Object1 o1) { if (ModelState.IsValid) { // Additional validation o1.Name = o1.Name.Trim(); if (this.Repository.Any<Domain.Common.Object1>(a => a.Name.ToLower() == plant.Name.ToLower())) this.ModelState.AddModelError("Name", "Duplicate found."); } if (ModelState.IsValid) { var entity = this.Repository.Add(o1); if (Request.IsAjaxRequest()) return this.Json(new { Completed = true, Id = entity.Id }); return RedirectToAction("Details", new { id = entity.Id }); } if (Request.IsAjaxRequest()) return PartialView("_Create", o1); return View("Create", o1); } } Repository is a completely separate project as is the domain. My repository code is setup so that I can use the one repository to query any object based upon the generic: public IQueryable<T> GetAll<T>() where T : AbstractEntity { return this.DbContext.Set<T>(); } Note: AbstractEntity is a domain abstract class all of my POCO objects inherit from. Everything is fine when using Moq to unit test the controller : [TestMethod] public void Create_Post_DuplicateNameAddsError() { // Arrange var repository = new Mock<Repository.IRepository>(); repository.Setup(a => a.Any<Domain.Common.Object1>(It.IsAny<System.Linq.Expressions.Expression<Func<Domain.Common.Object1, bool>>>())) .Returns(true); var controller = ControllerFactory<MyController>.GetController(); controller.Repository = repository.Object; var model = new Domain.Common.Object1() { Id = Guid.NewGuid() , Name = "Name" }; // Act var result = controller.Create(model) as ViewResult; // Assert Assert.IsFalse(controller.ModelState.IsValid); Assert.IsNotNull(result); Assert.AreEqual("Create", result.ViewName, false); Assert.AreEqual(model, result.Model); } Note: ControllerFactory is a way to generate a controller with certain properties filled, such as Request, Response, User, Request.Headers ect... Where this fails is if I have to use IRepository.Any(predicate) more than once, or any method that uses expressions that is called more than once. I need it to say true for one and false for another. If the expression strings were a match, this would be a non-issue, but as everything is in different projects the expression strings come out as: a => (a.Name.ToLower() == value(foo.Web.Tests.Controllers.Object1ControllerTests+<>c__DisplayClass3).ob1.Name.ToLower()) a => (a.Name.ToLower() == value(foo.Controllers.MyController+<>c__DisplayClass1).ob1.Name.ToLower()) The difference lies in the value function. I have tried matching from Regular Expressions, which works, but is ugly as you have to escape every .<>(), which in turn makes it very difficult to maintain. I tried using Matt Meber's Expression Equality Comparer, but they are not equal due to that value function (my belief). Suggestions?
ASP.NET MVC: How to maintain TextBox State when your ViewModel is a Collection/List/IEnumerable
I am using ASP.NET MVC 2 Beta. I can create a wizard like workflow using Steven Sanderson's technique (in his book Pro ASP.NET MVC Framework) except using Session instead of hidden form fields to preserve the data across requests. I can go back and forth between pages and maintain the values in a TextBox without any issue when my model is not a collection. An example would be a simple Person model: public class Person { public string Name { get; set; } public int Age { get; set; } public string Email { get; set; } } But I am unable to get this to work when I pass around an IEnumerable. In my view I am trying to run through the Model and generate a TextBox for Name and Email for each Person in the list. I can generate the form fine and I can submit the form with my values and go to Step2. But when I click the Back button in Step2 it takes me back to Step1 with an empty form. None of the fields that I previously populated are there. There must be something I am missing. Can somebody help me out? Here is my View: <% using (Html.BeginForm()) { %> <% int index = 0; foreach (var person in Model) { %> <fieldset> <%= Html.Hidden("persons.index", index.ToString())%> <div>Name: <%= Html.TextBox("persons[" + index.ToString() + "].Name")%></div> <div>Email: <%= Html.TextBox("persons[" + index.ToString() + "].Email")%></div> </fieldset> <% index++; } %> <p><input type="submit" name="btnNext" value="Next >>" /></p> <% } %> And here is my controller: public class PersonListController : Controller { public IEnumerable<Person> persons; protected override void OnActionExecuting(ActionExecutingContext filterContext) { persons = (Session["persons"] ?? TempData["persons"] ?? new List<Person>()) as List<Person>; // I've tried this with and without the prefix. TryUpdateModel(persons, "persons"); } protected override void OnResultExecuted(ResultExecutedContext filterContext) { Session["persons"] = persons; if (filterContext.Result is RedirectToRouteResult) TempData["persons"] = persons; } public ActionResult Step1(string btnBack, string btnNext) { if (btnNext != null) return RedirectToAction("Step2"); // Setup some fake data var personsList = new List<Person> { new Person { Name = "Jared", Email = "test#email.com", }, new Person { Name = "John", Email = "test2#email.com" } }; // Populate the model with fake data the first time // the action method is called only. This is to simulate // pulling some data in from a DB. if (persons == null || persons.Count() == 0) persons = personsList; return View(persons); } // Step2 is just a page that provides a back button to Step1 public ActionResult Step2(string btnBack, string btnNext) { if (btnBack != null) return RedirectToAction("Step1"); return View(persons); } }
As far as I can tell, this is not supported in ASP.NET MVC 2 Beta, nor is it supported in ASP.NET MVC 2 RC. I dug through the MVC source code and it looks like Dictionaries are supported but not Models that are IEnumerable<> (or that contain nested IEnumerable objects) and it's inheritors like IList<>. The issue is in the ViewDataDictionary class. Particularly, the GetPropertyValue method only provides a way to retrieve property values from dictionary properties (by calling GetIndexedPropertyValue) or simple properties by using the PropertyDescriptor.GetValue method to pull out the value. To fix this, I created a GetCollectionPropertyValue method that handles Models that are collections (and even Models that contain nested collections). I am pasting the code here for reference. Note: I don't make any claims about elegance - in fact all the string parsing is pretty ugly, but it seems to be working. Here is the method: // Can be used to pull out values from Models with collections and nested collections. // E.g. Persons[0].Phones[3].AreaCode private static ViewDataInfo GetCollectionPropertyValue(object indexableObject, string key) { Type enumerableType = TypeHelpers.ExtractGenericInterface(indexableObject.GetType(), typeof(IEnumerable<>)); if (enumerableType != null) { IList listOfModelElements = (IList)indexableObject; int firstOpenBracketPosition = key.IndexOf('['); int firstCloseBracketPosition = key.IndexOf(']'); string firstIndexString = key.Substring(firstOpenBracketPosition + 1, firstCloseBracketPosition - firstOpenBracketPosition - 1); int firstIndex = 0; bool canParse = int.TryParse(firstIndexString, out firstIndex); object element = null; // if the index was numeric we should be able to grab the element from the list if (canParse) element = listOfModelElements[firstIndex]; if (element != null) { int firstDotPosition = key.IndexOf('.'); int nextOpenBracketPosition = key.IndexOf('[', firstCloseBracketPosition); PropertyDescriptor descriptor = TypeDescriptor.GetProperties(element).Find(key.Substring(firstDotPosition + 1), true); // If the Model has nested collections, we need to keep digging recursively if (nextOpenBracketPosition >= 0) { string nextObjectName = key.Substring(firstDotPosition+1, nextOpenBracketPosition-firstDotPosition-1); string nextKey = key.Substring(firstDotPosition + 1); PropertyInfo property = element.GetType().GetProperty(nextObjectName); object nestedCollection = property.GetValue(element,null); // Recursively pull out the nested value return GetCollectionPropertyValue(nestedCollection, nextKey); } else { return new ViewDataInfo(() => descriptor.GetValue(element)) { Container = indexableObject, PropertyDescriptor = descriptor }; } } } return null; } And here is the modified GetPropertyValue method which calls the new method: private static ViewDataInfo GetPropertyValue(object container, string propertyName) { // This method handles one "segment" of a complex property expression // First, we try to evaluate the property based on its indexer ViewDataInfo value = GetIndexedPropertyValue(container, propertyName); if (value != null) { return value; } // If the indexer didn't return anything useful, continue... // If the container is a ViewDataDictionary then treat its Model property // as the container instead of the ViewDataDictionary itself. ViewDataDictionary vdd = container as ViewDataDictionary; if (vdd != null) { container = vdd.Model; } // Second, we try to evaluate the property based on the assumption // that it is a collection of some sort (e.g. IList<>, IEnumerable<>) value = GetCollectionPropertyValue(container, propertyName); if (value != null) { return value; } // If the container is null, we're out of options if (container == null) { return null; } // Third, we try to use PropertyDescriptors and treat the expression as a property name PropertyDescriptor descriptor = TypeDescriptor.GetProperties(container).Find(propertyName, true); if (descriptor == null) { return null; } return new ViewDataInfo(() => descriptor.GetValue(container)) { Container = container, PropertyDescriptor = descriptor }; } Again, this is in the ViewDataDictionary.cs file in ASP.NET MVC 2 RC. Should I create a new issue to track this on the MVC codeplex site?