ASP.NET Core MVC

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

<input> Tag Ids... Really?

02/28/2017

ASP.NET Core MVC 1.1 has another unexpected "gift" in store. It will decorate all tags with id=property which is utterly unexpected (compared to MVC5) and utterly unnecessary. If your EditorFor(), DisplayFor(), Hidden() etc. statements don't supply an id, one is generated for you, based on the property name. But WHY? If my code doesn't ask for it, does my code really want an id?

What makes this completely untolerable is the fact that for W3C compliance no two tags can have the same id. If you happen to have multiple forms (<form> tags), a collision is likely to happen. Because of this W3C requirement, ids are not typically used throughout YetaWF. Instead, most tags are referenced using the name= tag (in Javascript/jQuery).

What are the options? You could explicitly specify a (unique) id for each tag. Seriously? If you don't need them, why would you want burden the browser with unnecessary ids?

Instead, implement your own Html Generator (IHtmlGenerator). This is quite easily accomplished, thanks to the power of MVC6. They taketh (compatibility) and giveth (more power). Still, a random and dubious design decision choice to generate ids when they're not requested.

public void ConfigureServices(IServiceCollection services) {
    . . . 
    services.AddSingleton(typeof(IHtmlGenerator), typeof(YetaWFDefaultHtmlGenerator));

    services.AddMvc((options) => {
    . . . 
}

The actual Html Generator implementation just derives from the DefaultHtmlGenerator class and overrides the GenerateInput method:
(Update: During development of ASP.NET Core MVC 1.2 2.0 (not yet released), the clientValidatorCache argument was removed from the DefaultHtmlGenerator constructor. Simply remove that argument to use it with 1.2 2.0.)

using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Text.Encodings.Web;

namespace YetaWF.Core.Views
{
    public class YetaWFDefaultHtmlGenerator : DefaultHtmlGenerator {

        public YetaWFDefaultHtmlGenerator(
                IAntiforgery antiforgery,
                IOptions<MvcViewOptions> optionsAccessor,
                IModelMetadataProvider metadataProvider,
                IUrlHelperFactory urlHelperFactory,
                HtmlEncoder htmlEncoder,
                ClientValidatorCache clientValidatorCache,
                ValidationHtmlAttributeProvider validationAttributeProvider) :
                    base(antiforgery, optionsAccessor, metadataProvider, urlHelperFactory, htmlEncoder, clientValidatorCache, validationAttributeProvider) { }

        protected override TagBuilder GenerateInput(
                ViewContext viewContext,
                InputType inputType,
                ModelExplorer modelExplorer,
                string expression,
                object value,
                bool useViewData,
                bool isChecked,
                bool setId,
                bool isExplicitValue,
                string format,
                IDictionary<string, object> htmlAttributes) {
            setId = false;
            return base.GenerateInput(viewContext, inputType, modelExplorer, expression, value, useViewData, isChecked, setId, isExplicitValue, format, htmlAttributes);
        }
    }
}

No Comments

Be the first to add a comment!

Add New Comment

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