How to use FluentValidation with MetadataTypeAttribute? - c#

I'm developing ASP.NET MVC appliation. I've found Fluent Validation great validation tool and it works, but with my current architecture it has one drawback. The validator does not care about Metadata. I'm using Metadata on seperate class for clarity.
Model
[MetadataType(typeof(DocumentEditMetadata))]
[Validator(typeof(DocumentValidator))]
public class DocumentEditModel
{
public string DocumentNumber { get; set; }
(etc...)
}
Metadata Model
public class DocumentEditMetadata
{
[Required]
[StringLength(50)]
[Display(ResourceType = typeof(Label), Name = "DocumentNumber")]
public string DocumentNumber { get; set; }
(etc...)
}
Can anyone point a solution? I need data annotations for localization of labels (hence the DisplayAttribute).

Think you need to write your own Display name resolver for fluent validation (guess this should be placed in your global.asax).
Caution
This solution is only trying to resolve the display name.
Your other "validation" attributes (Required, StringLength) should no more be used, as you will manage that with FluentValidation.
ValidatorOptions.DisplayNameResolver = (type, memberInfo, expression) =>
{
//this will get in this case, "DocumentNumber", the property name.
//If we don't find anything in metadata / resource, that what will be displayed in the error message.
var displayName = memberInfo.Name;
//we try to find a corresponding Metadata type
var metadataType = type.GetCustomAttribute<MetadataTypeAttribute>();
if (metadataType != null)
{
var metadata = metadataType.MetadataClassType;
//we try to find a corresponding property in the metadata type
var correspondingProperty = metadata.GetProperty(memberInfo.Name);
if (correspondingProperty != null)
{
//we try to find a display attribute for the property in the metadata type
var displayAttribute = correspondingProperty.GetCustomAttribute<DisplayAttribute>();
if (displayAttribute != null)
{
//finally we got it, try to resolve the name !
displayName = displayAttribute.GetName();
}
}
}
return displayName ;
};
Personal point of view
By the way, if you just use Metadata classes for clarity, don't use them !
It may be a solution if you have no choice (when entity classes are generated from an edmx and you really want to manage the display names this way), but I would really avoid them if it's not necessary.

public class CreateHireViewModel
{
[Display(Name = nameof(CreateHireViewModel.Title), ResourceType = typeof(Resource.HireResource.Hire))]
public string Title { get; set; }
}
public class CreateHireViewModelValidator : AbstractValidator<CreateHireViewModel>
{
public CreateHireViewModelValidator(IStringLocalizer<Resource.HireResource.Hire> l)
{
RuleFor(x => x.Title).NotEmpty().WithName(l[nameof(CreateHireViewModel.Title)]);
RuleFor(x => x.Title).Length(3, 50).WithName(l[nameof(CreateHireViewModel.Title)]);
}
}

Related

Add custom attribute to a class generated by Entity Framework

I am trying to use a custom attribute on a Entity class generated automatically by the Entity Framework.
The problem is how to add an property attribute on an existing field?
Here the point where I am right now:
// the custom attribute class
public class MyCustomAttribute : Attribute
{
public String Key { get; set; }
}
// Entity Framework class generated automatically
public partial class EntityClass
{
public String Existent { get; set; }
//...
}
// set a metadata class for my entity
[MetadataType(typeof(EntityClassMetaData))]
public partial class EntityClass
{
// if I add a new property to the entity, it works. This attribute will be read
[MyCustomAttribute(Key = "KeyOne" )]
public int newProp { get; set; }
}
public class EntityClassMetaData
{
// adding the custom attribute to the existing property
[MyCustomAttribute(Key = "keyMeta") ]
public String Existent { get; set; }
}
Running this test:
[TestMethod]
public void test1()
{
foreach (var prop in typeof(EntityClass).GetProperties())
{
var att = prop.GetCustomAttribute<MyCustomAttribute>();
if (att != null)
{
Console.WriteLine($"Found {att.Key}");
}
}
}
will produce:
Found KeyOne
Or the Metadata class store the attribute in a different way or only works for data annotations.
I am stuck here, how can I set and read custom attributes of the generated class without having to edit the generated file?
I came across this same problem today. I figured EF magic would do the trick and map the attribute across to each model property. Turns out it does, but only for EF data annotations and I couldn't find an answered solution to pull out custom attributes so made this function. Hope it helps dude.
private object[] GetMetadataCustomAttributes(Type T, string propName)
{
if (Attribute.IsDefined(T, typeof(MetadataTypeAttribute)))
{
var metadataClassType =
(T.GetCustomAttributes(typeof(MetadataTypeAttribute), true).FirstOrDefault() as
MetadataTypeAttribute).MetadataClassType;
var metaDataClassProperty = metadataClassType.GetProperty(propName);
if (metaDataClassProperty != null)
{
return metaDataClassProperty.GetCustomAttributes(true);
}
}
return null;
}
I believe if you want to set an attribute in the metadata class, you have to use this syntax:
public class EntityClassMetaData
{
// adding the custom attribute to the existing property
[MyCustomAttribute(Key = "keyMeta") ]
public String Existent;
}
You must not have { get; set; } on your pre-existing property - just the property with the correct name and datatype.

Adding validations on class fields in WCF

I have a class Customers. I want to put some validations on it.
e.g. CustGuidId is not Guid.Empty, CustName is NOT NULL (Required).
public class Customer
{
public int CustId;
public string CustName;
public Guid CustGuid;
public Guid[] OrderGuids;
}
I have such collection of customers. So I have ended up adding code like this, which makes it look ugly.
public class BatchError
{
public int Index;
public string ErrorCode;
public string ErrorMessage;
}
public void GenerateValidationErrors(List<Customer> customers, out List<BatchError> batchErrors)
{
int rowNum = 0;
batchErrors = new List<BatchError>(customers.Count);
foreach (var customer in customers)
{
rowNum ++;
Guid customerGuidParsed;
if(!Guid.TryParse(customer.CustGuid.ToString(), out customerGuidParsed))
{
batchErrors.Add(new BatchError { Index = rowNum, ErrorCode = "CustomerGuidcannotBeNull", ErrorMessage = "Customer guid cannot be null." });
}
if (string.IsNullOrEmpty(customer.CustName))
{
batchErrors.Add(new BatchError { Index = rowNum, ErrorCode = "CustomerNamecannotBeEmpty", ErrorMessage = "Customer Name cannot be empty." });
}
}
}
Can we write separate validator classes, like GuidValidator, StringValidator.
and Create array of delegates & chain their invokes ?
(Customer c) => new GuidValidator(c.CustGuid.toString()),
(Customer c) => new StringValidator(c.CustName.toString())
But what design pattern would be best suitable for this scenario?
Is there any other way to add validations in WCF?
There are many ways to do the validation. I prefer to validate DataContract itself before any action.
It can also be done in many like :
DatamemberAttribute has many properties. One of them is
IsRequired,it controls the minOccurs attribute for the schema
element. The default value is false. You can use it like:
[DataContract(Name ="Place", Namespace ="")]
public class DataContractExample
{
[DataMember(IsRequired=true)]
public string DataMemberExample;
}
For more information refer: DataMemberAttribute Class on MSDN.
Easiest way is to validate property like:
[DataContract]
public class Customer
{
[DataMember]
public string CustName
{
get
{
return this._custName;
}
set
{
if(string.IsNullOrEmpty(value))
throw new MyValidationException();
else
this._custName=value;
}
}
}
Another way can be to use Microsoft Enterprise Library. In order to enable validation of the properties of a request message, you only need to add a [ValidationBehavior] attribute to your service interface, just next (or before) the [ServiceContract], and a [FaultContract(typeof(ValidationFault))] on the method declaration. The ValidationBehaviorAttribute and ValidationFault classes are defined in the Microsoft.Practices.EnterpriseLibrary.Validation.Integration.WCF assembly and are part of the Validation Application Block of the Enterprise Library 4.1, more specifically, of the WCF integration module. See full implementation in detail at: http://weblogs.asp.net/ricardoperes/validation-of-wcf-requests-with-the-enterprise-library-validation-block
Finally one more solution cane be to use WCF Data Annotations from http://wcfdataannotations.codeplex.com/. Using this you can use validations like:
[DataMember]
[Required]
[StringLength(500, MinimumLength = 5)]
public string Description{ get; set; }
Choose which ever suite your requirements. Cheers.

How to handle custom Properties in AutoMapper

I've got a ViewModel that takes some Model data and slightly alters it.
The way I'm doing it "works" since I just pass the DomainModel to the constructor for the ViewModel, but since I'm using AutoMapper on some of my one-to-one ViewModels, I thought I'd try and learn how to do the mapping across all ViewModels.
Here's an example of a ViewModel that does a little extra.
public class UsersDetailsViewModel
{
public string UserName { get; set; }
public string Email { get; set; }
public string Website { get; set; }
public int ID { get; set; }
public List<OpenID> OpenIds { get; set; }
public string UserAge { get; set; }
public string About { get; set; }
public string Slug { get; set; }
public System.DateTime LastSeen { get; set; }
public string Region { get; set; }
public string MemberSince { get; set; }
public string Reputation { get; set; }
public bool IsUserMatch { get; set; }
private readonly MarkdownDeep.Markdown _markdown;
public UsersDetailsViewModel(Domain.User user)
{
AuthUserData currentuser = AuthenticationHelper.RetrieveAuthUser;
_markdown.NoFollowLinks = true;
_markdown.SafeMode = true;
_markdown.ExtraMode = false;
_markdown.MarkdownInHtml = true;
// We want to ensure that the user has a username, even if they
// haven't set one yet. What this does is check to see if the
// user.UserName field is blank, and if it is, it will set the
// username to "UserNNNN" where NNNN is the user ID number.
_UserName = (user.UserName != null) ? user.UserName : "User" + user.ID.ToString;
// Nothing fancy going on here, we're just re-passing the object from
// the database to the View. No data manipulation!
_Email = user.Email;
_Website = user.WebSite;
_ID = user.ID;
// Get's a list of all of the user's OpenID's
_OpenIds = user.OpenIDs.ToList;
// Converts the users birthdate to an age representation
_UserAge = user.BirthDate.ToAge;
//IE: 29
// Because some people can be real ass holes and try to submit bad
// data (scripts and shitè) we have to modify the "About" content in
// order to sanitize it. At the same time, we transform the Markdown
// into valid HTML. The raw input is stored without sanitization in
// the database. this could mean Javascript injection, etc, so the
// output ALWAYS needs to be sanitized.
// This method below was used in conjunction with MarkDownSharp
// _About = Trim(Utilities.HtmlSanitizer.Sanitize(Markdown.Transform(user.About)))
_About = _markdown.Transform(user.About);
// Removes spaces from Usernames in order to properly display the
// username in the address bar
_Slug = Strings.Replace(user.UserName, " ", "-");
// Returns a boolean result if the current logged in user matches the
// details view of tBhe user in question. This is done so that we can
// show the edit button to logged in users.
_IsUserMatch = (currentuser.ID == user.ID);
// Grabs the users registration data and formats it to a <time> tag
// for use with the timeago jQuery plugin
_MemberSince = user.MemberSince;
// Grabs the users last activity and formats it to a <time> tag
// for use with the timeago jQuery plugin
_LastSeen = user.ActivityLogs.Reverse.FirstOrDefault.ActivityDate;
// Formats the users reputation to a comma Deliminated number
// IE: 19,000 or 123k
_Reputation = user.Reputation.ToShortHandNumber;
// Get the name of the users Default Region.
_Region = user.Region.Name.FirstOrDefault;
}
}
And here's how I currently utilize the above ViewModel
public ActionResult Details(int id)
{
User user = _userService.GetUserByID(id);
if (user != null) {
Domain.ViewModels.UsersDetailsViewModel userviewmodel = new Domain.ViewModels.UsersDetailsViewModel(user);
return View(userviewmodel);
} else {
// Because of RESTful URL's, some people will want to "hunt around"
// for other users by entering numbers into the address. We need to
// gracefully redirect them to a not found page if the user doesn't
// exist.
throw new ResourceNotFoundException();
}
}
How can I use (or should I use) AutoMapper to map my DomainModel to my ViewModel while doing the custom processing you see above?
On automapper where you create the Map you can specify additional processes for specific members of the destination type.
So where your default map would be
Mapper.Map<Domain.User, UsersDetailsViewModel>();
there is a fluent syntax to define the more complicated mappings:
Mapper.Map<Domain.User, UsersDetailsViewModel>()
.ForMember(vm=>vm.UserName, m=>m.MapFrom(u=>(u.UserName != null)
? u.UserName
: "User" + u.ID.ToString()));
Here the ForMember takes two Arguments the first defines the property that you are mapping to. The second provides a means of defining the mapping. For an example I have copped out and shown one of the easy mappings.
If you require a more difficult mapping, (such as your CurrentUser mapping) you can create a class that implements the IResolver interface, incorporate your mapping logic in that new clases and then add that into the mapping.
Mapper.Map<Domain.User, UsersDetailsViewModel>()
.ForMember(vm=>vm.IsUserMatch, m=>m.ResolveUsing<MatchingUserResolver>()));
when Mapper comes to do the mapping it will invoke your custom resolver.
Once you discover the syntax of the .ForMember method everything else kind of slots into place.
Custom mapping can be defined in global.ascx (at startup) by following codes :
AutoMapper.Mapper.CreateMap<Domain.User, UsersDetailsViewModel>()
.ForMember(o => o.Email, b => b.MapFrom(z => z.Email))
.ForMember(o => o.UserName , b => b.MapFrom(user => (user.UserName != null) ? user.UserName : "User" + user.ID.ToString));
you can do some initialization via BeforeMap () method. But you may need to do some changes in your viewmodel.
I think the syntax has slightly changed in 2019 (ASP.NET Core 2.2), this method is now handled with the MapperConfiguration and the static methods are no more available.
But I agree with #KJSR, this post is still really useful :-)
private Mapper UserMapper= new Mapper(new MapperConfiguration(cfg => (cfg.CreateMap<Domain.User, UsersDetailsViewModel>())
.ForMember(x=>x.Email, y=>y.MapFrom(z=>z.Email))
.ForMember(x => x.UserName , y => y.MapFrom(user => (user.UserName != null) ? user.UserName : "User" + user.ID.ToString))));

Attributes of properties in MetadataType are ignored by EntLib Validation

It's an EntLib-Validator-issue again. I'm playing with EntLib 5.0 in C# and .Net 4.0 on XP pro.
I have some business objects (partial classes) generated by T4 templates. So I decided to put their validation attributes in buddy-classes by using MetadataTypeAttribute as definitely recommended by the documentation of entLib 5.0 (msdn).
But the Validator object I get from the ValidatorFactory doesn't know about the validation attributes, defined in the metadata-class.
The business object is defined like this:
[MetadataType(typeof(PatientMetadata))]
public partial class Patient
{
private string _Name;
private int _DiagnosisCount;
public int DiagnosisCount
{
get
{
return _DiagnosisCount;
}
set
{
if (value != _DiagnosisCount)
{
_DiagnosisCount = value;
}
}
}
public string Name
{
get
{
return _Name;
}
set
{
if (value != _Name)
{
_Name = value;
}
}
}
}
And the metadata class like this, according to documentation:
public class PatientMetadata
{
[RangeValidator(4)]
public int DiagnosisCount { get; set; }
[StringLengthValidator(64, ErrorMessage = "Name must not exceed 64 chars.")]
public string Name { get; set; }
}
If I know try to do validation this way:
var factory = ValidationFactory.DefaultCompositeValidatorFactory;
var validator = factory.CreateValidator<Patient>();
...then watching into validator (during debugging) already says, that it's just an AndCompositeValidator without any children validators.
Again, if I put the validation attributes right in the Patient class, it works perfectly.
By now, I have no real idea, what I'm missing here, since I think doing everything according to the docs.
Thanks in advance to you guys!
The property names of the metadata class must match the property names of the main class.
In your case your metadata class should look like:
public class PatientMetadata
{
[RangeValidator(0, RangeBoundaryType.Inclusive, 10, RangeBoundaryType.Ignore)]
public int DiagnosisCount { get; set; }
[StringLengthValidator(6, ErrorMessage = "Name must not exceed 6 chars.")]
public string Name { get; set; }
}
Also, the docs indicate the accepted approach is to declare all return types as object. However, the docs also talk about using properties but in their example use fields so take it under advisement. :)

DisplayName attribute from Resources?

I have a localized application, and I am wondering if it is possible to have the DisplayName for a certain model property set from a Resource.
I'd like to do something like this:
public class MyModel {
[Required]
[DisplayName(Resources.Resources.labelForName)]
public string name{ get; set; }
}
But I can't to it, as the compiler says: "An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type" :(
Are there any workarounds? I am outputting labels manually, but I need these for the validator output!
If you use MVC 3 and .NET 4, you can use the new Display attribute in the System.ComponentModel.DataAnnotations namespace. This attribute replaces the DisplayName attribute and provides much more functionality, including localization support.
In your case, you would use it like this:
public class MyModel
{
[Required]
[Display(Name = "labelForName", ResourceType = typeof(Resources.Resources))]
public string name{ get; set; }
}
As a side note, this attribute will not work with resources inside App_GlobalResources or App_LocalResources. This has to do with the custom tool (GlobalResourceProxyGenerator) these resources use. Instead make sure your resource file is set to 'Embedded resource' and use the 'ResXFileCodeGenerator' custom tool.
(As a further side note, you shouldn't be using App_GlobalResources or App_LocalResources with MVC. You can read more about why this is the case here)
How about writing a custom attribute:
public class LocalizedDisplayNameAttribute: DisplayNameAttribute
{
public LocalizedDisplayNameAttribute(string resourceId)
: base(GetMessageFromResource(resourceId))
{ }
private static string GetMessageFromResource(string resourceId)
{
// TODO: Return the string from the resource file
}
}
which could be used like this:
public class MyModel
{
[Required]
[LocalizedDisplayName("labelForName")]
public string Name { get; set; }
}
If you open your resource file and change the access modifier to public or internal it will generate a class from your resource file which allows you to create strongly typed resource references.
Which means you can do something like this instead (using C# 6.0).
Then you dont have to remember if firstname was lowercased or camelcased. And you can see if other properties use the same resource value with a find all references.
[Display(Name = nameof(PropertyNames.FirstName), ResourceType = typeof(PropertyNames))]
public string FirstName { get; set; }
Update:
I know it's too late but I'd like to add this update:
I'm using the Conventional Model Metadata Provider which presented by Phil Haacked it's more powerful and easy to apply take look at it :
ConventionalModelMetadataProvider
Old Answer
Here if you wanna support many types of resources:
public class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
private readonly PropertyInfo nameProperty;
public LocalizedDisplayNameAttribute(string displayNameKey, Type resourceType = null)
: base(displayNameKey)
{
if (resourceType != null)
{
nameProperty = resourceType.GetProperty(base.DisplayName,
BindingFlags.Static | BindingFlags.Public);
}
}
public override string DisplayName
{
get
{
if (nameProperty == null)
{
return base.DisplayName;
}
return (string)nameProperty.GetValue(nameProperty.DeclaringType, null);
}
}
}
Then use it like this:
[LocalizedDisplayName("Password", typeof(Res.Model.Shared.ModelProperties))]
public string Password { get; set; }
For the full localization tutorial see this page.
I got Gunders answer working with my App_GlobalResources by choosing the resources properties and switch "Custom Tool" to "PublicResXFileCodeGenerator" and build action to "Embedded Resource".
Please observe Gunders comment below.
Works like a charm :)
public class Person
{
// Before C# 6.0
[Display(Name = "Age", ResourceType = typeof(Testi18n.Resource))]
public string Age { get; set; }
// After C# 6.0
// [Display(Name = nameof(Resource.Age), ResourceType = typeof(Resource))]
}
Define ResourceType of the attribute so it looks for a resource
Define Name of the attribute which is used for the key of resource, after C# 6.0, you can use nameof for strong typed support instead of hard coding the key.
Set the culture of current thread in the controller.
Resource.Culture = CultureInfo.GetCultureInfo("zh-CN");
Set the accessibility of the resource to public
Display the label in cshtml like this
#Html.DisplayNameFor(model => model.Age)

Categories

Resources