Is it possible to access additional metadata info from a custom display or editor template? - asp.net-mvc-3

I am aware that in a custom display or editor template I can get metadata about the model via ViewData.ModelMetadata, which has properties that indicate whether certain metadata attributes have been defined for the property, such as IsRequired, DisplayName, and so on. But is there anyway I can access custom metadata I've added to the property via custom attributes?
For example, say in my view I have a property like so:
[UIHint("Whizbang")]
[SomeAttribute("foobar")]
public string LeftWhizbang { get; set; }
And I have a custom display template named Whizbang.cshtml with the following content:
#model string
Left Whizbang Value: #Model
What I'd like to do is be able to determine whether the property LeftWhizbang is decorated with the attribute SomeAttribute and, if so, I'd like to access the attribute's Message property (say), namely the value "foobar".
I'd like to be able to do something like this in my template:
#model string
Left Whizbang Value: #Model
#{
SomeAttributeAttribute attr = ViewData.ModelMetadata.GetAttributes(...);
if (attr != null)
{
<text>... and the value is #attr.Message</text>
}
}
Is this at all possible, or am I looking down a dead end?

Sure. First you'll need your attribute which implements IMetadataAware so that DataAnnotationsModelMetadataProvider knows about it
public class TooltipAttribute : Attribute, IMetadataAware {
public TooltipAttribute(string tooltip) {
this.Tooltip = tooltip;
}
public string Tooltip { get; set; }
public void OnMetadataCreated(ModelMetadata metadata) {
metadata.AdditionalValues["Tooltip"] = this.Tooltip;
}
}
You can then access the attribute by creating a helper method:
public static IHtmlString TooltipFor<TModel, TValue>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, TValue>> expression) {
var data = ModelMetadata.FromLambdaExpression<TModel, TValue>(expression, html.ViewData);
if (data.AdditionalValues.ContainsKey("Tooltip"))
return new HtmlString((string)data.AdditionalValues["Tooltip"]);
return new HtmlString("");
}

Related

ASP.NET Web API - how to pass unknown number of form-encoded POST values

The front-end of my application can send unknown number of POST values inside a form. Fro example in some cases there will be 3 values coming from certain textboxes, in some cases there will be 6 values coming from textboxes, dropdowns etc. The backend is ASP.NET Web API. I know that a simple .NET value can be passed in URI parameter to a "POST Action" using FromURI attribute and a complex type can be passed in body and fetched using FromBody attribute, in any POST Action. But in my case the number of form data values will NOT be constant rather variable and I can't use a pre-defined class to hold values using 'FromBody' attribute.
How can I tackle this situation?
You can use the FormDataCollection from the System.Net.Http.Formatting namespace.
public class ApiFormsController : ApiController
{
[HttpPost]
public IHttpActionResult PostForm(FormDataCollection form)
{
NameValueCollection items = form.ReadAsNameValueCollection();
foreach (string key in items.AllKeys)
{
string name = key;
string val = items[key];
}
return Ok();
}
}
Try to send this properties as list of properties. Make model something like this:
public class PostModel
{
public IEnumerable<PropertyModel> Properties { get; set; }
}
public class PropertyModel
{
public string Value { get; set; }
public string Source { get; set; }
// etc.
}
And action:
public IHttpActionResult Post(PostModel model)
{
//Omited
return Ok();
}

ASP.NET MVC enable custom validation attributes

I'm attempting to create my own model validation attributes for an ASP.NET MVC project. I've followed the advice from this question but cannot see how to get #Html.EditorFor() to recognise my custom attributes. Do I need to register my custom attribute classes in the web.config somewhere? A comment on this this answer seems to be asking the same thing.
FYI the reason I'm creating my own attributes is because I want to retrieve the field display names and validation messages from Sitecore and don't really want to go down the route of creating a class with a ton of static methods to represent each text property, which is what I'd have to do if I were to use
public class MyModel
{
[DisplayName("Some Property")]
[Required(ErrorMessageResourceName="SomeProperty_Required", ErrorMessageResourceType=typeof(MyResourceClass))]
public string SomeProperty{ get; set; }
}
public class MyResourceClass
{
public static string SomeProperty_Required
{
get { // extract field from sitecore item }
}
//for each new field validator, I would need to add an additional
//property to retrieve the corresponding validation message
}
This question has been answered here:
How to create custom validation attribute for MVC
In order to get your custom validator attribute to work, you need to register it. This can be done in Global.asax with the following code:
public void Application_Start()
{
System.Web.Mvc.DataAnnotationsModelValidatorProvider.RegisterAdapter(
typeof (MyNamespace.RequiredAttribute),
typeof (System.Web.Mvc.RequiredAttributeAdapter));
}
(If you're using WebActivator you can put the above code into a startup class in your App_Start folder.)
My custom attribute class looks like this:
public class RequiredAttribute : System.ComponentModel.DataAnnotations.RequiredAttribute
{
private string _propertyName;
public RequiredAttribute([CallerMemberName] string propertyName = null)
{
_propertyName = propertyName;
}
public string PropertyName
{
get { return _propertyName; }
}
private string GetErrorMessage()
{
// Get appropriate error message from Sitecore here.
// This could be of the form "Please specify the {0} field"
// where '{0}' gets replaced with the display name for the model field.
}
public override string FormatErrorMessage(string name)
{
//note that the display name for the field is passed to the 'name' argument
return string.Format(GetErrorMessage(), name);
}
}

Is it possible to force the currency for an MVC3 field with DataType as DataType.Currency

I'm writing an MVC3 application that reads in a bunch of monetary data from a database. The issue I have is that these amounts are all in different currencies.
If I set the type of a field like this:
[DataType(DataType.Currency)]
public Amount{ get; set;}
I get the decimal places and a currency symbol, which looks nice, but it defaults to the user's local currency. A US user sees $423.29 whereas a GB user sees £423.29. I can override the currency by using a <globalization culture="{something}"> in the Web.config, but this sets all currency fields globally.
What would be the easiest way of marking up a field so that it renders with the correct decimal places and currency symbol?
In an ideal world, I'd like to be able to do something like this (for USD):
[DataType(DataType.Currency, culture="en-us")]
public Amount{ get; set; }
and have that always render as $439.38, but that's not possible with the built-in annotations.
The way I would do this is to create a custom attribute that extends the DataType attribute and a custom html helper. It's not necessarily the easiest way of doing it but it would save time in the future.
EDIT
Incorporated CultureInfo.CreateSpecificCulture(cultureName) instead of a switch
Custom Attribute
public class CurrencyDisplayAttribute : DataTypeAttribute
{
public string Culture { get; set; }
public CurrencyDisplayAttribute(string culture)
: base(DataType.Currency)
{
Culture = culture;
}
}
Html Helper
public static class Helpers
{
public static IHtmlString CurrencyDisplayFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression)
{
double value = double.Parse(expression.Compile().Invoke(helper.ViewData.Model).ToString());
var metadata = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
var prop = typeof (TModel).GetProperty(metadata.PropertyName);
var attribute = prop.GetCustomAttribute(typeof (CurrencyDisplayAttribute)) as CurrencyDisplayAttribute;
// this should be whatever html element you want to create
TagBuilder tagBuilder = new TagBuilder("span");
tagBuilder.SetInnerText(value.ToString("c", CultureInfo.CreateSpecificCulture(attribute.Culture));
return MvcHtmlString.Create(tagBuilder.ToString());
}
}
You can use the attribute in your model
[CurrencyDisplay("en-us")]
public double Amount { get; set; }
Then in your view you can use the helper by
#Html.CurrencyDisplayFor(x => x.Amount);
Provided your model is passed in correctly.
Obviously, you'd need to do error checking and so on.
Make string the amount
public string Amount{ get; set;}
Create a method that converts to string with currency exact
private string LocalizedAmount(decimal theAmount, string cultureName)
{
return theAmount.ToString("c",CultureInfo.CreateSpecificCulture(cultureName));
}
If you are stored in your database two fields or columns one for value and one for culture.
And in the Repository or the controller:
Amount = LocalizedAmount(Convert.ToDecimal(reader[0]),reader[1].ToString());
You need to assing the culture:
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureName);
Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(cultureName);
Inside this Controller overrided methods:
protected override void OnActionExecuted(ActionExecutedContext filterContext)
protected override void ExecuteCore()
Read the whole technique in this NadeemAfana's blog ASP.NET MVC 3 Internationalization

MVC HtmlHelper vs FluentValidation 3.1: Troubles getting ModelMetadata IsRequired

I created a HtmlHelper for Label that puts a star after the name of that Label if associated field is required:
public static MvcHtmlString LabelForR<TModel, TValue>(
this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
return LabelHelper(
html,
ModelMetadata.FromLambdaExpression(expression, html.ViewData),
ExpressionHelper.GetExpressionText(expression),
null);
}
private static MvcHtmlString LabelHelper(HtmlHelper helper, ModelMetadata metadata, string htmlFieldName, string text)
{
... //check metadata.IsRequired here
... // if Required show the star
}
If I use DataAnnotations and slap [Required] on the property in my ViewModel, metadata.IsRequired in my private LabelHelper will be equal to True and everything will work as intended.
However, if I use FluentValidation 3.1 and add a simple rule like that:
public class CheckEmailViewModelValidator : AbstractValidator<CheckEmailViewModel>
{
public CheckEmailViewModelValidator()
{
RuleFor(m => m.Email)
.NotNull()
.EmailAddress();
}
}
... in my LabelHelper metadata.IsRequired will be incorrectly set to false. (The validator works though: you can't submit empty field and it needs to be an Email like).
The rest of the metadata looks correct (Ex: metadata.DisplayName = "Email").
In theory, FluentValidator slaps RequiredAttribute on property if Rule .NotNull() is used.
For references:
My ViewModel:
[Validator(typeof(CheckEmailViewModelValidator))]
public class CheckEmailViewModel
{
//[Required]
[Display(Name = "Email")]
public string Email { get; set; }
}
My Controller:
public class MemberController : Controller
{
[HttpGet]
public ActionResult CheckEmail()
{
var model = new CheckEmailViewModel();
return View(model);
}
}
Any help is appreciated.
By default, MVC uses the DataAnnotations attributes for two separate purposes - metadata and validation.
When you enable FluentValidation in an MVC application, FluentValidation hooks into the validation infrastructure but not metadata - MVC will continue to use attributes for metadata. If you want to use FluentValidation for metadata as well as validation then you'd need to write a custom implementation of MVC's ModelMetadataProvider that knows how to interrogate the validator classes - this isn't something that FluentValidation supports out of the box.
I have a custom ModelMetadataProvider that enhances the default DataAnnotations one giving the following:
populates "DisplayName" from propertyname splitting string from Camel Case, if none is specified through DisplayAttribute.
If the ModelMetadata.IsRequired is set to false it checks if there are any fluent validator rules present (of type NotNull or NotEmpty).
I definitely checked out the source code that Jeremy has prepared but I was not ready for a total overhaul so I mixed and matched in order not to lose the default behavior. You can find it here
Here is the code with some additional goodness taken from this post.
public class CustomModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
readonly IValidatorFactory factory;
public CustomModelMetadataProvider(IValidatorFactory factory)
: base() {
this.factory = factory;
}
// Uppercase followed by lowercase but not on existing word boundary (eg. the start)
Regex _camelCaseRegex = new Regex(#"\B\p{Lu}\p{Ll}", RegexOptions.Compiled);
// Creates a nice DisplayName from the model’s property name if one hasn't been specified
protected override ModelMetadata GetMetadataForProperty(
Func<object> modelAccessor,
Type containerType,
PropertyDescriptor propertyDescriptor) {
ModelMetadata metadata = base.GetMetadataForProperty(modelAccessor, containerType, propertyDescriptor);
metadata.IsRequired = metadata.IsRequired || IsNotEmpty(containerType, propertyDescriptor.Name);
if (metadata.DisplayName == null)
metadata.DisplayName = displayNameFromCamelCase(metadata.GetDisplayName());
if (string.IsNullOrWhiteSpace(metadata.DisplayFormatString) &&
(propertyDescriptor.PropertyType == typeof(DateTime) || propertyDescriptor.PropertyType == typeof(DateTime?))) {
metadata.DisplayFormatString = "{0:d}";
}
return metadata;
}
string displayNameFromCamelCase(string name) {
name = _camelCaseRegex.Replace(name, " $0");
if (name.EndsWith(" Id"))
name = name.Substring(0, name.Length - 3);
return name;
}
bool IsNotEmpty(Type type, string name) {
bool notEmpty = false;
var validator = factory.GetValidator(type);
if (validator == null)
return false;
IEnumerable<IPropertyValidator> validators = validator.CreateDescriptor().GetValidatorsForMember(name);
notEmpty = validators.OfType<INotNullValidator>().Cast<IPropertyValidator>()
.Concat(validators.OfType<INotEmptyValidator>().Cast<IPropertyValidator>()).Count() > 0;
return notEmpty;
}
}

How can Editor Templates / Display Templates recognize any Attributes assigned to them?

I want to add a [Required] attribute to my DateTime editor template so that I can add the appropriate validation schemes or a DataType.Date attribute so I know when I should only display dates. But I can't figure out how to get the metadata that says which attributes the Editor Template has assigned to it.
The built-in attributes, such as [Required] assign different properties on the metadata (see the blog post I have linked at the end of my answer to learn more). For example:
public class MyViewModel
{
[Required]
public string Foo { get; set; }
}
would assign:
#{
var isRequired = ViewData.ModelMetadata.IsRequired;
}
in the corresponding editor/display template.
And if you had a custom attribute:
public class MyCustomStuffAttribute : Attribute, IMetadataAware
{
public void OnMetadataCreated(ModelMetadata metadata)
{
metadata.AdditionalValues["customStuff"] = "some very custom stuff";
}
}
and a view model decorated with it:
public class MyViewModel
{
[MyCustomStuff]
public string Foo { get; set; }
}
in the corresponding editor/display template you could fetch this:
#{
var myCustomStuff = ViewData.ModelMetadata.AdditionalValues["customStuff"];
}
Also you should absolutely read Brad Wilson's series of blog posts about what ModelMetadata and templates in ASP.NET MVC is and how to use it.

Resources