Asp.Net MVC 3.0 Model Localization With RegularExpression Attribute - c#

I've written a custom error message localization logic in my custom DataAnnotationsModelMetadataProvider class. It's working just fine with build-in StringLengthAttribute or RequiredAttribute validation error messages. But i am having trouble with my custom derived RegularExpressionAttribute classes. The logic i am using is something like below :
public class AccountNameFormatAttribute : RegularExpressionAttribute {
public AccountNameFormatAttribute()
: base(Linnet.Core.Shared.RegExPatterns.AccountNamePattern) {
}
public override string FormatErrorMessage(string name) {
return string.Format("{0} field must contain only letters, numbers or | . | - | _ | characters.", name);
}
}
public class SignUpViewModel {
[AccountNameFormat()]
[StringLength(16, MinimumLength = 3)]
[Required]
[DisplayName("Account Name")]
public string AccountName { get; set; }
[Required]
[DisplayName("Password")]
[StringLength(32, MinimumLength = 6)]
[DataType(System.ComponentModel.DataAnnotations.DataType.Password)]
public string Password { get; set; }
// .... and other properties, quite similar ... //
}
public class MvcDataAnnotationsModelValidatorProvider : DataAnnotationsModelValidatorProvider {
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes) {
MyMvcController myMvcController = context.Controller as MyMvcController; /* custom mvc controller, that contains methods for wcf service activations and common properties. */
if (myMvcController == null) {
return base.GetValidators(metadata, context, attributes);
}
List<Attribute> newAttributes = new List<Attribute>();
foreach (Attribute att in attributes) {
if (att.GetType() != typeof(ValidationAttribute) && !att.GetType().IsSubclassOf(typeof(ValidationAttribute))) {
// if this is not a validation attribute, do nothing.
newAttributes.Add(att);
continue;
}
ValidationAttribute validationAtt = att as ValidationAttribute;
if (!string.IsNullOrWhiteSpace(validationAtt.ErrorMessageResourceName) && validationAtt.ErrorMessageResourceType != null) {
// if resource key and resource type is already set, do nothing.
newAttributes.Add(validationAtt);
continue;
}
string translationKey = "MvcModelMetaData.ValidationMessages." + metadata.ModelType.Name + (metadata.PropertyName != null ? "." + metadata.PropertyName : string.Empty) + "." + validationAtt.GetType().Name;
string originalText = validationAtt.FormatErrorMessage("{0}"); /* non-translated default english text */
// clonning current attiribute into a new attribute
// not to ruin original attribute for later usage
// using Activator.CreateInstance and then mapping with AutoMapper inside..
var newAtt = this.CloneValidationAttiribute(validationAtt);
// fetching translation from database via WCF service...
// At this point, i can see error strings are always translated.
// And it works perfect with [Required], [StringLength] and [DataType] attributes.
// But somehow it does not work with my AccountNameFormatAttribute on the web page, even if i give it the translated text as expected..
// Even if its ErrorMessage is already set to translated text,
// it still displays the original english text from the overridden FormatErrorMessage() method on the web page.
// It is the same both with client side validation or server side validation.
// Seems like it does not care the ErrorMessage that i manually set.
newAtt.ErrorMessage = myMvcController.Translations.GetTranslation(translationKey, originalText);
newAttributes.Add(newAtt);
}
IEnumerable<ModelValidator> result = base.GetValidators(metadata, context, newAttributes);
return result;
}
private ValidationAttribute CloneValidationAttiribute(ValidationAttribute att) {
if (att == null) {
return null;
}
Type attType = att.GetType();
ConstructorInfo[] constructorInfos = attType.GetConstructors();
if (constructorInfos == null || constructorInfos.Length <= 0) {
// can not close..
return att;
}
if (constructorInfos.Any(ci => ci.GetParameters().Length <= 0)) {
// clone with no constructor paramters.
return CloneManager.CloneObject(att) as ValidationAttribute;
}
// Validation attributes that needs constructor paramters...
if (attType == typeof(StringLengthAttribute)) {
int maxLength = ((StringLengthAttribute)att).MaximumLength;
return CloneManager.CloneObject(att, maxLength) as StringLengthAttribute;
}
return att;
}
}
public class CloneManager {
public static object CloneObject(object input) {
return CloneObject(input, null);
}
public static object CloneObject(object input, params object[] constructorParameters) {
if (input == null) {
return null;
}
Type type = input.GetType();
if (type.IsValueType) {
return input;
}
ConstructorInfo[] constructorInfos = type.GetConstructors();
if (constructorInfos == null || constructorInfos.Length <= 0) {
throw new LinnetException("0b59079b-3dc4-4763-b26d-651bde93ba56", "Object type does not have any constructors.", false);
}
if ((constructorParameters == null || constructorParameters.Length <= 0) && !constructorInfos.Any(ci => ci.GetParameters().Length <= 0)) {
throw new LinnetException("f03be2b9-b629-4a72-b025-c7a87924d9a4", "Object type does not have any constructor without parameters.", false);
}
object newObject = null;
if (constructorParameters == null || constructorParameters.Length <= 0) {
newObject = Activator.CreateInstance(type);
} else {
newObject = Activator.CreateInstance(type, constructorParameters);
}
return MapProperties(input, newObject);
}
private static object MapProperties(object source, object destination) {
if (source == null) {
return null;
}
Type type = source.GetType();
if (type != destination.GetType()) {
throw new LinnetException("e67bccfb-235f-42fc-b6b9-55f454c705a8", "Use 'MapProperties' method only for object with same types.", false);
}
if (type.IsValueType) {
return source;
}
var typeMap = AutoMapper.Mapper.FindTypeMapFor(type, type);
if (typeMap == null) {
AutoMapper.Mapper.CreateMap(type, type);
}
AutoMapper.Mapper.Map(source, destination, type, type);
return destination;
}
}

