Entity Framework Core 1.0 Migrations in a Separate Class Library

At the beginning of last year I started blogging about ASP.NET Core 1.0 (née ASP.NET 5). But it was early days and things were far from stable with the product, so I soon came to the conclusion that being such an early adopter was more trouble than it was worth due to the number of bugs, missing features, and breaking changes. Roll on a year, and ASP.NET Core 1.0 along with Entity Framework Core 1.0 and the requisite Visual Studio 2015 tooling has finally been released. So time to start playing again.

I can’t say that I’ve done a huge amount with it yet, so can’t really comment on it’s stability. But I would assume that it’s about as stable as you would expect from a 1.0.0 version. Good enough to work with but challenging at times.

And it didn’t take me long to come across a challenge, which I’d like to share with you here.

In any but the smallest of solutions, it’s good practice to seperate data concerns from domain and presentational concerns. In the case of ASP.NET MVC and Entity Framework, that typically involves placing the data context and data model classes into a separate class library.

If I do this and then attempt to add a migration, as follows –

PM> Add-Migration -Name "Initial" -Project "SandboxCore.Data"

I get an error telling me that the preview of EF does not support commands on class library projects –

Could not invoke this command on the startup project 'SandboxCore.Data'. 
This preview of Entity Framework tools does not support commands on class library projects in ASP.NET Core 
and .NET Core applications. See http://go.microsoft.com/fwlink/?LinkId=798221 for details and workarounds.

The link in the error message describes 2 workarounds. However, at the time of writing it is out-of-date, and the workarounds aren’t as simple as described.

So here’s what I believe to be the easiest solution to the problem, which is to create the data project, not as a class library, but as a console application.

1. Add a .NET Core console project, to your solution, to contain the data context and data model classes.

2. Add the entity framework core provider, for your chosen database, as a project dependency to your data project. If targetting SQL Server, add Microsoft.EntityFrameworkCore.SqlServer.

3. Add the entity framework tools, Microsoft.EntityFrameworkCore.Tools, as both a project dependency and as a tool to your data project. Adding it as a project dependency ensures that it is resolved and downloaded from NuGet. Whereas, adding it as a tool ensures that the entity framework commands are accessible. Your data project’s project.json should now look like this –

{
  "version": "1.0.0-*",
  "buildOptions": {
    "emitEntryPoint": true
  },

  "dependencies": {
    "Microsoft.NETCore.App": {
      "type": "platform",
      "version": "1.0.0"
    },
    "Microsoft.EntityFrameworkCore.SqlServer": "1.0.0",
    "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final"
  },

  "frameworks": {
    "netcoreapp1.0": {
      "imports": "dnxcore50"
    }
  },

  "tools": {
    "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final"
  }
}

4. Add data model classes to your data project. I like to add mine to a Models folder to keep the root of the project tidy.

using System.ComponentModel.DataAnnotations;

namespace SandboxCore.Data.Models
{
    public class Product
    {
        public int ProductId { get; set; }

        [StringLength(200)]
        public string ProductName { get; set; }
    }
}

5. And finally, add an implmentation of DbContext to the data project. The implementation should have a publically accessible Dbset<TEntity> for each data model class, and the OnConfiguring method should be overridden in order to configure the context to target your database of choice. This last part is necessary for migrations.

using Microsoft.EntityFrameworkCore;
using SandboxCore.Data.Models;

namespace SandboxCore.Data
{
    public class SandboxCoreDbContext : DbContext
    {
        public DbSet<Product> Projects { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=SandboxCore;Trusted_Connection=True;");
        }
    }
}

6. You can now enable and add migrations using the following command.

PM> Add-Migration -Name "Initial" -Project "SandboxCore.Data" -StartupProject "SandboxCore.Data"
 

The -Project option specifies SandboxCore.Data as the target for migrations, whilst the -StartupProject option specifies SandboxCore.Data as the entry point for running the EF command.

 

ASP.NET 5 MVC TagHelpers (6.0.0-beta3)

Note. ASP.NET 5 will be released with Visual Studio 2015. Visual Studio 2015 is currently at CTP 6, with a potential Q3 release date. Microsoft is still developing the product, and the fact that TagHelpers are not included in CTP 6, but instead must be installed from Nuget, suggests that they may still be subject to change. Maybe I'm jumping the gun with this post. But I think that TagHelpers are a great step forward from HtmlHelpers in terms of improving the format and usability of ASP.NET MVC views. So I want to share.

I’m starting to port across my BikeStore application to ASP.NET 5. There are lots of new features to digest, and as mentioned in my previous post, I will be outlining the steps required to migrate the application, and sharing some lessons learnt, on this blog.

An ASP.NET 5 web application differs in many ways from previous versions of ASP.NET (there is a good overview of some of the new features here – Introduction to ASP.NET 5). For this reason my starting point for porting across my application is to create a new ASP.NET 5 Preview Starter Web.

NewApp

NewPreview

As mentioned above, the TagHelpers are not included in the CTP 6 version of Visual Studio 2015. This is why when we inspect the out-of-the-box views, we see HtmlHelper method calls rather than TagHelpers.

Before I start migrating across my BikeStore views, I’m going to update the out-of-box views to use TagHelpers. When Visual Studio 2015 is released you will not have to do this, as the out-of-the-box views should have been updated to use TagHelpers. But I hope this post will still be relevant as I will be detailing the TagHelper classes by comparing the before and after views.

The source code for the TagHelpers is in Microsoft.AspNet.Mvc.TagHelpers namespace and can be found on GitHub. It’s well worth looking through the TagHelper concrete classes to learn the rules that each implement. I will be outlining some of these rules below.

I firstly need to download the Microsoft.AspNet.Mvc.TagHelpers pre-release assembly from NuGet. I’m going to do this using the NuGet Package Manager window –

Nuget

Having clicked on Install the dependency is added to Project.json –

projectjson

With ASP.NET 5 it is possible to add dependencies directly to the Project.json file, helped with intellisense support for package names and versions. However, the package manager window is still relevant as it allows searching, gives descriptive information on the packages, and allows for the adding and removing of packages from multiple projects in a solution.

_ViewStart.cshtml

To enable TagHelpers in a view we need to add the @addtaghelper “Microsoft.AspNet.Mvc.TagHelpers” directive to the view. If we want to use TagHelpers in all views, we can add the directive to _ViewStart.

@using BikeStore;
@addtaghelper "Microsoft.AspNet.Mvc.TagHelpers"
@{
    Layout = "/Views/Shared/_Layout.cshtml";
}

 _ChangePasswordPartial.cshtml

HtmlHelpers (Before) –

@using System.Security.Principal
@model BikeStore.Models.ManageUserViewModel

<p>You're logged in as <strong>@User.Identity.GetUserName()</strong>.</p>

