Custom metadata in validation messages
I have implemented a custom metadata provider extending AssociatedMetaDataProvider. The initial purpose of the class was to take a property name and turn it into Pascal cased words.
I have a new requirement to add a visual indicator (an '*' character) for the labels for all required fields in the site. I can make this change, by adding the following code to the custom metadata provider:
PropertyInfo property = containerType.GetProperty(propertyName);
开发者_如何学JAVA if (property.GetCustomAttributes(typeof(RequiredAttribute), true).Any())
metadata.DisplayName = "* " + metadata.DisplayName;
This code "works" in that it adds the '*' character to all of the labels. However, it has a side effect of also adding the '*' character to all of the validation messages (such as those used by Html.ValidationMessageFor(x => x.FirstName)
. In this case, I would like the label to be '* First Name' and the error message to be 'The First Name field is required.' rather than 'The * First Name Field is required.'.
Is there a way to provide the add the '*' character as part of the metadata for labels without affecting the property name used in validation messages? Alternatively, is there a way to provide custom metadata when validation messages are being constructed?
After spending some more time thinking about the problem, I managed to come up with something that seems to work. I don't really love the solution, however:
foreach (ValidationAttribute validationAttribute in attributes.Where(x => x is ValidationAttribute))
{
if (String.IsNullOrEmpty(validationAttribute.ErrorMessage))
{
validationAttribute.ErrorMessage = validationAttribute.FormatErrorMessage(formattedPropertyName);
}
}
This code runs at the same time as the code in the question (as part of the CreateMetadata method in the custom metadata provider). It works in all of the cases I've tested, but it's not really providing the metadata to the validation provider so much as formatting the error string using a Pascal cased version of the property name. This works with the way that most of the normal validation messages are setup. The fact that it is explicitly setting the error message on the validation attributes doesn't seem correct.
I don't known that this is would work as a global solution, however. This solution "works", but I hope that a better solution is available.
I'm on my phone at the moment, so don't have the benefit of reflector or an IDE, however I don't think you can do this without changing the validators, because their behaviour is to use the format string of the error message, passing in the same display name that you have modified here. I could be wrong though (i do like your approach here).
I have done what you've done, however, by writing custom editor templates, which also puts the control of the html in the hands of the web developer. using the metadata approach you can't give the asterisk, say, its own class for colouring purposes.
Even better with using templates, if using razor, you can use a layout template so that all field types can use a standard label/input approach.
Using Html.LabelFor, followed by an optional block if the current model is required offers a lot of flexibility I think, and I would prefer that over the metadata solution since it also doesn't interfere with the validation messages.
Like I say, on my initial doubt, though,I could be wrong :)
I know it's a 7 years question but I've been through the same problem for a while and in my case, the solution provided here don't work.
Well, it works but all my translated messages will be gone.
Here's what I've done for ASP.NET MVC Core 2:
public class CustomDisplayMetadataProvider : IDisplayMetadataProvider
{
public void CreateDisplayMetadata(DisplayMetadataProviderContext context)
{
if(context.Attributes.OfType<RequiredAttribute>().FirstOrDefault()?.GetType() == typeof(RequiredAttribute))
{
var displayName = context.Attributes.OfType<DisplayNameAttribute>().FirstOrDefault()?.DisplayName ?? context.Key.Name;
context.DisplayMetadata.DisplayName = () =>
{
if(new System.Diagnostics.StackTrace(0).GetFrames().Any(f => f.GetMethod().Name == "Validate"))
{
return displayName;
}
else
{
return displayName + " *";
}
};
}
}
}
The idea is to verify if DisplayName was called by Validate method so that way we don't mess with the error message and custom message providers still work.
It's not an ideal solution either but is working so far.
精彩评论