Seems like my logic was actually an odd approach.
I've discovered making custom DataAnnotationsModelValidators for each type of validation attiributes. And then translating the ErrorMessages inside Validate() and GetClientValidationRules() methods.
public class MvcRegularExpressionAttributeAdapter : RegularExpressionAttributeAdapter {
public MvcRegularExpressionAttributeAdapter(ModelMetadata metadata, ControllerContext context, RegularExpressionAttribute attribute)
: base(metadata, context, attribute) {
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules() {
return MvcValidationResultsTranslation.TranslateClientValidationRules(base.GetClientValidationRules(), this.Metadata, this.ControllerContext, this.Attribute);
}
public override IEnumerable<ModelValidationResult> Validate(object container) {
return MvcValidationResultsTranslation.TranslateValidationResults(base.Validate(container), this.Metadata, this.ControllerContext, this.Attribute);
}
}
public class MvcValidationResultsTranslation {
public static IEnumerable<ModelClientValidationRule> TranslateClientValidationRules(IEnumerable<ModelClientValidationRule> validationRules, ModelMetadata metadata, ControllerContext context, ValidationAttribute validationAttribute) {
if (validationRules == null) {
return validationRules;
}
MvcController mvcController = context.Controller as MvcController;
if (mvcController == null) {
return validationRules;
}
if (!string.IsNullOrWhiteSpace(validationAttribute.ErrorMessageResourceName) && validationAttribute.ErrorMessageResourceType != null) {
// if resource key and resource type is set, do not override..
return validationRules;
}
string translatedText = GetTranslation(metadata, mvcController, validationAttribute);
foreach (var validationRule in validationRules) {
List<string> msgParams = new List<string>();
msgParams.Add(!string.IsNullOrEmpty(metadata.DisplayName) ? metadata.DisplayName : metadata.PropertyName);
if (validationRule.ValidationParameters != null) {
msgParams.AddRange(validationRule.ValidationParameters.Where(p => p.Value.GetType().IsValueType || p.Value.GetType().IsEnum).Select(p => p.Value.ToString()));
}
validationRule.ErrorMessage = string.Format(translatedText, msgParams.ToArray());
}
return validationRules;
}
public static IEnumerable<ModelValidationResult> TranslateValidationResults(IEnumerable<ModelValidationResult> validationResults, ModelMetadata metadata, ControllerContext context, ValidationAttribute validationAttribute) {
if (validationResults == null) {
return validationResults;
}
MvcController mvcController = context.Controller as MvcController;
if (mvcController == null) {
return validationResults;
}
if (!string.IsNullOrWhiteSpace(validationAttribute.ErrorMessageResourceName) && validationAttribute.ErrorMessageResourceType != null) {
// if resource key and resource type is set, do not override..
return validationResults;
}
string translatedText = GetTranslation(metadata, mvcController, validationAttribute);
List<ModelValidationResult> newValidationResults = new List<ModelValidationResult>();
foreach (var validationResult in validationResults) {
ModelValidationResult newValidationResult = new ModelValidationResult();
newValidationResult.Message = string.Format(translatedText, (!string.IsNullOrEmpty(metadata.DisplayName) ? metadata.DisplayName : metadata.PropertyName));
newValidationResults.Add(newValidationResult);
}
return newValidationResults;
}
}

You can use my Griffin.MvcContrib to get easier localization.
Use nuget to download griffin.mvccontrib
Define a string table as described here.
Use the regular [RegularExpression] attribute directly in your view model.
Add this to your string table:
SignUpViewModel_AccountName_RegularExpression "{0} field must contain only letters, numbers or | . | - | _ | characters.
That's it..

Related

Newtonsoft issue with IDynamicMetaObjectProvider implementing classes on deserialization

currently I stumble upon a issue with Newtonsofts Json library, which is a total mystery to me.
I'm having a few classes, which are implementing the IDynamicMetaObjectProvider interface. Serialising several objects to json is no issue, I get exactly the json I expect from every instance of the object.
However deserialisation is giving me a headache. From what I have observed so far it seems like that the library is caching the value for every dynamic property it can't find and keeps this while the application is running. So as example I'm having following three Jsons:
{ "PropA": "1" }
{ "PropA": "2", "PropB": "1" }
{ "PropA": "3", "PropB": "2", "PropC": "1" }
Deserialising this the Jsons in a row will give me following .NET objects:
{ "PropA": "1" }
{ "PropA": "1", "PropB": "1" }
{ "PropA": "1", "PropB": "1", "PropC": "1" }
HOWEVER! If I change the target type from the one implementing IDynamicMetaObjectProvider to Dicitionary or simply dynamic, the deserialised object will have the properties set correctly.
My class is having an index property, setting a breakpoint on the setter, that already the setter is provided with the wrong value (so it is no issue with the implementation of my class).
public abstract class DynamicModelObject : IDynamicMetaObjectProvider //, IPropertyIndexer //, IDictionary<String, Object>
{
[NotMapped]
[JsonIgnore]
internal Dictionary<String, Object> properties = new Dictionary<String, Object>();
[IgnoreProperty]
[JsonIgnore]
public override Object this[String propertyName]
{
get
{
object val;
if (properties.TryGetValue(propertyName, out val)) {
return val;
}
var prop = this.GetType().GetProperty(propertyName);
if (prop != null && prop.CanRead) {
return prop.GetValue(this);
}
return null;
}
set
{
isDearty = true;
var prop = this.GetType().GetProperty(propertyName);
if (prop != null && prop.CanWrite) {
prop.SetValue(this, value);
} else {
properties[propertyName] = value;
}
var val = value as String;
if (value == null || (val != null && String.IsNullOrEmpty(val))) {
properties.Remove(propertyName);
}
}
}
public DynamicMetaObject GetMetaObject(System.Linq.Expressions.Expression parameter)
{
return new DynamicDictionaryPropertyStore<DynamicModelObject>(parameter, this);
}
[IgnoreProperty]
public IEnumerable<String> DynamicPropertyMemberNames
{
get
{
foreach (var key in properties.Keys) {
yield return key;
}
}
}
private List<String> staticProperties = null;
[IgnoreProperty]
private IEnumerable<String> StaticPropertyMemberNames
{
get
{
if (staticProperties == null) {
staticProperties = new List<String>();
foreach (var prop in this.GetType()
.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetProperty)) {
if (!Attribute.IsDefined(prop, typeof(IgnorePropertyAttribute)) && !Attribute.IsDefined(prop, typeof(ScriptIgnoreAttribute))) {
staticProperties.Add(prop.Name);
yield return prop.Name;
}
}
} else {
foreach (var prop in staticProperties) {
yield return prop;
}
}
}
}
[IgnoreProperty]
private IEnumerable<String> AllPropertyMemberNames
{
get
{
foreach (var prop in DynamicPropertyMemberNames.Concat(StaticPropertyMemberNames)) {
yield return prop;
}
}
}
private class DynamicDictionaryPropertyStore<T> : DynamicMetaObject where T : DynamicModelObject
{
private T target;
internal DynamicDictionaryPropertyStore(System.Linq.Expressions.Expression parameter, T target)
: base(parameter, BindingRestrictions.Empty, target)
{
this.target = target;
}
public override IEnumerable<string> GetDynamicMemberNames()
{
return target.DynamicPropertyMemberNames;
}
public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value)
{
BindingRestrictions restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType);
System.Linq.Expressions.Expression self = System.Linq.Expressions.Expression.Convert(Expression, LimitType);
if (binder == null) return null;
var body = System.Linq.Expressions.Expression.Property(self, "Item", System.Linq.Expressions.Expression.Constant(binder.Name));
var convert = System.Linq.Expressions.Expression.Convert(System.Linq.Expressions.Expression.Constant(value.Value), typeof(object));
var lambda = System.Linq.Expressions.Expression.Assign(body, convert);
return new DynamicMetaObject(lambda, restrictions);
}
public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
{
BindingRestrictions restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType);
System.Linq.Expressions.Expression self = System.Linq.Expressions.Expression.Convert(Expression, LimitType);
if (binder == null) return null;
var body = System.Linq.Expressions.Expression.Property(self, "Item", System.Linq.Expressions.Expression.Constant(binder.Name));
return new DynamicMetaObject(body, restrictions);
}
}
}
Whats going on??
Your implementation of DynamicModelObject is not correct. In DynamicDictionaryPropertyStore child class you do the following:
public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) {
BindingRestrictions restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType);
System.Linq.Expressions.Expression self = System.Linq.Expressions.Expression.Convert(Expression, LimitType);
if (binder == null) return null;
var body = System.Linq.Expressions.Expression.Property(self, "Item", System.Linq.Expressions.Expression.Constant(binder.Name));
var convert = System.Linq.Expressions.Expression.Convert(System.Linq.Expressions.Expression.Constant(value.Value), typeof(object));
var lambda = System.Linq.Expressions.Expression.Assign(body, convert);
return new DynamicMetaObject(lambda, restrictions);
}
If you will look at the resulting expression you have, you will see (for example for PropA):
Convert($arg0).Item["PropA"] = Convert("1")
So as a setter you return expression which calls your indexer and assigns constant value (1), regardless of what value was actully passed. This expression will be used later for all setters to PropA (cached). Hence your problem: all your setters will ignore passed values and will always assign value with which you called them for a first time. To fix, replace this line:
var convert = System.Linq.Expressions.Expression.Convert(System.Linq.Expressions.Expression.Constant(value.Value), typeof(object));
With this:
var convert = System.Linq.Expressions.Expression.Convert(value.Expression, typeof(object));
Resulting setter expression will be:
Convert($arg0).Item["PropA"] = Convert($arg1)
Note that no costants are there, just arguments. After that your problem will be solved.

"An object reference is required for the non-static field,method,or property ' RxCard.dataobjects.Pharmacy.Area.Get' "

I have a custom validation attribute that I need to pass in some properties to. However, my problem occurs when applying the attribute itself. I'm learning .net backwards so I tend to get stuck on the "simpler" problems. I already tried making the property a static but that messed up parts of my view. How can I approach this?
Attribute:
public class MinimumPhoneDigits : ValidationAttribute
{
public string[] _properties;
public int _expectedsize;
public MinimumPhoneDigits(int expectedsize, params string[] properties)
{
ErrorMessage = "Not the expected size!";
_properties = properties;
_expectedsize = expectedsize;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (_properties == null || _properties.Length < 1)
{
return new ValidationResult("WOAH! Not the right size.");
}
int totalsize = 0;
foreach (var property in _properties)
{
var propinfo = validationContext.ObjectType.GetProperty(property);
if (propinfo == null)
return new ValidationResult(string.Format("could not find {property}"));
var propvalue = propinfo.GetValue(validationContext.ObjectInstance, null) as string;
if (propvalue == null)
return new ValidationResult(string.Format("wrong property for {property}"));
totalsize += propvalue.Length;
}
if (totalsize != _expectedsize)
return new ValidationResult(ErrorMessage);
return ValidationResult.Success;
}
}
class:
public class Pharmacy
{
[MinimumPhoneDigits(10, Area)]
public string PhoneNumber
{
get
{
return _phoneNumber;
}
set
{
_phoneNumber = value;
}
}
private string _phoneNumber;
public string Area
{
get
{
try
{
return _phoneNumber.Split(new char[] { '(', ')', '-' }, StringSplitOptions.RemoveEmptyEntries)[0].Trim();
}
catch
{
return "";
}
}
}
}
Attributes are design-time. You can't pass values that are only known at runtime like Area
I think you might actually be intending to pass a string, like this
[MinimumPhoneDigits(10, "Area")]