@using (Html.BeginForm("Manage", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
    @Html.AntiForgeryToken()
    <h4>Change Password Form</h4>
    <hr />
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })
    <div class="form-group">
        @Html.LabelFor(m => m.OldPassword, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.PasswordFor(m => m.OldPassword, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.NewPassword, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.PasswordFor(m => m.NewPassword, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" })
        </div>
    </div>

    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" value="Change password" class="btn btn-default" />
        </div>
    </div>
}

TagHelpers (After) –

@using System.Security.Principal
@model BikeStore.Models.ManageUserViewModel

<p>You're logged in as <strong>@User.Identity.Name</strong>.</p>

<form asp-action="Manage" asp-controller="Account" method="post" class="form-horizontal" role="form" asp-anti-forgery="true">
    <h4>Change Password Form</h4>
    <hr />
    <div asp-validation-summary="ValidationSummary.All" class="text-danger"></div>
    <div class="form-group">
        <label asp-for="OldPassword" class="col-md-2 control-label"></label>
        <div class="col-md-10">
            <input asp-for="OldPassword" class="form-control" />
        </div>
    </div>
    <div class="form-group">
        <label asp-for="NewPassword" class="col-md-2 control-label"></label>
        <div class="col-md-10">
            <input asp-for="NewPassword" class="form-control" />
        </div>
    </div>
    <div class="form-group">
        <label asp-for="ConfirmPassword" class="col-md-2 control-label"></label>
        <div class="col-md-10">
            <input asp-for="ConfirmPassword" class="form-control" />
        </div>
    </div>

    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" value="Change password" class="btn btn-default" />
        </div>
    </div>
</form>

Comparing the before and after views for _ChangePasswordPartial.cshtml, the first thing to note is that we replace both the Html.BeginForm() and Html.AntiForgeryToken() method calls with a single <form> element.

The <form> element contains a number of TagHelper attributes prefixed with “asp-“. The Microsoft.AspNet.Mvc.TagHelpers namespace contains a number of classes derived from TagHelper. Each of these classes targets a specific HTML element type, and when the view is parsed, each element, along with their respective attributes and inner HTML, will be processed by the relevant TagHelper class, which will interpret the “asp-” attributes and output the relevant HTML. The <form> element is, believe it or not, targeted by the FormTagHelper class, which recognizes the following “asp-” attributes –

  • asp-action
    • The name of the action method
    • e.g. asp-action=”Manage”
  • asp-controller
    • The name of the controller
    • e.g. asp-controller=”Account”
  • asp-anti-forgery
    • Whether an anti-forgery token is generated
    • Valid values are “true” or “false”
    • e.g. asp-anti-forgery=”true”
  • asp-route-
    • Prefix for route attributes.
    • e.g. asp-route-returnurl=@ViewBag.ReturnUrl
    • e.g. asp-route-area=”Admin”

If the user has specified an “action” attribute, but has also specified values for either asp-action, asp-controller, or any asp-route- prefix, then the FormTagHelper class raises an InvalidOperationException.

Otherwise the FormTagHelper class will generate a <form> tag based either on the “action” attribute or a combination of asp-action, asp-controller, and any asp-route- prefixed attributes. The asp-anti-forgery attribute is not required, and will default to false if there is an “action” attribute, or true otherwise.

Back to comparing the views. Html.ValidationSummary() is replaced by a <div> element containing an asp-validation-summary attribute. And any <div> element with this attribute is targeted by the ValidationSummaryTagHelper class.

The ValidationSummaryTagHelper class only recognizes one “asp-” attribute –

  • asp-validation-summary
    • Validation summary rendering mode
    • Valid values are “ValidationSummary.None”, “ValidationSummary.ModelOnly”, or “ValidationSummary.All”
    • e.g. asp-validation-summary=”ValidationSummary.All”

The ValidationSummaryTagHelper will append the validation summary error messages to the inner HTML of the <div> element. So you can add content to the <div> element and it will get passed through to the output. For example –

<div asp-validation-summary="ValidationSummary.All">
	Messages will be appended to this content...
</div>

The Html.LabelFor() method call is replaced by a <label> element containing an asp-for attribute. This is targeted by the LabelTagHelper class.

The LabelTagHelper class only recognizes one “asp-” attribute –

  • asp-for
    • Expression to be evaluated against the Model, targeting a property of the Model
    • Implemented as type ModelExpression, for which the constructor takes a string parameter
    • e.g. asp-for=”UserName”
    • e.g. asp-for=”Product.Description”
    • e.g. asp-for=”Customers[i].FirstName”

The Html.PasswordFor() method is replaced by an <input> element, which is targeted by the InputTagHelper class.

The InputTagHelper class targets all <input> elements, irrespective of type. It recognizes the following “asp-” attributes –

  •  asp-for
    • Expression to be evaluated against the Model, targeting a property of the Model
  • asp-format
    • Composite format to be applied to the evaluation of the asp-for expression
    • e.g. asp-format=”{0:C}”
    • e.g. asp-format=”{0:MM/dd/yy}”

If the <input> element does not have a type attribute specified – as in my example – then the InputTagHelper will determine a default type based on the datatype and data annotation hints of the target. For example, if the target is of type boolean then the InputTagHelper will determine the default type to be “checkbox”. Or if the target is decorated with the [DataType(DataType.Password)] data annotation then the type will be password.

The InputTagHelper will also attempt to determine a format based on the target if asp-format has not been specified.

Login.cshtml

HtmlHelpers (Before) – 

@model BikeStore.Models.LoginViewModel

@{
    ViewBag.Title = "Log in";
}

<h2>@ViewBag.Title.</h2>
<div class="row">
    <div class="col-md-8">
        <section id="loginForm">
            @using (Html.BeginForm("Login", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
            {
                @Html.AntiForgeryToken()
                <h4>Use a local account to log in.</h4>
                <hr />
                @Html.ValidationSummary(true, "", new { @class = "text-danger" })
                <div class="form-group">
                    @Html.LabelFor(m => m.UserName, new { @class = "col-md-2 control-label" })
                    <div class="col-md-10">
                        @Html.TextBoxFor(m => m.UserName, new { @class = "form-control" })
                        @Html.ValidationMessageFor(m => m.UserName, "", new { @class = "text-danger" })
                    </div>
                </div>
                <div class="form-group">
                    @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" })
                    <div class="col-md-10">
                        @Html.PasswordFor(m => m.Password, new { @class = "form-control" })
                        @Html.ValidationMessageFor(m => m.Password, "", new { @class = "text-danger" })
                    </div>
                </div>
                <div class="form-group">
                    <div class="col-md-offset-2 col-md-10">
                        <div class="checkbox">
                            @Html.CheckBoxFor(m => m.RememberMe)
                            @Html.LabelFor(m => m.RememberMe)
                        </div>
                    </div>
                </div>
                <div class="form-group">
                    <div class="col-md-offset-2 col-md-10">
                        <input type="submit" value="Log in" class="btn btn-default" />
                    </div>
                </div>
                <p>
                    @Html.ActionLink("Register", "Register") if you don't have a local account.
                </p>
            }
        </section>
    </div>
</div>
@section Scripts {
    <script src="@Url.Content("~/lib/jquery-validation/jquery.validate.js")"></script>
    <script src="@Url.Content("~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js")"></script>
}

TagHelpers (After) –

@model BikeStore.Models.LoginViewModel

@{
    ViewBag.Title = "Log in";
}

<h2>@ViewBag.Title.</h2>
<div class="row">
    <div class="col-md-8">
        <section id="loginForm">
            <form asp-action="Login" asp-controller="Account" asp-route-returnurl="@ViewBag.ReturnUrl"
                  method="post" class="form-horizontal" role="form" asp-anti-forgery="true">
                <h4>Use a local account to log in.</h4>
                <hr />
                <div asp-validation-summary="ValidationSummary.All" class = "text-danger"></div>
                <div class="form-group">
                    <label asp-for="UserName" class="col-md-2 control-label"></label>
                    <div class="col-md-10">
                        <input asp-for="UserName" class="form-control" />
                        <span asp-validation-for="UserName" class="text-danger"></span>
                    </div>
                </div>
                <div class="form-group">
                    <label asp-for="Password" class="col-md-2 control-label"></label>
                    <div class="col-md-10">
                        <input asp-for="Password" class="form-control" />
                        <span asp-validation-for="Password" class="text-danger"></span>
                    </div>
                </div>
                <div class="form-group">
                    <div class="col-md-offset-2 col-md-10">
                        <div class="checkbox">
                            <input asp-for="RememberMe" />
                            <label asp-for="RememberMe"></label>
                        </div>
                    </div>
                </div>
                <div class="form-group">
                    <div class="col-md-offset-2 col-md-10">
                        <input type="submit" value="Log in" class="btn btn-default" />
                    </div>
                </div>
                <p>
                    <a asp-action="Register">Register</a> if you don't have a local account.
                </p>
            </form>
        </section>
    </div>
</div>
@section Scripts {
    <script src="@Url.Content("~/lib/jquery-validation/jquery.validate.js")"></script>
    <script src="@Url.Content("~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js")"></script>
}

As with _ChangePasswordPartial.cshtml, the first thing we need to do when converting the Login.cshtml view is to replace the Html.BeginForm() and Html.AntiForgeryToken() method calls with a <form> element.

The next thing is to replace the Html.ValidationSummary() call with a <div> element. And then replace the Html.LabelFor() and Html.PasswordFor() calls with <input> elements.

The Html.TextBoxFor() and Html.CheckBoxFor() calls are also replaced with <input> elements. And as mentioned above, the InputTagHelper class will determine the types based on the data types of the target properties.

The Html.ValidationMessageFor() calls are replaced with <span> elements containing asp-validation-for attributes. Such elements are targeted by the ValidationMessageTagHelper class.

ValidationMessageTagHelper recognizes the following “asp-” attribute –

  • asp-validation-for
    • Expression to be evaluated against the Model, targeting a property of the Model
    • Implemented as type ModelExpression, for which the constructor takes a string
    • e.g. asp-validation-for=”UserName”

To complete the conversion of the Login.cshtml view, we replace Html.ActionLink() with a <a> element. The AnchorTagHeper class targets <a> elements and recognizes the following “asp-” attributes –

  • asp-action
    • The name of the action method
    • e.g. asp-action=”Register”
  • asp-controller
    • The name of the controller
    • e.g. asp-controller=”Account”
  • asp-fragment
    • URL fragment name, specifying a location in the document
    • e.g. asp-fragment=”section1″
  • asp-host
    • Host name for the URL
    • e.g. asp-host=”bikestore.com”
  • asp-protocol
    • Protocol for the URL
    • “http” or “https”
    • e.g. asp-protocol=”https”
  • asp-route
    • Name of the route
    • e.g. asp-route=”default”
  • asp-route-
    • Prefix for route attributes.
    • e.g. asp-route-returnurl=@ViewBag.ReturnUrl
    • e.g. asp-route-area=”Admin”

If the user has specified a “href” attribute, and has also specified one or more of the “asp-” attributes, then the AnchorTagHelper will raise an InvalidOperationException.

If asp-route is specified then the AnchorTagHelper will generate a anchor based on the route. Otherwise, the anchor will be generated based on the asp-controller and asp-action attributes.

Manage.cshtml

The Manage view does not require updating. It therefore remains as follows –

@{
    ViewBag.Title = "Manage Account";
}

<h2>@ViewBag.Title.</h2>
<p class="text-success">@ViewBag.StatusMessage</p>

<div class="row">
    <div class="col-md-12">
        @await Html.PartialAsync("_ChangePasswordPartial")
    </div>
</div>

@section Scripts {
    <script src="@Url.Content("~/lib/jquery-validation/jquery.validate.js")"></script>
    <script src="@Url.Content("~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js")"></script>
} 

Register.cshtml

HtmlHelpers (Before) –

@model BikeStore.Models.RegisterViewModel

@{
    ViewBag.Title = "Register";
}

<h2>@ViewBag.Title.</h2>

@using (Html.BeginForm("Register", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
    @Html.AntiForgeryToken()
    <h4>Create a new account.</h4>
    <hr />
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })
    <div class="form-group">
        @Html.LabelFor(m => m.UserName, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.UserName, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.PasswordFor(m => m.Password, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" class="btn btn-default" value="Register" />
        </div>
    </div>
}

@section Scripts {
    <script src="@Url.Content("~/lib/jquery-validation/jquery.validate.js")"></script>
    <script src="@Url.Content("~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js")"></script>
 } 

TagHelpers (After) –

@model BikeStore.Models.RegisterViewModel

@{
    ViewBag.Title = "Register";
}

<h2>@ViewBag.Title.</h2>

<form asp-action="Register" asp-controller="Account" method="post" class="form-horizontal" role="form" asp-anti-forgery="true">
    <h4>Create a new account.</h4>
    <hr />
    <div asp-validation-summary="ValidationSummary.All" class="text-danger"></div>
    <div class="form-group">
        <label asp-for="UserName" class="col-md-2 control-label"></label>
        <div class="col-md-10">
            <input asp-for="UserName" class="form-control" />
        </div>
    </div>
    <div class="form-group">
        <label asp-for="Password" class="col-md-2 control-label"></label>
        <div class="col-md-10">
            <input asp-for="Password" class="form-control" />
        </div>
    </div>
    <div class="form-group">
        <label asp-for="ConfirmPassword" class="col-md-2 control-label"></label>
        <div class="col-md-10">
            <input asp-for="ConfirmPassword" class="form-control" />
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" class="btn btn-default" value="Register" />
        </div>
    </div>
</form>

@section Scripts {
    <script src="@Url.Content("~/lib/jquery-validation/jquery.validate.js")"></script>
    <script src="@Url.Content("~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js")"></script>
 } 

The Html.BeginForm() and Html.AntiForgeryToken() method calls are replaced with a <form> element. The Html.ValidationSummary() call with a <div> element. And the Html.LabelFor(), Html.PasswordFor() and Html.TextBoxFor() calls are replaced with <input> elements.

_Layout.cshtml

HtmlHelpers (Before) –

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>

    <link rel="stylesheet" href="~/lib/bootstrap/css/bootstrap.css" />
    <link rel="stylesheet" href="~/css/site.css" />
    <link rel="stylesheet" href="~/lib/bootstrap-touch-carousel/css/bootstrap-touch-carousel.css" />
</head>
<body>
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                @Html.ActionLink("Application name", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li>@Html.ActionLink("Home", "Index", "Home")</li>
                    <li>@Html.ActionLink("About", "About", "Home")</li>
                    <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
                </ul>
                @await Html.PartialAsync("_LoginPartial")
            </div>
        </div>
    </div>
    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; @DateTime.Now.Year - My ASP.NET Application</p>
        </footer>
    </div>
    <script src="~/lib/jquery/jquery.js"></script>
    <script src="~/lib/bootstrap/js/bootstrap.js"></script>
    <script src="~/lib/hammer.js/hammer.js"></script>
    <script src="~/lib/bootstrap-touch-carousel/js/bootstrap-touch-carousel.js"></script>
    @RenderSection("scripts", required: false)
</body>
</html>

TagHelpers (After) –

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>
    <environment names="Development">
        <link rel="stylesheet" asp-href-include="{lib/**/*.css,css/site.css}" asp-href-exclude="lib/**/*.min.css" />
    </environment>
    <environment names="Staging,Production">
        <link rel="stylesheet" asp-href-include="{lib/**/*.min.css,css/site.css}" />
    </environment>
</head>
<body>
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a asp-action="Index" asp-controller="Home" class="navbar-brand" asp-route-area="">Application name</a>
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li><a asp-action="Index" asp-controller="Home">Home</a></li>
                    <li><a asp-action="About" asp-controller="Home">About</a></li>
                    <li><a asp-action="Contact" asp-controller="Home">Contact</a></li>
                </ul>
                @await Html.PartialAsync("_LoginPartial")
            </div>
        </div>
    </div>
    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; @DateTime.Now.Year - My ASP.NET Application</p>
        </footer>
    </div>
    <environment name="Development">
        <script asp-src-include="lib/**/*.js" asp-src-exclude="lib/**/*.min.js"></script>
    </environment>
    <environment names="Staging,Production">
        <script asp-src-include="lib/**/*.min.js"></script>
    </environment>
    @RenderSection("scripts", required: false)
</body>
</html>

The Html.ActionLink() calls have been replaced with <a> elements.

But the most interesting replacements are the stylesheet <link> and javascript <script> elements at the top and bottom of the view respectively. These have been replaced with a combination of <environment>, <link>, and <script> elements.

The EnvironmentTagHelper class introduces the <environment> tag, and enables the conditional rendering of content based on whether the current IHostingEnvironment.EnvironmentName matches one of a comma-delimited list of names.

The EnvironmentTagHelper class recognizes only one element –

  • names
    • A comma delimited list of environment names

Please note that the attribute name is not prefixed with “asp-“. I believe this is because the element is a TagHelpers construct rather than a HTML element. The same applies for the <cache> element which will be discussed later on in this post. Personally, for sake of consistency, I would have chosen to prefix these attribute names with “asp-“.

I have used the elements in the _Layout.cshtml form to include the non-minified versions of the stylesheets and javascript files when we are rendering views in the Development environment, and the minified versions when we are rendering views in the Staging and Production environments.

The LinkTagHelper class targets <link> elements that contain one or more of the “asp-” attributes shown below. It supports file name expansion (globbing) patterns, and fallback paths.

The LinkTagHelper recognizes the following “asp-” attributes –

  • asp-href-include
    • A comma separated list of globbed file patterns of CSS stylesheets to load.
    • e.g. asp-href-include=”css/**/*.css”
  • asp-href-exclude
    • A comma separated list of globbed file patterns of CSS stylesheets to exclude from loading.
    • e.g. asp-href-exclude=”css/**/*.min.css”
  • asp-fallback-href
    • The URL of a CSS stylesheet to fallback to in the case the primary one fails.
  • asp-fallback-href-include
    • A comma separated list of globbed file patterns of CSS stylesheets to fallback to in the case the primary one fails.
  • asp-fallback-href-exclude
    • A comma separated list of globbed file patterns of CSS stylesheets to exclude from the fallback list, in the case the primary one fails.
  • asp-fallback-test-class
    • The class name defined in the stylesheet to use for the fallback test.
  • asp-fallback-test-property
    • The CSS property name to use for the fallback test.
  • asp-fallback-test-value
    • The CSS property value to use for the fallback test.
  • asp-file-version
    • Boolean indicating if file version should be appended to the href urls
    • e.g. asp-file-version=”true”

The ScriptTagHelper targets <script> elements that contain one or more of the “asp-” attributes shown below. It also supports file name expansion (globbing) patterns, and fallback paths.

The  ScriptTagHelper recognizes the following “asp-” attributes –

  • asp-src-include
    • A comma separated list of globbed file patterns of JavaScript scripts to load.
    • e.g. asp-src-include=”js/**/*.js”
  • asp-src-exclude
    • A comma separated list of globbed file patterns of JavaScript scripts to exclude from loading.
    • asp-src-exclude=”*.min.js”
  • asp-fallback-src
    • The URL of a Script tag to fallback to in the case the primary one fails.
  • asp-fallback-src-include
    • A comma separated list of globbed file patterns of JavaScript scripts to fallback to in the case the primary one fails.
  • asp-fallback-src-exclude
    • A comma separated list of globbed file patterns of JavaScript scripts to exclude from the fallback list, in the case the primary one fails.
  • asp-fallback-test
    • The script method defined in the primary script to use for the fallback test.
  • asp-file-version
    • Boolean indicating if the file version should be appended to the src urls
    • e.g. asp-file-version=”false”

_LoginPartial.cshtml

HtmlHelpers (Before) –

@using System.Security.Principal

@if (User.Identity.IsAuthenticated)
{
    using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm", @class = "navbar-right" }))
    {
        @Html.AntiForgeryToken()
        <ul class="nav navbar-nav navbar-right">
            <li>
                @Html.ActionLink("Hello " + User.Identity.GetUserName() + "!", "Manage", "Account", routeValues: null, htmlAttributes: new { title = "Manage" })
            </li>
            <li><a href="javascript:document.getElementById('logoutForm').submit()">Log off</a></li>
        </ul>
    }
}
else
{
    <ul class="nav navbar-nav navbar-right">
        <li>@Html.ActionLink("Register", "Register", "Account", routeValues: null, htmlAttributes: new { id = "registerLink" })</li>
        <li>@Html.ActionLink("Log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })</li>
    </ul>
}

TagHelpers (After) –

@using System.Security.Principal

@if (User.Identity.IsAuthenticated)
{
    <form asp-action="LogOff" asp-controller="Account" action="post" id="logoutForm" class="navbar-right" asp-anti-forgery="true">
        <ul class="nav navbar-nav navbar-right">
            <li>
                <a asp-action="Manage" asp-controller="Account" title="Manage">Hello @User.Identity.GetUserName() !</a>
            </li>
            <li><a href="javascript:document.getElementById('logoutForm').submit()">Log off</a></li>
        </ul>
    </form>
}
else
{
    <ul class="nav navbar-nav navbar-right">
        <li><a asp-action="Register" asp-controller="Account" id="registerLink">Register</a></li>
        <li><a asp-action="Login" asp-controller="Account" id="loginLink">Log in</a></li>
    </ul>
}

The Html.BeginForm() and Html.AntiForgeryToken() callshave been replaced with a <form> element. And the Html.ActionLink() calls have been replaced with <a> elements. This completes the changes required to convert the out-of-the-box views to use the TagHelper classes.

So far we have used and discussed the following TagHelper classes –

  • AnchorTagHelper
  • EnvironmentTagHelper
  • FormTagHelper
  • InputTagHelper
  • LabelTagHelper
  • LinkTagHelper
  • ScriptTagHelper
  • ValidationMessageTagHelper
  • ValidationSummaryTagHelper

There are a few more, which I’d like to briefly discuss –

  • CacheTagHelper
  • SelectTagHelper
  • OptionTagHelper
  • TextAreaTagHelper

The CacheTagHelper targets <cache> elements, which have been introduced with ASP.NET 5 to enable the developer to mark fragments of a view to be cached in the .NET in-memory cache. 

In prior versions of MVC, it has been easy to cache a view by applying the [OutputCache] attribute to a controller’s action method. But if we wanted to cache only a fragment of the view – i.e. donut hole caching – then we would have to move the fragment to a partial view, and then create an action method decorated with the [OutputCache] attribute to render the partial view. We would then use the Html.Action() method to inject the partial view into the parent.

The CacheTagHelper and <cache> attribute enable us to implement donut caching declaratively, without the need to move the cached fragment to a partial view. This is much easier to implement and modify, and makes the views much more readable.

The CacheTagHelper recognizes the following “asp-” attributes –

  • vary-by
    • A string to vary the cached result by
    • vary-by=“@item.Name
  • vary-by-header
    • Name of a HTTP request header to vary the cached result by
    • e.g. vary-by-header=”User-Agent”
  • vary-by-query
    • A comma-delimited set of query string parameters to vary the cached result by
    • e.g. var-by-query=”page,category”
  • vary-by-route
    • A comma-delimited set of route data parameters to vary the cached result by
    • vary-by-route=category
  • vary-by-cookie
    • A comma-delimited set of cookie names to vary the cached result by
    • e.g. vary-by-cookie=”user”
  • vary-by-user
    • A bool that determines if the cached result is to be varied by the Identity for the logged in
    • e.g. vary-by-user=”true”
  • expires-on
    • The DateTimeOffset (point in time) the cache entry should be evicted
  • expires-after
    • A TimeSpan duration, from the time the cache entry was added, when it should be evicted
    • expires-after=“@TimeSpan.FromSeconds(60)
  • expires-sliding
    • A TimeSpan duration from the last access that the cache entry should be evicted.
  • priority
    • The CachePreservationPriority policy that specifies how items are prioritized for preservation during a memory pressure triggered cleanup.

The CacheTagHelper will generate a key to uniquely identify each fragment, based on the context and the values supplied for the vary-by and vary-by-* attributes. It will expire the cached entry for a fragment based on the expires-* attributes.

The SelectTagHelper class targets <select> elements containing asp-for attributes, and recognizes the following “asp-” attributes –

  • asp-for
    • Expression to be evaluated against the Model, targeting a property of the Model
    • Implemented as type ModelExpression, for which the constructor takes a string
    • e.g. asp-for=”ProductId”
  • asp-items
    • A collection of SelectListItem objects used to populate the <select> element with <option> elements.
    • Type is IEnumerable<SelectListItem>
    • e.g. asp-items=”ProductsList”
    • e.g. asp-items=”ViewBag.Products”

If the asp-for expression is evaluated as a property that implements IEnumerable, and as such may contain multiple values, then the SelectTagHelper will generate a multiselect list.

The SelectTagHelper class appends the generated <option> elements to any <option> elements that the user has provided in the markup. And it will only select generated options based on the asp-for expression. The selection of any user provided options is dealt with by the OptionTagHelper class.

The OptionTagHelper class targets <option> elements that are children of <select> elements that have been targeted by the SelectTagHelper class. It does not recognize any “asp-” attributes. It selects the user defined <option> elements based on the parent <select> element’s asp-for expression.

The TextAreaTagHelper class targets <textarea> elements containing asp-for attributes, which are alternatives to Html.TextAreaFor() method calls.

The TextAreaTagHelper class recognizes only one “asp-” attribute –

  • asp-for
    • Expression to be evaluated against the Model, targeting a property of the Model
    • Implemented as type ModelExpression, for which the constructor takes a string
    • e.g. asp-for=”Description”

Summary

I hope you agree that views containing TagHelpers are better structured and more readable than views containing HtmlHelpers methods. They are closer in format to “raw” Html and as such will make collaboration between developers and designers much easier.

Also, TagHelpers are easier to code than HtmlHelpers. We are no longer working with many overloads of HtmlHelper methods. And we can apply HTML attributes directly to the elements rather than having to construct an anonymous class and escape any attribute names that may collide with C# reserved keywords.

So in my opinion, TagHelpers area a great step forward from HtmlHelpers.

Porting legacy applications to ASP.NET 5

ASP.NET 5 (vNext) will be released with Visual Studio 2015.

Microsoft has embraced open source, and is sharing the development of ASP.NET 5 via GitHub – https://github.com/aspnet. This is a great decision on Microsoft’s part. It’s engaging for the development community, helping to build both knowledge, loyalty, and momentum for the product. It also provides Microsoft with a huge resource of mostly valuable professionals – MVP pun intended – helping test and drive improvements to the features. When the product does finally come to market there will already be a wealth of knowledge and articles on the web ready to go.

Currently at version CTP 6, Visual Studio 2015 and ASP.NET 5 are looking pretty solid so far. Microsoft hasn’t given a specific release date, But my guess is some time in Q3.

I must admit to being quite excited about the new features – I know, I need to get out more. And I look forward to using them in anger. But obviously it would not be wise to commit to upgrading to Visual Studio 2015 and ASP.NET 5, in a production environment, until product development is complete and there is a stable release available. This may be release 1 or possibly SP1 depending on your risk propensity.

You would have to be sure that legacy solutions, built on earlier versions of .NET and Visual Studio, could be easily ported into Visual Studio 2015. OK, you can run versions of .NET and Visual Studio side-by-side in a development environment. But how many versions do you want cluttering up your development servers? Best to have only 1 version, particularly when provisioning new development servers, and when recruiting and training new developers.

Well, the word from Microsoft with regard to porting across legacy applications to Visual Studio 2015 is as follows –

http://www.asp.net/vnext/overview/aspnet-vnext/aspnet-5-overview#legacy

You may be worried that, with the number of changes in ASP.NET 5, you now need to re-write all of your applications. Don’t worry. Applications that you built on earlier versions of ASP.NET will continue to work with the new .NET Framework. You do not need to update or port these applications if you do not need the new features in ASP.NET 5.

Great news if it works. But of course the proof of the pudding is in the eating. I thought I would try it out.

My pet BikeStore solution is comprised of an MVC 5 web application, a number of class libraries, a unit test project, and a WCF service application. So a variety of project types.

Here’s the project in VS 2013, with the web application’s properties displayed –

VS2013

And the project opens in VS 2015 CTP 6, without needing any intervention at all –

VS2015

And yes, I did do a double check as the IDE, and properties windows are almost identical. The second image is definitely 2015.

I am able to compile and run the web application, run the tests, and debug the WCF service classes using the WCF test client.

In comparison, if I create a new ASP.NET 5 Preview application, this is what I get for the project properties –

vNext

It’s very much simplified due in part to some of the new ASP.NET 5 features. And this is a key point. Yes, it looks like we can easily port legacy applications to VS 2015 – although maybe there will be issues with very early versions of .NET and VS. The legacy environment and features are pretty much replicated. But what about the new ASP.NET 5 features? There’s a lot of great stuff, such as the cross-platform runtime, simplified dependency management, and some cracking improvement to MVC. I suspect that to use this features with a legacy application, the solution will need to be built from the ground up, starting with new projects, and then adding the existing classes and dependencies. Whether all the existing functionality will work with the new CLR and new version of MVC – your guess is as good as mine. I will be finding out though as I’m going to be “Re-writing” my BikeStore application to use the new features. I will share my experiences in future blog posts.

Bottom line is that ASP.NET 5 is significantly different from earlier versions.

If you have a legacy application that you are maintaining and will require incremental changes going forward, then by all means open and maintain the solution using VS 2015 – when a stable version is available.

If you are planning on a major change to an existing legacy application, and thus will be making significant modifications and extensions, then it may be worthwhile fully migrating to ASP.NET 5 by starting with a new ASP.NET 5 solution. This will future proof the application, and will make the developers very happy – and happy developers are good developers 😉

 

ASP.NET 5 vNext Dependency Injection

I spent the whole day going around in circles attempting to fix an application that I broke. It’s not the first time, and I’m sure it won’t be the last.

It’s a pet application of mine that I work on from time to time to keep up to date with the latest Visual Studio and ASP.NET features. I thought it would be a good idea to upgrade from MVC 4 to 5, as a precursor to porting the solution across to Visual Studio 2015. So off I went half-cocked and installed the ASP.NET MVC 5.2.2 NuGet package, which in turn upgraded WebPages and Razor from version 2 to version 3. But in typical, if it ain’t broke then don’t fix it, style my application imploded and refused to compile. A couple of hours later and it was compiling, but Unity was no longer resolving dependencies even though the types were correctly registered in the container. Bottom line was that the version of Unity that I was using depended on version 2 of WebPages and Razor, and as such does not sit well with MVC 5. Once I had worked this out, I was able to upgrade Unity and all was well.

I did not really put a great deal of thought into the upgrade. If I had I would have realized that what I should be doing is porting the MVC 4 solution across to Visual Studio 2015 and then upgrading to MVC 5, whilst removing Unity completely. The reason being is that one of the neat features that will be introduced with ASP.NET 5 is out-of-the-box dependency injection. And out-of-the-box means no need to shoe-horn 3rd party packages/assemblies into the solution – unless you really want to.

What I’d like to demonstrate is the steps you need to take to implement Unity for constructor injection in an MVC 4 project, and how the ASP.NET 5 out-of-the-box dependency injection makes life a whole lot easier.

Implementing Unity in an MVC 4 project

Create a new ASP.NET MVC 4 and install the Unity Bootstrapper package

I’m using Visual Studio 2013 for this demo. If you have Visual Studio 2012 then the steps and outcomes should be the same, although I haven’t tested this. If you have Visual Studio 2010 SP1 then in theory you can download the MVC 4 framework. But it’s probably better to request an upgrade. Although I appreciate that this is not always possible given company and project constraints.

Create a new Visual C# ASP.NET project, and then instal the Unity Bootstrapper for ASP.NET MVC NuGet package –

NuGet Unity

The Unity Bookstrapper package adds 2 class libraries to the App_Start folder – UnityConfig.cs and UnityMvcActivator.cs –

Unity Solution

UnityMvcAcvtivator.cs

The UnityMvcActivator class manages the life-time of the Unity container. It utilises the WebActivatorEx module to instantiate and configure the container during application start-up, and to dispose of the container during shut-down,

An alternative to using WebActivatorEx is to modify the Global.asax Application_Start and Application_End events to instantiate and dispose of the container. However, NuGet cannot do this automatically. It is something you would have to code manually.

UnityMvcActivator

The Start() method is executed during application start-up. The key point to getting Unity up and running is that UnityWebActivator calls the UnityConfig.GetConfiguredContainer() method to get a configured Unity container to use when resolving dependencies. We need to tell the system how to resolve the dependencies. We do this by registering the types – which we will do next.

UnityConfig.cs

The GetConfiguredContainer() method returns an instantiated Unity container that includes the details of which concrete types to instantiate when it instantiates an object that requires the injection of an interface.

In the example code below I have registered ProductService as the concrete implementation of IProductService.

UnityConfig

In the next section I will implement IProductService, and ProductService. I will then modify the HomeController to require IProductService to be injected into it’s constructor. And then show that the dependency injection is working as expected.

IProductService and ProductService

To keep things simple, the IProductService interface exposes a single method, WhoIAm(), which returns a string.

IProductService

The ProductService class implements this method, returning “I am product service”.

ProductService

Injecting IProductService into HomeController

I have modified HomeController as follows –

  • Added _productService as a private field of type IProductService
  • Added a constructor which receives an instance of IProductService, and assigns this instance to the _productService field.
  • Modified the Index() method to return the result of the _productService.WhoIAm() call.

HomeController

Running the Project

Running the project results in the following action result from the Home/Index method –

UnityResult

This proves that Unity is correctly injecting an instance of the concrete ProductService class into the HomeController constructor.

It’s as easy as that. And it gets even easier with ASP.NET 5 out-of-box dependency injection.

Implementing Dependency Injection in an ASP.NET 5 vNext MVC Project

For this example I’m going to boot up Visual Studio 2015 preview.

This time I’m going to create a new ASP.NET 5 vNext Starter Web project.

New vNext Project

IProductService, ProductService, and HomeController changes

I created the same IProductService and ProductService classes. And injected the IProductService class into the HomeController’s constructor. So exactly the same code as before.

IProductService

ProductService

HomeController

As an aside, check out the hints telling us how many references each class and method has. This is a by-product of another really neat vNext feature – dynamic compilation.

Register the ProductService Type

As the dependency injection is out-of-the-box we do not need to install and configure any packages. All we need to do is register our concrete types.

The Startup class has a ConfigureServices method. The runtime calls this method, and passes through an instance of a class which implements the Microsoft.Framework.DependencyInjection.IServiceCollection interface.

All we need to do register the ProductService type, with the instance of IServiceCollection, as the concrete implementation for IProductService –

 services.AddTransient<IProductService, ProductService>();

Startup

And Bob’s your Uncle

Results

So the out-of-the-box implementation works – as it’s name suggests – straight out-of-the-box, without the need to install additional dependencies, or create the code to manage the life-cycle of the dependency injection container. All that is required is for the concrete types to be registered.

One thing to note is that at the time of writing vNext only supports constructor injection. It does not support property setter or method call injection. Not that this is an issue for me, as I only ever use constructor injection.