Skip to content

Instantly share code, notes, and snippets.

@wilsonat
Last active December 26, 2015 02:28
Show Gist options
  • Save wilsonat/7078449 to your computer and use it in GitHub Desktop.
Save wilsonat/7078449 to your computer and use it in GitHub Desktop.
A couple examples of the convention-based metadata provider I created.
// This code uses "moment.js" for conversion and formatting
$(function () {
$('[data-date-type=datetime]').each(function () {
var dt = moment($(this).data('dateMilliseconds'));
$(this).html(dt.format("L") + " " + dt.format("LT"));
});
});
// All conventions implement this interface. The "Apply" method is responsible for determining if a convention
// applies, as well as the actual transformation of the metadata. (I'm working on an enhanced version that
// separates the "is a match" logic from the actual metadata application.)
public interface IMetadataConvention
{
void Apply(IEnumerable<Attribute> attributes, ModelMetadata metadata);
}
// This convention applies our default naming convention. This convention will split a "pascal-cased" property name
// into individual words. For example, "BorrowerFirstName" would become "Borrower First Name". This convention can
// be overridden using the "DisplayName" attribute.
public class DefaultDisplayNameMetadataConvention : IMetadataConvention
{
public void Apply(IEnumerable<Attribute> attributes, ModelMetadata metadata)
{
var attributeList = new List<Attribute>(attributes);
if (string.IsNullOrEmpty(metadata.PropertyName) || attributeList.OfType<DisplayNameAttribute>().Any())
{
return;
}
var displayAttribute = attributeList.OfType<DisplayAttribute>().FirstOrDefault();
if (displayAttribute != null && !String.IsNullOrEmpty(displayAttribute.Name))
{
return;
}
metadata.DisplayName = metadata.PropertyName.SplitPascalCase();
if(metadata.ModelType.IsBoolean() && metadata.DisplayName.StartsWith("is ", StringComparison.OrdinalIgnoreCase))
{
metadata.DisplayName = metadata.DisplayName.Substring(3) + "?";
}
}
}
public class CurrencyMetadataConvention : IMetadataConvention
{
public void Apply(IEnumerable<Attribute> attributes, ModelMetadata metadata)
{
if (attributes.OfType<DataTypeAttribute>().Any()) return;
if (metadata.ModelType.IsNumericType() && metadata.PropertyEndsWith("Amount"))
{
metadata.DataTypeName = DataType.Currency.ToString();
metadata.DisplayFormatString = "{0:C}";
metadata.EditFormatString = "{0:0.00}";
}
}
}
// In addition to setting the DataType, this convention will emit "data-*" attributes into the HTML markup. These
// data-* attributes are used to convert UTC date time values to local date time values.
// (See the 'datetime-local.js' Gist for details.)
public class DateMetadataConvention : IMetadataConvention
{
public void AssignMetadata(IEnumerable<Attribute> attributes, ModelMetadata metadata)
{
var attributeList = attributes.ToList();
var dataTypeAttribute = attributeList.OfType<DataTypeAttribute>().FirstOrDefault();
if (dataTypeAttribute == null && metadata.ModelType.IsDateTime())
{
var isDate = metadata.PropertyEndsWith("Date");
metadata.DataTypeName = isDate ? DataType.Date.ToString() : DataType.DateTime.ToString();
}
if (dataTypeAttribute != null &&
(dataTypeAttribute.DataType != DataType.Date && dataTypeAttribute.DataType != DataType.DateTime))
{
return;
}
if (metadata.DataTypeName == DataType.Date.ToString())
{
metadata.DisplayFormatString = "{0:MM/dd/yyyy}";
metadata.EditFormatString = "{0:MM/dd/yyyy}";
}
else
{
var modelValue = metadata.Model as DateTime?;
if (modelValue == null) return;
metadata.AdditionalValues.Add("data-date-milliseconds",
modelValue.Value.Subtract(new DateTime(1970, 1, )).TotalMilliseconds);
metadata.AdditionalValues.Add("data-date-type", "datetime");
}
}
}
// This custom provider extends the out-of-the-box metadata provider. It's responsible for applying all instances of
// IMetadataConvention. (The enhanced version of this code allows the user to override conventions at the namespace and/or
// class level.)
public class ConventionalModelMetaDataProvider : DataAnnotationsModelMetadataProvider
{
private readonly IEnumerable<IMetadataConvention> _conventions;
// NOTE: The "conventions" parameter is injected via an IOC container
public ConventionalModelMetaDataProvider(IEnumerable<IMetadataConvention> conventions)
{
Ensure.Argument.NotNull((object) conventions, "conventions");
this._conventions = conventions;
}
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object>
modelAccessor, Type modelType, string propertyName)
{
List<Attribute> attributeList = Enumerable.ToList<Attribute>(attributes);
ModelMetadata metadata = base.CreateMetadata((IEnumerable<Attribute>) attributeList, containerType, modelAccessor,
modelType, propertyName);
GenericEnumerableExtensions
.Each<IMetadataConvention>(this._conventions,
(Action<IMetadataConvention>) (p => p.Apply(
(IEnumerable<Attribute>) attributeList, metadata)));
return metadata;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment