.NET Core - MVC

Observations about .NET Core, MVC and converting YetaWF from ASP.NET MVC5 to .NET Core MVC. The new .NET Core has great new features. Getting there from an existing MVC 5 app is HARD!

AdditionalMetadataAttribute Anyone?

02/15/2017

If you developed any non-trivial MVC3-5 application, you may have used the AdditionalMetadataAttribute. YetaWF uses the AdditionalMetadata attribute to "decorate" properties for its scaffolding support. For example, when using a Url, the property is defined similar to this:

[Caption("Url"), Description("Url")]
[UIHint("Url"), AdditionalMetadata("UrlType", UrlHelperEx.UrlTypeEnum.Local | UrlHelperEx.UrlTypeEnum.Remote), UrlValidation(UrlValidationAttribute.SchemaEnum.Any, UrlHelperEx.UrlTypeEnum.Local | UrlHelperEx.UrlTypeEnum.Remote)]
[StringLength(Globals.MaxUrl), Required, Trim]
public string Url { get; set; }

The UIHint attribute specifies the template "Url" which renders a template so the user can enter a remote Url or select one of the designed pages. The AdditionalMetadata attribute further defines the specific editing (or display) support required by the template. So rather than implementing multiple very similar templates, one template is used and the AdditionalMetadata attribute defines the actual template-specific options.

MVC6 spoils the party! By not providing an AdditionalMetadata attribute!!! (Dammit!)

Googling offered this: https://github.com/aspnet/Mvc/issues/324  It's disappointing that this new version of ASP.NET Core MVC does not seem to care about compatibility.

Fortunately there is a pretty simple solution, which we found while upgrading YetaWF (after a lot of complaining, grumbling, etc., we're just learning ASP.NET Core and MVC6 like everyone else). As negative as our feelings are towards the process of upgrading to MVC6, the solution below speaks volumes for the power of MVC6!

Add the following to your startup code:

public void ConfigureServices(IServiceCollection services) {
              
    ... other stuff

    services.AddMvc((mopts) => {
        // We need to roll our own support for AdditionalMetadataAttribute 
        mopts.ModelMetadataDetailsProviders.Add(new AdditionalMetadataProvider());
    });
}

Then you need to implement the AdditionalMetadataProvider class (in the same source file or a separate file):

using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;

namespace YetaWF.Core.Support {

    public class AdditionalMetadataProvider : IDisplayMetadataProvider {

        public AdditionalMetadataProvider() {}

        public void CreateDisplayMetadata(DisplayMetadataProviderContext context) {
            // Extract all AdditionalMetadataAttribute values and add to AdditionalValues
            // Why oh why was this omitted from MVC6????
            if (context.PropertyAttributes != null) {
                foreach (object propAttr in context.PropertyAttributes) {
                    AdditionalMetadataAttribute addMetaAttr = propAttr as AdditionalMetadataAttribute;
                    if (addMetaAttr != null) {
                        context.DisplayMetadata.AdditionalValues.Add(addMetaAttr.Name, addMetaAttr.Value);
                    }
                }
            }
        }
    }
}

And you need the AdditionalMetadataAttribute class:

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Property, AllowMultiple = true)]
    public sealed class AdditionalMetadataAttribute : Attribute {

        public AdditionalMetadataAttribute(string name, object value) {
            if (name == null) {
                throw new ArgumentNullException("name");
            }
            Name = name;
            Value = value;
        }

        public string Name { get; private set; }
        public object Value { get; private set; }
    }

... and you're back in business.

No Comments

Be the first to add a comment!

Add New Comment

Complete this simple form to add a comment to this blog entry.