MVC LabelFor to replace underscores with CamelCase - c#

my DB model has the column name with "_" in the field name.
is there a way to dislay these values in Camel Case when displaying in the form? like
"BusinessName"
i am using asp.net MVC3 with Razor
#Html.LabelFor(model => model.business_name)

Could you not just use the DisplayAttribute?

You need a new metadataprovider which can inherit from the default one like this:
using System;
using System.Web.Mvc;
using System.Collections.Generic;
public class MyMetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
if (metadata.DisplayName == null)
metadata.DisplayName = GetDisplayNameFromDBName(propertyName);
return metadata;
}
private string GetDisplayNameFromDBName(string propertyName)
{
return ...;
}
}
Register it in global.asax like this:
ModelMetadataProviders.Current = new MyMetadataProvider();
You just need to provide the implementation of GetDisplayNameFromDBName to provide the correct display name given the property name

Why don't you just use #Html.Label("Business name")?

Related

Get localized display name attribute from a class property which use a resource file

Introduction
I have a class, which has properties localized through data annotations by a resource file, like this:
[Display(Name = nameof(ResxFile.SomeProperty), ResourceType = typeof(ResxFile)]
public string SomeProperty { get; set; }
Where ResxFile is a .resx file, and I'm using Name = nameof(ResxFile.SomeProperty) to get the name property of the resource file row (to make it strongly typed), and ResourceType = typeof(ResxFile) to indicate which is the resource file to use.
In my ResxFile, for the previous example, I would have something like:
Name | Value
------------------------------------------
SomeProperty | Some property localized
And in this way, for example, I can bind my class to a grid, and the column names will be localized according to the content of the resource file.
Question
I'm working with a kind of dynamic mapping, where I use the property names of my classes, and in general I get them with something like this: string propertyName = typeof(MyClassName).GetProperty(myPropertyName).Name
In this case, what I need, is the localized name assigned to that property, according to the resource file. To be more clear: string localizedPropertyName = typeof(MyClassName).GetProperty(myPropertyName).SomeMagic(); where localizedPropertyName would be "Some property localized"
I've been looking in CustomAttributes, but I only could get display name attributes, and some types, and that lands me in another job, which is invoke the resource file to get the value of a name.
I'm using .Net Framework 4.7.
Thanks in advance!
Finally, I found a solution on my own.
The problem
Then, letting a clear context, what we have is just a class (from which we can extract its type), and a PropertyName on a string, and what we want is the the localized DisplayName of that property of that class, according to a Resource File assigned on its decoration.
Let's suppose some elements to start. We have the class MyClass, which has a property called MyProperty, and which will be localized with the resource file MyResx:
public class MyClass
{
private string myProperty;
[Display(Name = nameof(MyResx.MyProperty), ResourceType = typeof(MyResx))]
public string MyProperty
{
get { return myProperty; }
set { myProperty = value; }
}
}
The resource file MyResx, has some localized string for the name MyProperty, and will look like this:
The solution
// We start with the class type, and the property name on a string
Type classType = typeof(MyClass);
string nameOfTheProperty = "MyProperty";
/* Now we get the MemberInfo of our property, wich allow us to get the
* property metadata, where is the information we are looking for. */
MemberInfo propertyMetadata = classType.GetProperty(nameOfTheProperty);
/* The decorations we used, are "Custom Attributes". Now we get those
* attributes from our property metadata: */
var customAttributes = CustomAttributeData.GetCustomAttributes(propertyMetadata).FirstOrDefault();
/* If we pay attention to our decoration, we defined "Name = nameof(MyResx.MyProperty)"
* and "ResourceType = typeof(MyResx))", so, what we are looking for from our custom
* attribures are those members, Name and ResourceType: */
var customAttributeName = customAttributes.NamedArguments.FirstOrDefault(n => n.MemberName == "Name");
var name = (customAttributeName != null) ? (string)customAttributeName.TypedValue.Value : null;
var customAttributeResourceType = customAttributes.NamedArguments.FirstOrDefault(n => n.MemberName == "ResourceType");
var resourceType = (customAttributeResourceType != null) ? (Type)customAttributeResourceType.TypedValue.Value : null;
/* Now, having the resource file from the decoration, we just create an instance to
* use it: */
var decorationResx = new ComponentResourceManager(resourceType);
// And finally, from our resource file, we get our localized display name
string localizedAttribute = decorationResx.GetString(name);
Extra
I got a lot of important information from the Microsoft reference about the NamedArguments, here: https://learn.microsoft.com/en-us/dotnet/api/system.reflection.customattributedata.namedarguments?view=netcore-3.1
Hopefully this helps you as in the past I have used this method to translate keys in a database. This does not cover the pulling out data from the resource file, but you can either declare [Display] attribute on a property and use the full name as the key or give a static string as the key to use later in the meta data provider.
Add your own meta data providor
public class MyMetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes,
Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
var metadata = new ModelMetadata(this, containerType, modelAccessor, modelType, propertyName);
//Do what ever you want here to translate either by the property name or the display attribute key
if (propertyName != null)
{
var displayAttribute = attributes.OfType<DisplayAttribute>().FirstOrDefault();
if (displayAttribute != null)
{
//Translate using the key you provided before however you like
metadata.DisplayName = TranslateFunction(displayAttribute.Name);
}
}
return metadata;
}
}
add the translation key to the prop
[Display(Name = "ResourceKey")]
public string Something { get; set; }
Add this to application start up
protected void Application_Start(object sender, EventArgs e)
{
ModelMetadataProviders.Current = new MyMetadataProvider();
}

ModelMetadata TemplateHint always null

I am trying to create a custom ModelMetadataProvider to provide unobtrusive attributes for the JQuery UI Autocomplete widget.
I have a custom attribute that looks like this:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class AutocompleteAttribute : Attribute, IMetadataAware
{
public void OnMetadataCreated(ModelMetadata metadata)
{
metadata.TemplateHint = "Autocomplete";
}
}
and an editor template that looks like this:
#{
var attributes = new RouteValueDictionary
{
{"class", "text-box single-line"},
{"autocomplete", "off"},
{"data-autocomplete-url", "UrlPlaceholder" },
};
}
#Html.TextBox("", ViewContext.ViewData.TemplateInfo.FormattedModelValue, attributes)
I have a viewModel with a property of type string that includes the AutocompleteAttribute like this:
public class MyViewModel
{
[Autocomplete]
public string MyProperty { get; set; }
}
When I use this viewModel in my view I check the generated html and I am getting an <input> tag which has an attribute like this: data-autocomplete-url="UrlPlaceholder".
What I want to do next is to be able to specify the URL in my view that uses my viewModel like this:
#model MyViewModel
#{ ViewBag.Title = "Create item"; }
#Html.AutoCompleteUrlFor(p => p.MyProperty, UrlHelper.GenerateUrl(null, "Autocomplete", "Home", null, Html.RouteCollection, Html.ViewContext.RequestContext, true))
// Other stuff here...
<div>
#Html.ActionLink("Back to List", "Index")
</div>
My AutoCompleteForUrl helper just saves the generated URL in a dictionary, using the property name as a key.
Next I have created a custom ModelMetadataProvider and registered it in global.asax using this line of code ModelMetadataProviders.Current = new CustomModelMetadataProvider();.
What I want to do is to insert the URL to be used by the JQuery UI Autocomplete widget into the metadata.AdditionalValues dictionary to be consumed by the Autocomplete editor template.
My custom ModelMetadataProvider looks like this:
public class CustomModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(IEnumerable<System.Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
if (metadata.TemplateHint == "Autocomplete")
{
string url;
if(htmlHelpers.AutocompleteUrls.TryGetValue(metadata.propertyName, out url)
{
metadata.AdditionalValues["AutocompleteUrl"] = url;
}
}
return metadata;
}
}
and my updated editor template looks like this:
#{
object url;
if (!ViewContext.ViewData.ModelMetadata.TryGetValue("AutocompleteUrl", out url))
{
url = "";
}
var attributes = new RouteValueDictionary
{
{"class", "text-box single-line"},
{"autocomplete", "off"},
{"data-autocomplete-url", (string)url },
};
}
#Html.TextBox("", ViewContext.ViewData.TemplateInfo.FormattedModelValue, attributes)
The problem is, the TemplateHint property never equals "Autocomplete" in my custom model metadata provider so my logic to generate the URL never gets called. I would have thought that at this point the TemplateHint property would be set as I have called the base implementation of CreateMetadata of the DataAnnotationsModelMetadataProvider.
Here's what I can confirm:
The CustomModelMetadataProvider is correctly registered as it contains other code which is getting called.
The correct editor template is getting picked up as the Html that is generated contains an attribute called "data-autocomplete-url".
If I put a breakpoint in the Autocomplete template, Visual Studio goes to the debugger.
So can anyone shed any light on this for me please? What am I misunderstanding about the ModelMetadataProvider system?
After looking through the ASP.NET MVC 3 source code I have discovered that the reason for this is because the CreateMetadata method is called prior to the OnMetadataCreated method of any IMetadataAware attributes that are applied to the model.
I have found an alternative solution that allows me to do what I wanted.
First of all I updated my AutocompleteAttribute:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class AutocompleteAttribute : Attribute, IMetadataAware
{
public const string Key = "autocomplete-url";
internal static IDictionary<string, string> Urls { get; private set; }
static AutocompleteAttribute()
{
Urls = new Dictionary<string, string>();
}
public void OnMetadataCreated(ModelMetadata metadata)
{
metadata.TemplateHint = "Autocomplete";
string url;
if (Urls.TryGetValue(metadata.PropertyName, out url))
{
metadata.AdditionalValues[Key] = url;
Urls.Remove(metadata.PropertyName);
}
}
}
and my Html helper method for setting the url in my views looks like this:
public static IHtmlString AutocompleteUrlFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string url)
{
if (string.IsNullOrEmpty(url))
throw new ArgumentException("url");
var property = ModelMetadata.FromLambdaExpression(expression, html.ViewData).PropertyName;
AutocompleteAttribute.Urls[property] = url;
return MvcHtmlString.Empty;
}
And then all I have to do in my editor template is this:
#{
object url;
ViewData.ModelMetadata.AdditionalValues.TryGetValue(AutocompleteAttribute.Key, out url);
var attributes = new RouteValueDictionary
{
{"class", "text-box single-line"},
{"autocomplete", "off"},
{ "data-autocomplete-url", url },
};
}
#Html.TextBox("", ViewContext.ViewData.TemplateInfo.FormattedModelValue, attributes)

Application level number format asp.net mvc

In my application I have many properties like
[DisplayFormat(ApplyFormatInEditMode=false,ConvertEmptyStringToNull=false,DataFormatString="{0:0.00}")]
public decimal somedecimalvalue { get; set; }
Is there any way i can generalize this whenever a decimal property is created above format is applied to it
You can manually assign metadata for decimal properties in your models by creating custom DataAnnotationsModelMetadataProvider:
public class DecimalMetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
if (propertyName == null)
return metadata;
if (metadata.ModelType == typeof(decimal))
{
// Given DisplayFormat Attribute:
// if ApplyFormatInEditMode = true
// metadata.EditFormatString = "{0:0.00}";
// for DataFormatString
metadata.DisplayFormatString = "{0:0.00}";
// for ConvertEmptyStringToNull
metadata.ConvertEmptyStringToNull = false;
}
return metadata;
}
}
And then register this provider in Global.asax.cs in Application_Start() method:
ModelMetadataProviders.Current = new DecimalMetadataProvider();
Then you can remove DisplayFormat attribute from decimal properties. Note that this won't affect other properties and you can safely add other data annotations on your decimal properties.
Read more about MetaData class and its properties.
Happy coding! :)
Cant understand exactly what you wanted to know. describe some more
I think there is no direct feature to do that. How about having one Interface and implement the same for all your entities?
You have to check out using Model Templates.
If you want to customize the way in which the decimal is displayed throughout the application then you have to create a partial view named decimal.cshtml (if Razor) and store it in the Views/Shared/DisplayTemplates folder.
In the decimal.cshtml you can control the way in which the model decimal is being displayed.
Like that, to customize the way in which it is displayed for editing then you have to create another view with the same name and dump it into the Views/Shared/EditorTemplates folder.
The MVC framework takes care of the remaining things.
Ex.
Views/Shared/DisplayTemplates/decimal.cshtml
#model decimal
<p>
#Model.ToString("0.00")
</p>

MVC3 localization for default validation errors