Howto search through Properties of all kinds of types

I have a base class called Part and derived classes like Wire or Connector and many more that inherit from Part.
Now I want to implement a search function that searches all Properties of the derived classes for a string.
If necessary that string should be tried to be converted to the type of the Property. The Properties can also be Lists and should be searched on the first level.
class Part
{
public int Id { get; set; }
public string Name { get; set; }
}
class Wire : Part
{
public NumberWithUnit Diameter { get; set; }
public Weight Weight { get; set; }
}
class Connector : Part
{
public List<Part> ConnectedParts { get; set; }
}
I know how to generally search through the Properties of base types with Reflection like this
private bool SearchProperties<T>(T part, string searchString) where T : Part
{
var props = typeof(T).GetProperties();
foreach (var prop in props)
{
var value = prop.GetValue(part);
if (value is string)
{
if (string.Equals(value, searchString))
return true;
}
else if (value is int)
{
int v;
if (int.TryParse(searchString, out v))
{
if(v == (int) value)
return true;
}
}
}
return false;
}
But that would be a long list of types and I have Properties of Type Weight for instance and many more. Is there some kind of general way to search without casting all types?
Consider going the opposite direction with your conversion. Rather than converting your search string into each possible value, just convert the value into a string:
private bool SearchProperties<T>(T part, string searchString) where T : Part
{
var props = typeof(T).GetProperties();
foreach (var prop in props)
{
var value = prop.GetValue(part);
if (value is IEnumerable)
{
// special handling for collections
}
else if(value != null)
{
string valueString = value.ToString();
if (string.Equals(valueString, searchString))
return true;
}
}
return false;
}
Besides working pretty well for most built-in types, the only thing you have to do to get it to work for Weight, etc. is make sure they implement ToString().
Another solution would be to use TypeDescriptor:
private bool SearchProperties<T>(T part, string searchString) where T : Part
{
var props = typeof(T).GetProperties();
foreach (var prop in props)
{
var value = prop.GetValue(part);
if (value is IEnumerable)
{
// special handling for collections
}
else if(value != null)
{
object searchValue = null;
try
{
searchValue = TypeDescriptor.GetConverter(value).ConvertFromString(searchString);
} catch {}
if (searchValue != null && object.Equals(value, searchValue))
return true;
}
}
return false;
}
TypeDescriptor works well for most built-in types, but requires extra work if you're dealing with custom types.
I think the following should cover the most of the practical scenarios:
public static bool SearchProperties(object target, string searchString)
{
if (target == null) return false;
// Common types
var convertible = target as IConvertible;
if (convertible != null)
{
var typeCode = convertible.GetTypeCode();
if (typeCode == TypeCode.String) return target.ToString() == searchString;
if (typeCode == TypeCode.DBNull) return false;
if (typeCode != TypeCode.Object)
{
try
{
var value = Convert.ChangeType(searchString, typeCode);
return target.Equals(value);
}
catch { return false; }
}
}
if (target is DateTimeOffset)
{
DateTimeOffset value;
return DateTimeOffset.TryParse(searchString, out value) && value == (DateTimeOffset)target;
}
var enumerable = target as IEnumerable;
if (enumerable != null)
{
// Collection
foreach (var item in enumerable)
if (SearchProperties(item, searchString)) return true;
}
else
{
// Complex type
var properties = target.GetType().GetProperties();
foreach (var property in properties)
{
if (property.GetMethod == null || property.GetMethod.GetParameters().Length > 0) continue;
var value = property.GetValue(target);
if (SearchProperties(value, searchString)) return true;
}
}
return false;
}
I will give you one different idea to do it.
You could try something like that:
private bool SearchProperties<T, W>(T part, W searchValue) where T : Part
{
var props = typeof(T).GetProperties();
foreach (var prop in props)
{
if (typeof(W) == prop.PropertyType)
{
var value = prop.GetValue(part, null);
if (searchValue.Equals(value))
return true;
}
}
return false;
}
You need to call the method like this:
private void button12_Click(object sender, EventArgs e)
{
Part p = new Part();
p.Id = 2;
p.Name = "test";
p.bla = new Bla();
SearchProperties<Part, int>(p, 2);
}
And if you need to compare the complex properties (Weight, ...) by a different way from GetHashCode you could override the method Equals or the == operator.
class Weight
{
public int Id { get; set; }
public override bool Equals(object obj)
{
return Id == ((Weight)obj).Id;
}
}

