Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

I can't adopt MVC 6 due to its broken abstractions #5532

@nathan-alden-sr

Description

@nathan-alden-sr

I, like many others, want .NET Core to be an amazing experience, not just for being able to deploy code to different platforms but also as an amazing development experience. I have been using Web API 2 for many years to build robust APIs to support various teams API-first development practices. Particularly important to me is the effort spent developing several internal NuGet packages that allow several .NET teams to share code and implementations of common concerns such as JWT creation and validation, authorization strategy in the Web API pipeline, standard bootstrapping configuration in Startup, etc. These teams don't write toy applications; our needs are great and varied.

Recently, with .NET Standard finally settling down and becoming a bit more mature, I decided to attempt to convert all our code from .NET 4.6.1 to .NET Standard 1.6. Unfortunately, this has not gone nearly as smoothly as I had hoped. There are several issues with MVC 6's design that are preventing me from not only migrating to it, but also considering it whatsoever for future use. I don't have a blog, so I created this issue instead to document my frustrations in the hopes that someone out there is listening, and in the hopes that I can save others with these same issues some time.

These issues are listed in the order in which I ran into them during my migration effort.

Terminology: MVC vs Web API

Why does MVC get first billing in the new framework? People are and should be writing less server-side-templated web applications over time. Any new web framework should focus more on API-first development and less on server-side templating. In other words, the framework should've been called something more neutral.

IHeaderDictionary

IHeaderDictionary should be IRequestHeaderDictionary and IResponseHeaderDictionary, each with properties defined for each standard HTTP header, the way it was with Web API 2's HttpRequestHeaders and HttpResponseHeaders classes. Why remove this very useful abstraction? I can't extend IHeaderDictionary because then those extensions would improperly apply to both request and response headers and the members would have to be methods instead of properties. As a result, we're back to accessing headers using Dictionary with magic strings for keys.

Status Codes

Why are status codes exposed everywhere as ints? What happened to using the HttpStatusCode enum? I am aware of the StatusCodes class that contains all the status codes as constant integers, but this is not discoverable from IntelliSense; less-experienced developers are going to type in the integer, instead. At the very least, an overload should have been provided that contained the standard HTTP status codes.

Missing Helper Methods

Several status code helper methods are missing from the Controller class. A robust design would see every HTTP status code accounted for with a helper method. Web API had the same issue; in both cases, it necessitated a derived class that provided all the missing status code wrapper methods.

Response Envelopes

I had code that would wrap response models in standard success/error response envelopes by using DelegatingHandlers, thus avoiding violating DRY in every action method. I have not found a way to do this in MVC 6 other than OutputFormatters, which are limited in usefulness since they have no reference to the original action method. As a result, I can't use attributes to control response behavior (e.g., allowing action methods to opt out of response envelopes with something like [ResponseEnvelopeOptOut]).

ClaimsPrincipal

HttpContext.User now returns ClaimsPrincipal instead of IPrincipal. This change completely breaks the custom IPrincipal implementation I had written that encapsulated a set of claims with useful properties for each claim. Were I to use ClaimsPrincipal, I'd be forced to rely on the broken Claim class implementation that doesn't handle arrays properly and can't handle more complex types of claim values like JSON objects. As a result, I have to store my custom principal in HttpContext.Items, which is not easily discoverable and will break third-party middleware that relies on the HttpContext.User property being populated.

Authentication Abstractions

The forced authentication abstraction (requirements, policies) eliminated a very useful shared custom authorization attribute I had written that allowed this: [CustomAuthorize(Operator.And, Permission.Create, Permission.Read)]. My attribute allowed for a single authorization algorithm to be implemented (within the attribute's code) that would check various JWT claims, with each action method choosing what parameters fed into that algorithm. Further, "The ASP.NET security guy"'s (blowdart's) dismissive tone to several people with the exact same concerns left a really bad taste in my mouth. Here's the Stack Overflow thread I'm referring to: http://stackoverflow.com/questions/31464359/custom-authorizeattribute-in-asp-net-5-mvc-6/31465227

Summary

In summary, my gripes with MVC 6 mostly come down to forced, broken abstractions that assume people are going to only want to do things in one specific way. In Web API 2, I managed to implement a ton of custom code using the abstractions provided by Web API (e.g., the super-useful Content property, HttpRequestMessage, and HttpResponseMessage instead of the obfuscated IActionResult interface). As it stands, I am unable to migrate my team's code to MVC 6 without significant and backward changes (e.g., using the horrible policy/requirement abstraction).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions