ASP.NET MVC Core: The Good Parts

MVC 6 should be out any day, so we need to be prepared.

The good thing is, it’s all very similar to MVC 5; the even better thing is, it got better! A couple of ways it is so cool, in my mind, are:

  • Most of the stuff is very similar to what we had: controllers, views, mostly work the same way;
  • MVC and Web API have been merged together, so it’s not really any different add an API controller that returns JSON or an MVC controller that returns an HTML view;
  • All of its code is available in GitHub, and you can even contribute to it;
  • It is now cross-platform, meaning, you will be able to deploy your web app to Linux (even as a Docker container) and Mac (if you use .NET Core);
  • It is very modular: you only add to your project the Nuget packages you really need;
  • It now uses service providers to resolve all of its features; you do not need to know the static location of properties, like, ControllerBuilder.Current, GlobalFilters.Filters, etc; the boilerplate configuration in the Startup class is pretty easy to follow and change;
  • The default template has Bower, NPM and Gulp support out of the box;
  • No need to explicitly add attribute routing, it is built-in by default;
  • We have a better separation of contents and code, in the form of the wwwroot folder to which servable contents are moved;
  • Logging is injected, as are most of the services we need, or we can easily add our own without the need to add any IoC library; even filters can come from IoC;
  • It is now possible to have our Razor pages inherit from a custom class, have custom functions defined in Razor (by the way, do not use it!) and inject components into it;
  • View components and tag helpers are a really cool addition;
  • The new OWIN-based pipeline that is now ASP.NET is much more extensible and easy to understand than System.Web-based ASP.NET used to be;
  • This one is a corollary from the latter: Web.config is gone; let’s face it, it was a big beast, so it’s better to just drop it.

On the other hand, we will need to learn a lot of new stuff, namely, a whole lot of new interfaces and base classes to use. Also, it may sometimes be a bit tricky to find out which Nuget package contains that specific API we’re after. And because there is no more System.Web, all of the infrastructure management is very different. Finally, not all the libraries we’re used to will be immediately available for .NET Core, but that’s really not a problem with ASP.NET Core itself.

All in all, I think it is a good thing! I’ll be talking more on ASP.NET Core, so stay tuned!

Implementing Missing Features in Entity Framework Core – Part 4: Conventions

Conventions

This will be the fourth in a series of posts about bringing the features that were present in Entity Framework pre-Core into EF Core. The others are:

  • Part 1: Introduction, Find, Getting an Entity’s Id Programmatically, Reload, Local, Evict
  • Part 2: Explicit Loading
  • Part 3: Validations

Conventions are a mechanism by which we do not have to configure specific aspects of our mappings over and over again. We just accept how they will be configured, of course, we can override them if we need, but in most cases, we’re safe.

In EF 6.x we had a number of built-in conventions (which we could remove) but we also had the ability to add our own. In Entity Framework Core 1, this capability hasn’t been implemented, or, rather, it is there, but hidden under the surface.

A lot has changed. As of Entity Framework Core, conventions are specific to the provider in use, which makes perfect sense. But then we have several kinds of conventions. Just to give you an idea, the ConventionSet class exposes 15 convention collections! This is the place where we can register our own conventions, but there are two problems:

  • The current ConventionSet instance is not publicly accessible, so we need to use reflection to get hold of it;
  • We don’t want to add a new “blank” instance of ConventionSet because this way we would lose all of the conventions that would have been injected by the provider.

So, the solution I came up with had to use reflection to get the current convention set and allow adding new conventions, which is not ideal. Let’s see the code.

We need an extension method to get hold of the ConventionSet from the ModelBuilder and allow adding a convention, in the form of a IModelConvention:

public static class ModelBuilderExtensions

{

    public static ModelBuilder AddConvention(this ModelBuilder modelBuilder, IModelConvention convention)

    {

        var imb = modelBuilder.GetInfrastructure();

        var cd = imb.Metadata.ConventionDispatcher;

        var cs = cd.GetType().GetField("_conventionSet", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(cd) as ConventionSet;

 

        cs.ModelBuiltConventions.Add(convention);

 

        return modelBuilder;

    }

 

    public static ModelBuilder AddConvention<TConvention>(this ModelBuilder modelBuilder) where TConvention : IModelConvention, new()

    {

        return modelBuilder.AddConvention(new TConvention());

    }

}

Feel free to cache the _conventionSet field somewhere, but that is not the point of this post.

You can see that we are adding the new convention to the ModelBuiltConventions collection, basically, because this is the only collection that Entity Framework Core will go through during the OnModelCreating method, all of the other collections will have been processed before it. We’re cool with that.

Let’s see two examples of sample conventions, first, one to set the maximum length of strings, where it hasn’t been explicitly set:

public sealed class DefaultStringLengthConvention : IModelConvention

{

    internal const int DefaultStringLength = 50;

    internal const string MaxLengthAnnotation = "MaxLength";

 

    private readonly int _defaultStringLength;

 

    public DefaultStringLengthConvention(int defaultStringLength = DefaultStringLength)

    {

        this._defaultStringLength = defaultStringLength;

    }

 

    public InternalModelBuilder Apply(InternalModelBuilder modelBuilder)

    {

        foreach (var entity in modelBuilder.Metadata.GetEntityTypes())

        {

            foreach (var property in entity.GetProperties())

            {

                if (property.ClrType == typeof(string))

                {

                    if (property.FindAnnotation(MaxLengthAnnotation) == null)

                    {

                        property.AddAnnotation(MaxLengthAnnotation, this._defaultStringLength);

                    }

                }

            }

        }

 

        return modelBuilder;

    }

}

Easy, easy, hey? We go through all of the mapped entities, then through all of their properties of type string, check if the maximum length annotation is present, and, if not, add a new one. Only one method, with access to all mapped entities.

Another example, turning off the table pluralization. As you know, as of EF RC2, table names of DbSet properties exposed in the DbContext are pluralized. We can get rid of this behavior if we explicitly set the name to something, in this case, it will be the entity type name:

public sealed class SingularizeTableNameConvention : IModelConvention

{

    public InternalModelBuilder Apply(InternalModelBuilder modelBuilder)

    {

        foreach (var entity in modelBuilder.Metadata.GetEntityTypes())

        {

            if (entity.FindAnnotation(RelationalAnnotationNames.TableName) == null)

            {

                entity.Relational().TableName = entity.Name.Split('.').Last();

            }

        }

 

        return modelBuilder;

    }

}

Now, some new extension methods come handy:

public static ModelBuilder UseDefaultStringLength(this ModelBuilder modelBuilder, int defaultStringLength = DefaultStringLengthConvention.DefaultStringLength)

{

    modelBuilder.AddConvention(new DefaultStringLengthConvention(defaultStringLength));

 

    return modelBuilder;

}

 

public static ModelBuilder UseSingularTableNames(this ModelBuilder modelBuilder)

{

    modelBuilder.AddConvention<SingularizeTableNameConvention>();

 

    return modelBuilder;

}

And that’s it! The way to apply these conventions (one or both) is during OnModelCreating:

protected override void OnModelCreating(ModelBuilder modelBuilder)

{

    modelBuilder

        .UseDefaultStringLength()

        .UseSingularTableNames();

 

    base.OnModelCreating(modelBuilder);

}

You can now reuse these conventions in all of your context classes very easily. Hope you enjoy it! Two final remarks about this solution:

  • The reflection bit is a problem, because things may change in the future. Hopefully Microsoft will give us a workaround;
  • There is no easy way to remove built-in (or provider-injected) conventions, because are applied before OnModelCreating, but I think this is not a big problem, since we can change them afterwards, as we’ve seen.

Stay tuned for more!

.NET Core Reloaded

OK, so here’s another post on .NET Core from a non-code perspective.

There has been recently lots of discussion about this all over the Internet!We are all well aware that .NET Core, which hasn’t been released yet (June 1st), has had some rough times. RC1 has been out for quite some time with a Go Live license, and RC2 just hit the streets a few weeks ago, substantially delayed according to the original schedule. The problem was, from RC1 to RC2 things have changed a lot, and are even going to change more with RTM.

We knew from day one that we wouldn’t have AppDomains, binary serialization, Remoting, sandboxing, Windows Forms, WPF, WCF and WWF, and most of us were cool about that. Also, having project.json instead of the old MSBuild seemed a step in the right direction – that is, the direction driven by Node.js and modern JavaScript technologies. Relying on Nuget for dependencies was also something that we were already doing, just not for the core assemblies. Having a smaller API surface than the one we were used to was harder to digest, but we could live with it.

But then it hit us: project.json is no more, and MSBuild is back, even if slightly crippled! Also, AppDomains will make a comeback, too… and so most of the “old” APIs, now hopefully sanitized for cross-platformity!

We recently had to put up with the horrible mess of KRE, DNU, DNX, DNVM just to end up with DOTNET. At some point we got a global service locator (something that the .NET Core team treats worse than the Devil itself) and them we lost it, as we did with property injection for ASP.NET MVC controllers (likewise), the ability to populate models from JSON or HTTP POSTs (also gone) and lots of namespace and type changes.

Let’s be clear: on the communication side, as well as in managing people’s expectations, Microsoft blew it. Even if in some aspects (IMO), they are going in the right direction, they got it all wrong. It seems that they haven’t given things enough thought, or have been experimenting with it for too long. Things don’t usually change so dramatically between RCs. How can software developers who have commercial products to release, are expected to deal with so many changes?

On the other hand, these were (are) RCs, meaning, pre-release versions, and nobody said that these would remain untouched. I don’t think these affect the global goal of .NET Core: having a modular, modern, cross-platform framework. I expect when it does come out, most problems will fade away. As we speak, it already runs in Linux Docker containers, and we can debug it from Visual Studio – and Visual Studio Code. I kind of like going back to MSBuild, because it is so much more powerful than project.json. Bringing back AppDomains only widens our horizons, not the opposite. And I really, really like the stuff they’ve been doing in ASP.NET MVC Core.

.NET Core will give us more flexibility, in the sense that we aren’t limited in using Windows as the deployment target. For myself, it’s going to be about the capability to deploy Linux VMs as Docker containers, but this time running .NET, and not just Node.js. I don’t see myself moving to full-time Linux or Mac development, but Visual Sudio Code and Project Rider do seem to make it possible. None of this was affected by all the shifts and changes that occurred in the .NET Core gestation. Only confidence in the Microsoft .NET Core team was affected, to some level.

So, in retrospective, I think Microsoft has learned a lesson. Give stuff more thought before announcing it or making it public (I know, I know, .NET Core is open source, so the repo is publicly visible), hear from the community before making changes and not after them, and stick to your decisions! Winking smile