How do I do Sub Object Custom Validation mvc3

I have an object (header) that has a list of sub-ojects (details) that I want to do custom validation on prior to accepting data. I've tried ModelState.IsValid and TryValidateModel, but it doesn't seem to fire the Validate method on the sub-objects (only the header object).
So on submission I see the validation fire for the header, but not the sub-ojects. Then if I do a TryValidateModel I again see (breakpoint) the validation method get called on the header, but not on the sub-objects.
The annotated validation (must be number, etc...) seems to be working on the sub-objects, just not the custom logic added via the IValidatableObject interface. Any help would be greatly appreciated.
I make an attribute ( [ValidateObject] ) and it will validate the attribute you put on your class, like you would think it suppose to do.
public class Personne
{
[ValidateObject]
public Address Address { get; set; }
//[...]
}
(Address is a custom class.)
It can be used for validating:
Object properties on a model. (like above)
Collection of sub object.
[ValidateObject]
public List<Address> Address { get; set; }
It support multi level of dept, if "Address" had a propertie of type "ZipCode" with the attribute [ValidateObject] it would be validate tho.
Code:
public class ValidateObjectAttribute : ValidationAttribute
{
public ValidateObjectAttribute()
{
}
private ValidationContext ValidationContext { get; set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
this.ValidationContext = validationContext;
var results = new List<ValidationResult>();
try
{
var isIterable = this.IsIterable(value);
if (isIterable)
{
int currentItemPosition = -1;
foreach (var objectToValidate in value as IEnumerable<object>)
{
currentItemPosition++;
var resultTemp = ValidationsForObject(objectToValidate, true, currentItemPosition);
if (resultTemp != null)
results.AddRange(resultTemp);
}
if (results.Count <= 0)
results = null;
}
else
results = ValidationsForObject(value);
if (results != null)
{
//Build a validation result
List<string> memberNames = new List<string>();
results.ForEach(r => memberNames.AddRange(r.MemberNames));
var compositeResultsReturn = new CompositeValidationResult($"Validation for {validationContext.DisplayName} failed!", memberNames.AsEnumerable());
results.ForEach(r => compositeResultsReturn.AddResult(r));
return compositeResultsReturn;
}
}
catch (Exception) { }
return ValidationResult.Success;
}
private List<ValidationResult> ValidationsForObject (object objectToValidate, bool IsIterable = false, int position = -1)
{
var results = new List<ValidationResult>();
var contextTemp = new ValidationContext(objectToValidate, null, null);
var resultsForThisItem = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(objectToValidate, contextTemp, resultsForThisItem, true);
if (isValid)
return null;
foreach (var validationResult in resultsForThisItem)
{
List<string> propNames = new List<string>();// add prefix to properties
foreach (var nameOfProp in validationResult.MemberNames)
{
if (IsIterable)
propNames.Add($"{this.ValidationContext.MemberName}[{position}].{nameOfProp}");
else
propNames.Add($"{this.ValidationContext.MemberName}.{nameOfProp}");
}
var customFormatValidation = new ValidationResult(validationResult.ErrorMessage, propNames);
results.Add(customFormatValidation);
}
return results;
}
private bool IsIterable(object value)
{
////COULD WRITE THIS, but its complicated to debug...
//if (value.GetType().GetInterfaces().Any(
//i => i.IsGenericType &&
//i.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
//{
// // foreach...
//}
Type valueType = value.GetType();
var interfaces = valueType.GetInterfaces();
bool isIterable = false;
foreach (var i in interfaces)
{
var isGeneric = i.IsGenericType;
bool isEnumerable = i.GetGenericTypeDefinition() == typeof(IEnumerable<>);
isIterable = isGeneric && isEnumerable;
if (isIterable)
break;
}
return isIterable;
}
}
public class CompositeValidationResult : ValidationResult
{
private readonly List<ValidationResult> _results = new List<ValidationResult>();
public IEnumerable<ValidationResult> Results
{
get
{
return _results;
}
}
public CompositeValidationResult(string errorMessage) : base(errorMessage)
{
}
public CompositeValidationResult(string errorMessage, IEnumerable<string> memberNames) : base(errorMessage, memberNames)
{
}
protected CompositeValidationResult(ValidationResult validationResult) : base(validationResult)
{
}
public void AddResult(ValidationResult validationResult)
{
_results.Add(validationResult);
}
}
This will work if you model is correctly binded :)
You might want to add the required attribute, making sure the the object is not null itself.
[Required]
Hope it help!
I wonder if you root object has errors preventing child validation.
See Recursive validation using annotations and IValidatableObject
This URL mentions that scenario and also code to force validation on the child from the root
As per the posting triggering off the validation from the root object
public IEnumerable Validate(ValidationContext validationContext)
{
var context = new ValidationContext(this.Details, validationContext.ServiceContainer, validationContext.Items);
var results = new List();
Validator.TryValidateObject(this.Details, context, results);
return results;
}
The TryValidateObject didn't appear to fire custom validation, only data annotations? I went down the path of adding validation of the details to the header validation by doing the following:
foreach (var detail in this.Details)
{
var validationResults = detail.Validate(validationContext);
foreach (var validationResult in validationResults)
{
yield return validationResult;
}
}
This worked in terms of validation, but the UI didn't display the error messages. Even though I have ValidationMessagesFor on the UI.
Validation not working solved here: MVC3 Master-Details Validation not Displaying

NHibernate 3.2 mapping by code ignores my IUserType

I have a LocalizedString classed used to store localization of a single value. The concept is loosely based on a post by Fabio Maulo.
I'm using the new Mapping-By-Code concept in NHibernate 3.2, but it seem to ignore the IUserType implementation, because when it generate the SQL, it create a column with a different name and with the default string NVARCHAR(255) type.
I'm trying to map this simple class
public class Region : Entity
{
/// <summary>
/// Initializes a new instance of the <see cref="Region"/> class.
/// </summary>
public Region()
{
}
/// <summary>
/// Gets or sets the localized name of the <see cref="Region"/>.
/// </summary>
public virtual LocalizedString Name { get; set; }
}
The resulting SQL is
create table Regions (RegionId INT not null, Item NVARCHAR(255) not null, primary key (RegionId))
The Item column here should be called Name and it should be of type XML. I presume the column name come from the name of the indexer of the LocalizedString.
This is my NHibernate configuration (its not complete, I'm in the process of building the convention)
private static Configuration CreateNHibernateConfiguration()
{
var cfg = new Configuration();
cfg.Proxy(p => p.ProxyFactoryFactory<NHibernate.Bytecode.DefaultProxyFactoryFactory>())
.DataBaseIntegration(db =>
{
db.ConnectionStringName = "***";
db.Dialect<MsSql2008Dialect>();
db.BatchSize = 500;
});
var mapper = new ConventionModelMapper();
var baseEntityType = typeof(Entity);
mapper.IsEntity((t, declared) => baseEntityType.IsAssignableFrom(t) && baseEntityType != t && !t.IsInterface);
mapper.IsRootEntity((t, declared) => baseEntityType.Equals(t.BaseType));
mapper.BeforeMapClass += (mi, t, map) =>
{
map.Table(Inflector.MakePlural(t.Name));
map.Id(x =>
{
x.Column(t.Name + "Id");
});
};
mapper.BeforeMapManyToOne += (insp, prop, map) =>
{
map.Column(prop.LocalMember.GetPropertyOrFieldType().Name + "Id");
map.Cascade(Cascade.Persist);
};
mapper.BeforeMapBag += (insp, prop, map) =>
{
map.Key(km => km.Column(prop.GetContainerEntity(insp).Name + "Id"));
map.Cascade(Cascade.All);
};
mapper.BeforeMapProperty += (insp, prop, map) =>
{
map.NotNullable(true);
};
var exportedTypes = baseEntityType.Assembly.GetExportedTypes();
mapper.AddMappings(exportedTypes.Where(t => t.Namespace.EndsWith("Mappings", StringComparison.Ordinal)));
var mapping = mapper.CompileMappingFor(exportedTypes.Where(t => t.Namespace.EndsWith("Data", StringComparison.Ordinal)));
cfg.AddDeserializedMapping(mapping, "MyModel");
SchemaMetadataUpdater.QuoteTableAndColumns(cfg);
return cfg;
}
This is the IUserType definition of my LocalizedString class:
/// <summary>
/// Defines a string that can have a different value in multiple cultures.
/// </summary>
public sealed partial class LocalizedString : IUserType
{
object IUserType.Assemble(object cached, object owner)
{
var value = cached as string;
if (value != null)
{
return LocalizedString.Parse(value);
}
return null;
}
object IUserType.DeepCopy(object value)
{
var toCopy = value as LocalizedString;
if (toCopy == null)
{
return null;
}
var localizedString = new LocalizedString();
foreach (var localizedValue in toCopy.localizedValues)
{
localizedString.localizedValues.Add(localizedValue.Key, localizedValue.Value);
}
return localizedString;
}
object IUserType.Disassemble(object value)
{
var localizedString = value as LocalizedString;
if (localizedString != null)
{
return localizedString.ToXml();
}
return null;
}
bool IUserType.Equals(object x, object y)
{
if (x == null && y == null)
{
return true;
}
if (x == null || y == null)
{
return false;
}
var localizedStringX = (LocalizedString)x;
var localizedStringY = (LocalizedString)y;
if (localizedStringX.localizedValues.Count() != localizedStringY.localizedValues.Count())
{
return false;
}
foreach (var value in localizedStringX.localizedValues)
{
if (!localizedStringY.localizedValues.ContainsKey(value.Key) || localizedStringY.localizedValues[value.Key] == value.Value)
{
return false;
}
}
return true;
}
int IUserType.GetHashCode(object x)
{
if (x == null)
{
throw new ArgumentNullException("x");
}
return x.GetHashCode();
}
bool IUserType.IsMutable
{
get { return true; }
}
object IUserType.NullSafeGet(System.Data.IDataReader rs, string[] names, object owner)
{
if (rs == null)
{
throw new ArgumentNullException("rs");
}
if (names == null)
{
throw new ArgumentNullException("names");
}
if (names.Length != 1)
{
throw new InvalidOperationException("names array has more than one element. can't handle this!");
}
var val = rs[names[0]] as string;
if (val != null)
{
return LocalizedString.Parse(val);
}
return null;
}
void IUserType.NullSafeSet(System.Data.IDbCommand cmd, object value, int index)
{
if (cmd == null)
{
throw new ArgumentNullException("cmd");
}
var parameter = (DbParameter)cmd.Parameters[index];
var localizedString = value as LocalizedString;
if (localizedString == null)
{
parameter.Value = DBNull.Value;
}
else
{
parameter.Value = localizedString.ToXml();
}
}
object IUserType.Replace(object original, object target, object owner)
{
throw new NotImplementedException();
}
Type IUserType.ReturnedType
{
get { return typeof(LocalizedString); }
}
NHibernate.SqlTypes.SqlType[] IUserType.SqlTypes
{
get { return new[] { new XmlSqlType() }; }
}
}
You shouldn't use the IUserType in your domain model.
The IUserType interface should actually be called something like IUserTypeMapper, and you have to specify it explicitly in your mapping.
I suggest that you re-read that post.
Update: try this to map your type by convention:
mapper.BeforeMapProperty +=
(insp, prop, map) =>
{
if (/*determine if this is member should be mapped as LocalizedString*/)
map.Type<LocalizedString>();
};
Of course the "determine if..." part would be something you determine, like the property name starting with "Localized", a custom attribute, or anything you want.

Categories