custom data annotation in partial metadata not working - c#

I am implementing a custom data annotation and in the main model it works correctly, but when I put the data annotation in the partial metada the validation does not find this data annotation.
my code is as follows:
public partial class register
{
public int id { get; set; }
public long idPerson { get; set; }
public string other { get; set; }
}
public partial class register_Metadata
{
[MyAttributeOne("val1")]
public int id { get; set; }
[MyAttributeOne("val1")]
public long idPerson { get; set; }
public string other { get; set; }
}
The namespace of the two classes is the same.
On the other hand I have a class where I link the two partial classes.
[MetadataType(typeof( register_Metadata))]
public partial class register
{
}
When I validate the field with customized metadata, the propierties function always has 0 results
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var properties = this.GetInvolvedProperties(validationContext.ObjectType);
var numberOfRequiredFields = RequireFromGroupAttribute.GetNumberOfRequiredFields(validationContext.ObjectType, this.Selector);
var values = new List<object> { value };
var otherPropertiesValues = properties.Where(p => p.Key.Name != validationContext.MemberName).Select(p => p.Key.GetValue(validationContext.ObjectInstance));
values.AddRange(otherPropertiesValues);
if (values.Count(s => !string.IsNullOrWhiteSpace(Convert.ToString(s))) >= numberOfRequiredFields)
{
return ValidationResult.Success;
}
return new ValidationResult(this.GetErrorMessage(numberOfRequiredFields, properties.Values), new List<string> { validationContext.MemberName });
}
private Dictionary<PropertyInfo, string> GetInvolvedProperties(Type type)
{
return type.GetProperties()
.Where(p => p.IsDefined(typeof(RequireFromGroupFieldAttribute)) &&
p.GetCustomAttribute<RequireFromGroupFieldAttribute>().Selector == this.Selector)
.ToDictionary(p => p, p => p.IsDefined(typeof(DisplayAttribute)) ? p.GetCustomAttribute<DisplayAttribute>().Name : p.Name);
}
I have changed the data annotation to the main class and then in the properties function I have the two parameters to evaluate.
However when I put them in the metadata class it does not work.

Related

How to get custom attribute of a property within a property in a class

My task is to the set/initialize all the properties tagged with custom attributes inside a class, derived to the class & properties within properties of that class.
Example:
class Program
{
static void Main(string[] args)
{
A a = new A();
a.InjectableProperties();
}
}
public class A : C
{
[CodeModuleProperty(ModulePropertyType.Injectable)]
public string NameA{ get; set; }
public B ObjB { get; set; }
public IEnumerable<PropertyInfo> InjectableProperties()
{
var response = new List<PropertyInfo>();
// get the mnodule properties
var moduleProperties = GetType().GetProperties();
foreach (var moduleProperty in moduleProperties)
{
var attribute = moduleProperty.GetCustomAttributes(typeof(CodeModulePropertyAttribute), false)
.FirstOrDefault();
if (attribute != null && ((CodeModulePropertyAttribute)attribute).PropertyType ==
ModulePropertyType.Injectable)
{
response.Add(moduleProperty);
}
}
return response // Only gives A,D &C . I also want custom attributes children properties within objB;
}
}
In this method, I also want to get custom attributes properties for property "ClassB" along with properties from Class A, D & E. How to achieve that?
public class B
{
[CodeModuleProperty(ModulePropertyType.Injectable)]
public string NameB { get; set; }
}
public class C : D
{
[CodeModuleProperty(ModulePropertyType.Injectable)]
public string NameC { get; set; }
}
public class D
{
[CodeModuleProperty(ModulePropertyType.Injectable)]
public string NameD { get; set; }
}
[AttributeUsage(AttributeTargets.Property)]
public class CodeModulePropertyAttribute : Attribute
{
public CodeModulePropertyAttribute(ModulePropertyType propertyType)
{
PropertyType = propertyType;
}
public ModulePropertyType PropertyType { get; set; }
}
public enum ModulePropertyType
{
Injectable = 1,
DynamicConfiguration = 2
}
For property
object attr = typeof(MyClass)
.GetProperty("NameB")
.GetCustomAttributes(typeof(CodeModuleProperty), false)
.FirstOrDefault();
For class
object attr = typeof(MyClass)
.GetCustomAttributes(typeof(CodeModuleProperty), false)
.FirstOrDefault();
if you want to check all properties, just do .GetProperties and iterate them with for-each and call GetCustomAttributes

Why is my custom member resolver for enum to class conversion not working?

I get the exception message:
The binary operator NotEqual is not defined for the types 'NotificationArea' and 'System.Object'
Source enum:
public enum NotificationArea
{
One,
Two,
Three
}
Destination class:
public class EnumValue
{
public EnumValue()
{
}
public EnumValue(Enum tEnum)
{
Id = Convert.ToInt32(tEnum);
Name = GetEnumValue(tEnum, tEnum.GetType());
}
public int Id { get; set; }
public string Name { get; set; }
public string GetEnumValue(Enum tEnum, Type type)
{
MemberInfo member = type.GetMember(tEnum.ToString())[0];
if (member.GetCustomAttribute<DisplayAttribute>() != null)
{
DisplayAttribute attribute = member.GetCustomAttribute<DisplayAttribute>();
return attribute.Name;
}
return tEnum.ToString();
}
}
Conversion class:
public class EnumConverter : IMemberValueResolver<Notification,
NotificationModel, NotificationArea, EnumValue>
{
public EnumValue Resolve(Notification source, NotificationModel destination,
NotificationArea sourceMember, EnumValue destMember, ResolutionContext context)
{
var model = source.Area.ToDisplay();
return model;
}
}
ToDisplay Extension:
public static EnumValue ToDisplay(this Enum value)
{
var display = new EnumValue(value);
return display;
}
Implementation:
// Query
var notifications = await _db.Notifications
.OrderByDescending(x => x.Timestamp)
.ProjectTo<NotificationModel>(_mapperConfig)
.ToListAsync(token);
// Mapping
CreateMap<Notification, NotificationModel>()
.ForMember(dest => dest.Area, opt => opt.ResolveUsing(src => src.Area));
Source and Destination Models:
public class Notification
{
public int Id {get;set;}
public NotificationArea Area { get; set; }
}
public class NotificationModel
{
public int Id {get;set;}
public EnumValue Area {get;set;}
}
I know I'm doing something wrong... obviously... I just don't know exactly where it's at. I feel Like I have A and C but am missing B.

Newtonsoft JSON dynamic property name

Is there a way to change name of Data property during serialization, so I can reuse this class in my WEB Api.
For an example, if i am returning paged list of users, Data property should be serialized as "users", if i'm returning list of items, should be called "items", etc.
Is something like this possible:
public class PagedData
{
[JsonProperty(PropertyName = "Set from constructor")]??
public IEnumerable<T> Data { get; private set; }
public int Count { get; private set; }
public int CurrentPage { get; private set; }
public int Offset { get; private set; }
public int RowsPerPage { get; private set; }
public int? PreviousPage { get; private set; }
public int? NextPage { get; private set; }
}
EDIT:
I would like to have a control over this functionality, such as passing name to be used if possible. If my class is called UserDTO, I still want serialized property to be called Users, not UserDTOs.
Example
var usersPagedData = new PagedData("Users", params...);
You can do this with a custom ContractResolver. The resolver can look for a custom attribute which will signal that you want the name of the JSON property to be based on the class of the items in the enumerable. If the item class has another attribute on it specifying its plural name, that name will then be used for the enumerable property, otherwise the item class name itself will be pluralized and used as the enumerable property name. Below is the code you would need.
First let's define some custom attributes:
public class JsonPropertyNameBasedOnItemClassAttribute : Attribute
{
}
public class JsonPluralNameAttribute : Attribute
{
public string PluralName { get; set; }
public JsonPluralNameAttribute(string pluralName)
{
PluralName = pluralName;
}
}
And then the resolver:
public class CustomResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty prop = base.CreateProperty(member, memberSerialization);
if (prop.PropertyType.IsGenericType && member.GetCustomAttribute<JsonPropertyNameBasedOnItemClassAttribute>() != null)
{
Type itemType = prop.PropertyType.GetGenericArguments().First();
JsonPluralNameAttribute att = itemType.GetCustomAttribute<JsonPluralNameAttribute>();
prop.PropertyName = att != null ? att.PluralName : Pluralize(itemType.Name);
}
return prop;
}
protected string Pluralize(string name)
{
if (name.EndsWith("y") && !name.EndsWith("ay") && !name.EndsWith("ey") && !name.EndsWith("oy") && !name.EndsWith("uy"))
return name.Substring(0, name.Length - 1) + "ies";
if (name.EndsWith("s"))
return name + "es";
return name + "s";
}
}
Now you can decorate the variably-named property in your PagedData<T> class with the [JsonPropertyNameBasedOnItemClass] attribute:
public class PagedData<T>
{
[JsonPropertyNameBasedOnItemClass]
public IEnumerable<T> Data { get; private set; }
...
}
And decorate your DTO classes with the [JsonPluralName] attribute:
[JsonPluralName("Users")]
public class UserDTO
{
...
}
[JsonPluralName("Items")]
public class ItemDTO
{
...
}
Finally, to serialize, create an instance of JsonSerializerSettings, set the ContractResolver property, and pass the settings to JsonConvert.SerializeObject like so:
JsonSerializerSettings settings = new JsonSerializerSettings
{
ContractResolver = new CustomResolver()
};
string json = JsonConvert.SerializeObject(pagedData, settings);
Fiddle: https://dotnetfiddle.net/GqKBnx
If you're using Web API (looks like you are), then you can install the custom resolver into the pipeline via the Register method of the WebApiConfig class (in the App_Start folder).
JsonSerializerSettings settings = config.Formatters.JsonFormatter.SerializerSettings;
settings.ContractResolver = new CustomResolver();
Another Approach
Another possible approach uses a custom JsonConverter to handle the serialization of the PagedData class specifically instead using the more general "resolver + attributes" approach presented above. The converter approach requires that there be another property on your PagedData class which specifies the JSON name to use for the enumerable Data property. You could either pass this name in the PagedData constructor or set it separately, as long as you do it before serialization time. The converter will look for that name and use it when writing out JSON for the enumerable property.
Here is the code for the converter:
public class PagedDataConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(PagedData<>);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Type type = value.GetType();
var bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
string dataPropertyName = (string)type.GetProperty("DataPropertyName", bindingFlags).GetValue(value);
if (string.IsNullOrEmpty(dataPropertyName))
{
dataPropertyName = "Data";
}
JObject jo = new JObject();
jo.Add(dataPropertyName, JArray.FromObject(type.GetProperty("Data").GetValue(value)));
foreach (PropertyInfo prop in type.GetProperties().Where(p => !p.Name.StartsWith("Data")))
{
jo.Add(prop.Name, new JValue(prop.GetValue(value)));
}
jo.WriteTo(writer);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use this converter, first add a string property called DataPropertyName to your PagedData class (it can be private if you like), then add a [JsonConverter] attribute to the class to tie it to the converter:
[JsonConverter(typeof(PagedDataConverter))]
public class PagedData<T>
{
private string DataPropertyName { get; set; }
public IEnumerable<T> Data { get; private set; }
...
}
And that's it. As long as you've set the DataPropertyName property, it will be picked up by the converter on serialization.
Fiddle: https://dotnetfiddle.net/8E8fEE
UPD Sep 2020: #RyanHarlich pointed that proposed solution doesn't work out of the box. I found that Newtonsoft.Json doesn't initialize getter-only properties in newer versions, but I'm pretty sure it did ATM I wrote this answer in 2016 (no proofs, sorry :).
A quick-n-dirty solution is to add public setters to all properties ( example in dotnetfiddle ). I encourage you to find a better solution that keeps read-only interface for data objects. I haven't used .Net for 3 years, so cannot give you that solution myself, sorry :/
Another option with no need to play with json formatters or use string replacements - only inheritance and overriding (still not very nice solution, imo):
public class MyUser { }
public class MyItem { }
// you cannot use it out of the box, because it's abstract,
// i.e. only for what's intended [=implemented].
public abstract class PaginatedData<T>
{
// abstract, so you don't forget to override it in ancestors
public abstract IEnumerable<T> Data { get; }
public int Count { get; }
public int CurrentPage { get; }
public int Offset { get; }
public int RowsPerPage { get; }
public int? PreviousPage { get; }
public int? NextPage { get; }
}
// you specify class explicitly
// name is clear,.. still not clearer than PaginatedData<MyUser> though
public sealed class PaginatedUsers : PaginatedData<MyUser>
{
// explicit mapping - more agile than implicit name convension
[JsonProperty("Users")]
public override IEnumerable<MyUser> Data { get; }
}
public sealed class PaginatedItems : PaginatedData<MyItem>
{
[JsonProperty("Items")]
public override IEnumerable<MyItem> Data { get; }
}
Here is a solution that doesn't require any change in the way you use the Json serializer. In fact, it should also work with other serializers. It uses the cool DynamicObject class.
The usage is just like you wanted:
var usersPagedData = new PagedData<User>("Users");
....
public class PagedData<T> : DynamicObject
{
private string _name;
public PagedData(string name)
{
if (name == null)
throw new ArgumentNullException(nameof(name));
_name = name;
}
public IEnumerable<T> Data { get; private set; }
public int Count { get; private set; }
public int CurrentPage { get; private set; }
public int Offset { get; private set; }
public int RowsPerPage { get; private set; }
public int? PreviousPage { get; private set; }
public int? NextPage { get; private set; }
public override IEnumerable<string> GetDynamicMemberNames()
{
yield return _name;
foreach (var prop in GetType().GetProperties().Where(p => p.CanRead && p.GetIndexParameters().Length == 0 && p.Name != nameof(Data)))
{
yield return prop.Name;
}
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (binder.Name == _name)
{
result = Data;
return true;
}
return base.TryGetMember(binder, out result);
}
}
The following is another solution tested in .NET Standard 2.
public class PagedResult<T> where T : class
{
[JsonPropertyNameBasedOnItemClassAttribute]
public List<T> Results { get; set; }
[JsonProperty("count")]
public long Count { get; set; }
[JsonProperty("total_count")]
public long TotalCount { get; set; }
[JsonProperty("current_page")]
public long CurrentPage { get; set; }
[JsonProperty("per_page")]
public long PerPage { get; set; }
[JsonProperty("pages")]
public long Pages { get; set; }
}
I am using Humanizer for pluralization.
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (member.GetCustomAttribute<JsonPropertyNameBasedOnItemClassAttribute>() != null)
{
Type[] arguments = property.DeclaringType.GenericTypeArguments;
if(arguments.Length > 0)
{
string name = arguments[0].Name.ToString();
property.PropertyName = name.ToLower().Pluralize();
}
return property;
}
return base.CreateProperty(member, memberSerialization);
}
There's a package called SerializationInterceptor. Here's the GitHub link: https://github.com/Dorin-Mocan/SerializationInterceptor/wiki. You can also install the package using Nuget Package Manager.
The example from below uses Syste.Text.Json for serialization. You can use any other serializer(except Newtonsoft.Json). For more info on why Newtonsoft.Json not allowed, please refer to GitHub documentation.
You can create an interceptor
public class JsonPropertyNameInterceptorAttribute : InterceptorAttribute
{
public JsonPropertyNameInterceptorAttribute(string interceptorId)
: base(interceptorId, typeof(JsonPropertyNameAttribute))
{
}
protected override void Intercept(in AttributeParams originalAttributeParams, object context)
{
string theNameYouWant;
switch (InterceptorId)
{
case "some id":
theNameYouWant = (string)context;
break;
default:
return;
}
originalAttributeParams.ConstructorArgs.First().ArgValue = theNameYouWant;
}
}
And put the interceptor on the Data prop
public class PagedData<T>
{
[JsonPropertyNameInterceptor("some id")]
[JsonPropertyName("during serialization this value will be replaced with the one passed in context")]
public IEnumerable<T> Data { get; private set; }
public int Count { get; private set; }
public int CurrentPage { get; private set; }
public int Offset { get; private set; }
public int RowsPerPage { get; private set; }
public int? PreviousPage { get; private set; }
public int? NextPage { get; private set; }
}
And then you can serialize the object like this
var serializedObj = InterceptSerialization(
obj,
objType,
(o, t) =>
{
return JsonSerializer.Serialize(o, t, new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve });
},
context: "the name you want");
Hope this will be of use to you.
have a look here:
How to rename JSON key
Its not done during serialization but with a string operation.
Not very nice (in my eyes) but at least a possibility.
Cheers Thomas

Show all validation errors at once for DataAnnotation and IValidatableObject

I can validate Data Annotation and IValidatableObject when these one are on simple objects. However, in scenario where an object has a property that has to be validated, things get wrong.
public class BaseClass
{
public IEnumerable<ValidationResult> Validate()
{
var results = new List<ValidationResult>();
var validationContext = new ValidationContext(this, null, null);
Validator.TryValidateObject(this, validationContext, results, true);
return results;
}
}
public class Class1 : BaseClass, IValidatableObject
{
public Class1()
{
Property1 = new Class2();
}
public Class2 Property1 { get; set; }
//[Required]
public string AString1 { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var e = new ValidationResult("Error from class1");
var s = Property1.Validate();
var r = new List<ValidationResult>(s) { e };
return r;
}
}
public class Class2 :BaseClass, IValidatableObject
{
public Class2()
{
Property2 = new Class3();
}
public Class3 Property2 { get; set; }
//[Required]
public string AString2 { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
yield return new ValidationResult("Error from class2");
}
}
public class Class3:BaseClass
{
//[Required]
public string AString3 { get; set; }
}
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
var s = new Class1();
var results = s.Validate();
Assert.AreEqual(5, results.Count());
}
}
This small code snippet return 2 errors. The two that are from the Validate method of IValidatableObject. This is fine. However, if I uncomment the three "Required" data annotation I should have 5 errors (2 from Validate method and 3 froms Data Annotation).
Why when I uncomment the three data annotation that I have only one error "The AString1 field is required." which is the first class data annotation?
How can I have the five errors to be returned?
I believe there is because of the code in DataAnotations.Validator.GetObjectValidationErrors that kicks out after the first property error, short-circuiting the rest of validation rules. You can get around this by doing all validations inside Validate() method.

ASP MVC unobrusive validation of complex properties

I have next (simplified) view model:
public class RegisterModel
{
public string UserName { get; set; }
[MustExistIf("SomeProperty", "some value", "SomeOtherProperty", ErrorMessage = "You have to select something")]
public string LastName { get; set; }
public AddressModel Address { get; set; }
}
public class AddressModel
{
public string Street { get; set; }
public string House { get; set; }
}
and I have custom validator
public class MustExistIfAttribute : ValidationAttribute, IClientValidatable
{
private string _masterName { get; set; }
private object _masterValue { get; set; }
private string _dependantName { get; set; }
public MustExistIfAttribute(string masterName, object masterValue, string dependantName)
{
this._masterName = masterName;
this._masterValue = masterValue;
this._dependantName = dependantName;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// get value of master property
var masValue = _getValue(validationContext.ObjectInstance, _masterName);
// get value of property whch depends on master property
var depValue = _getValue(validationContext.ObjectInstance, _dependantName);
if (masValue.Equals(_masterValue)) // if value in request is equal to value in specified in data annotation
{
if (depValue == null) // if dependant value does not exist
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
public override bool IsValid(object value)
{
return base.IsValid(value);
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var modelClientValidationRule = new ModelClientValidationRule
{
ValidationType = "mustexistif",
ErrorMessage = FormatErrorMessage(metadata.DisplayName)
};
modelClientValidationRule.ValidationParameters.Add("mastername", this._masterName);
modelClientValidationRule.ValidationParameters.Add("mastervalue", this._masterValue);
modelClientValidationRule.ValidationParameters.Add("dependantname", this._dependantName);
yield return modelClientValidationRule;
}
private static object _getValue(object objectInstance, string propertyName)
{
...
}
}
I have next javascript (please neglect returning false in mustexitif method - it's just for test purposes)
(function () {
jQuery.validator.addMethod('mustexistif', function (value, element, params) {
var masterName = params['mastername'];
var masterValue = params['mastervalue'];
var dependantName = params['dependantname'];
return false;
});
var setValidationValues = function (options, ruleName, value) {
options.rules[ruleName] = value;
if (options.message) {
options.messages[ruleName] = options.message;
}
};
var $Unob = $.validator.unobtrusive;
$Unob.adapters.add("mustexistif", ["mastername", "mastervalue", "dependantname"], function (options) {
var value = {
mastername: options.params.mastername,
mastervalue: options.params.mastervalue,
dependantname: options.params.dependantname
};
setValidationValues(options, "mustexistif", value);
});
})();
It works as expected when I decorate LastName property of RegisterModel class with MustExistIf annotation (like in provided code).
But what I really want is to decorate complex Address property of RegisterModel with MustExistIf annotation. Problem is that when I do that no unobrusive adapter gets registered (javascript doing that IS NOT triggered).
So, there is difference when I decoreate simple and complex properties. My solution does not allow me to decorate properties of Address class (FYI, I tried that and then also validation is working fine). Is there a way to accomplish what I intended? Am I missing something? Woud solution be to validate on model level? But then is it possible to do client side validation?
Maybe you can use Remote Validation.
http://msdn.microsoft.com/en-us/library/gg508808%28v=vs.98%29.aspx

Categories