When I put [Required] attribute on my ViewModel's property MVC3 automatically generate error messages like:
The Price field is required.
My site's single language is Russian, so I want to have localized error messages. I can localize field's name with [Display(Name = "blablabla")], but how can I localize the field is required part?
Update:
I know, that I can change an error message for concrete field by specifying it [Required(ErrorMessage = "blablabla")], is there a way I can change it in one place for all [Required] attributes, so I could use just [Required] without additional parameters, and it took the localized error message from some ressource/config/etc?
I've created an alternative solution where you don't have to use the attributes for the localization. I've created custom model/validation meta data providers.
All you need to do is to download my code and do the following in your global.asax:
var stringProvider = new ResourceStringProvider(Resources.LocalizedStrings.ResourceManager);
ModelMetadataProviders.Current = new LocalizedModelMetadataProvider(stringProvider);
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new LocalizedModelValidatorProvider(stringProvider));
(the ResourceStringProvider is my default implementation but it's easy to create an alternative that reads from XML files or a database)
You can read about it here:
http://blog.gauffin.org/2011/09/easy-model-and-validation-localization-in-asp-net-mvc3/
I'm going to release a nuget package as soon as I'm finished with my view localization and my alternative HTML helpers.
The Required attribute has properties that allow the message to be read from a resource string. See http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.requiredattribute.aspx for the details
Granted for this you should already have some Resource Manager which can return localized texts for string keys. My ResourceManager has static accessor for this (and registrations with Unity for DI), so there's no need to pass it.
In the global.asax:
ModelMetadataProviders.Current = new LocalizedModelMetadataProvider(); //field name localization
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new LocalizedModelValidatorProvider()); //validation message localization
And the implementation is this:
public class LocalizedModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType,
Func<object> modelAccessor, Type modelType, string propertyName)
{
var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
if (containerType == null || propertyName == null)
return metadata;
if (metadata.DisplayName == null)
metadata.DisplayName = ResourceManager.Current.GetLocalizedAttribute(containerType, propertyName);
if (metadata.Watermark == null)
metadata.Watermark = ResourceManager.Current.GetLocalizedAttribute(containerType, propertyName, "Watermark");
if (metadata.Description == null)
metadata.Description = ResourceManager.Current.GetLocalizedAttribute(containerType, propertyName, "Description");
if (metadata.NullDisplayText == null)
metadata.NullDisplayText = ResourceManager.Current.GetLocalizedAttribute(containerType, propertyName, "NullDisplayText");
if (metadata.ShortDisplayName == null)
metadata.ShortDisplayName = ResourceManager.Current.GetLocalizedAttribute(containerType, propertyName, "ShortDisplayName");
return metadata;
}
}
public class LocalizedModelValidatorProvider : DataAnnotationsModelValidatorProvider
{
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
foreach (var attribute in attributes.OfType<ValidationAttribute>())
attribute.ErrorMessage = ResourceManager.Current.GetValidationMessage(attribute);
return base.GetValidators(metadata, context, attributes);
}
}
Simply add/change the globalization tag in web.config:
<system.web>
<globalization uiCulture="your culture"/>

Extract Display name and description Attribute from within a HTML helper

I am building a custom HTML.LabelFor helper that looks like this :
public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> self, Expression<Func<TModel, TValue>> expression, Boolean showToolTip)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, self.ViewData);
...
}
To be able to get the proper name for the property I am using the following code :
metadata.DisplayName
And on the property of the ModelView class I got :
[DisplayName("Titel")]
The problem is that I also need a description. There is an Attribute called Display and that has Name and Description but I do not see how to extract this with the metadata variable in the above code?
Disclaimer: The following works only with ASP.NET MVC 3 (see the update at the bottom if you are using previous versions)
Assuming the following model:
public class MyViewModel
{
[Display(Description = "some description", Name = "some name")]
public string SomeProperty { get; set; }
}
And the following view:
<%= Html.LabelFor(x => x.SomeProperty, true) %>
Inside your custom helper you could fetch this information from the metadata:
public static MvcHtmlString LabelFor<TModel, TValue>(
this HtmlHelper<TModel> self,
Expression<Func<TModel, TValue>> expression,
bool showToolTip
)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, self.ViewData);
var description = metadata.Description; // will equal "some description"
var name = metadata.DisplayName; // will equal "some name"
// TODO: do something with the name and the description
...
}
Remark: Having [DisplayName("foo")] and [Display(Name = "bar")] on the same model property is redundant and the name used in the [Display] attribute has precedence in metadata.DisplayName.
UPDATE:
My previous answer won't work with ASP.NET MVC 2.0. There are a couples of properties that it is not possible to fill by default with DataAnnotations in .NET 3.5, and Description is one of them. To achieve this in ASP.NET MVC 2.0 you could use a custom model metadata provider:
public class DisplayMetaDataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(
IEnumerable<Attribute> attributes,
Type containerType,
Func<object> modelAccessor,
Type modelType,
string propertyName
)
{
var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
var displayAttribute = attributes.OfType<DisplayAttribute>().FirstOrDefault();
if (displayAttribute != null)
{
metadata.Description = displayAttribute.Description;
metadata.DisplayName = displayAttribute.Name;
}
return metadata;
}
}
which you would register in Application_Start:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
ModelMetadataProviders.Current = new DisplayMetaDataProvider();
}
and then the helper should work as expected:
public static MvcHtmlString LabelFor<TModel, TValue>(
this HtmlHelper<TModel> self,
Expression<Func<TModel, TValue>> expression,
bool showToolTip
)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, self.ViewData);
var description = metadata.Description; // will equal "some description"
var name = metadata.DisplayName; // will equal "some name"
// TODO: do something with the name and the description
...
}

Categories

Resources