Make sure your _reference.js references are in the correct order!

I’m messing about with angular at the moment, and I wasted an hour tonight trying to work out why javascript intellisense was not playing ball in Visual Studio 2015.

Mads Kristenson has written some great articles on javascript intellisense – here and here. He details the whys and wherefores, and in particular how the _references.js file controls which javascript files are included in javascript intelligence. I’m not going to detail how it works here, so check out Mads’ articles if you want to know more.

A typical _references.js file, and the one I was having problems with, looks like this –

/// <autosync enabled="true" />
/// <reference path="../js/app.js" />
/// <reference path="../js/project.js" />
/// <reference path="angular.min.js" />
/// <reference path="angular-animate.min.js" />
/// <reference path="angular-aria.min.js" />
/// <reference path="angular-cookies.min.js" />
/// <reference path="angular-loader.min.js" />
/// <reference path="angular-message-format.min.js" />
/// <reference path="angular-messages.min.js" />
/// <reference path="angular-mocks.js" />
/// <reference path="angular-resource.min.js" />
/// <reference path="angular-route.min.js" />
/// <reference path="angular-sanitize.min.js" />
/// <reference path="angular-scenario.js" />
/// <reference path="angular-touch.min.js" />
/// <reference path="bootstrap.js" />

Topping the file is the autosync directive which tells VS to automatically add, remove, and update references for all javascript files in the project. This is an awesome feature.

Having added the ../js/app.js and ../js/project.js files to my project, autosync added the references towards the top of the _references.js file, as shown above.

My app.js file simply defines an angular module, and as you can see below that intellisense is available for the angular object which is defined in the angular.min.js file.

intellisense1

However, when editing my project.js file, which extends the app module, there was no intellisense available for any of the angular objects.

intellisense2

This is where I spent a solid hour removing and adding references, trying out different intellisense configuration options, and basically going around in circles.

I finally tried reordering the references so that the angular files were referenced before my files.

/// <autosync enabled="true" />
/// <reference path="angular.min.js" />
/// <reference path="angular-animate.min.js" />
/// <reference path="angular-aria.min.js" />
/// <reference path="angular-cookies.min.js" />
/// <reference path="angular-loader.min.js" />
/// <reference path="angular-message-format.min.js" />
/// <reference path="angular-messages.min.js" />
/// <reference path="angular-mocks.js" />
/// <reference path="angular-resource.min.js" />
/// <reference path="angular-route.min.js" />
/// <reference path="angular-sanitize.min.js" />
/// <reference path="angular-scenario.js" />
/// <reference path="angular-touch.min.js" />
/// <reference path="bootstrap.js" />
/// <reference path="../js/app.js" />
/// <reference path="../js/project.js" />

And lo and behold, javascript intellisense now works. Doh!

intellisense3

To be exact, I’m using Visual Studio Community 2015 RTM, version 14.0.23107. I haven’t tried this out with any other version of Visual Studio. So it may simply be one of a number of teething problems with 2015 which will be ironed out. Let’s hope so. But if you experience this in another version, then you know what to do.

 

 

 

Advertisements

Zen Coding with Visual Studio

Zen Coding is a plugin that can significantly speed up the generation of HTML.

It enables the coder to enter HTML using CSS-style abbreviations, which are then expanded into full HTML.

Plugins are available for a variety of editors. For Visual Studio, Zen Coding functionality is incorporated into the Web Essential extension, which can be downloaded from here – http://vswebessentials.com/download

I’m going to demonstrate how to code HTML using Zen Coding abbreviations. This is the HTML snippet that I’m going to create –

<div id="wrap" class="pnl pnl-lg">
    <h1>Zen Test</h1>
    <div class="content">
        <p>Here's some paragraph text</p>
        <div class="box"></div>
    </div>
    <aside class="sidebar">
        <ul>
            <li id="listItem_1"><a id="link1" href="" target="_blank">Link 1</a></li>
            <li id="listItem_2"><a id="link2" href="" target="_blank">Link 2</a></li>
            <li id="listItem_3"><a id="link3" href="" target="_blank">Link 3</a></li>
        </ul>
    </aside>
</div>

I will do this step-by-step, introducing a feature with each step.

 HTML Elements

Open an HTML or CSHTML page.

Type div and then hit the [TAB] key. The editor expands the abbreviation into full HTML.

div [tab]

<div></div>

Class and ID Atrributes

A class attribute can be applied to an element by appending .classname to the element.

An id can be applied by appending #id.

Multiple classes can be applied by chaining the class names.

div.pnl [tab]

<div class="pnl"></div>

div#wrap [tab]

<div id="wrap"></div>

div.pnl.pnl-lg#wrap [tab]

<div id="wrap" class="pnl pnl-lg"></div>

Nesting Elements

A right-angled bracket, >, is used to denote nested or child elements. 

div.pnl.pnl-lg#wrap>h1 [tab]

<div id="wrap" class="pnl pnl-lg">
    <h1></h1>
</div>

Sibling Elements

To add multiple sibling elements, use +.

div.pnl.pnl-lg#wrap>h1+div.content+aside.sidebar [tab]

<div id="wrap" class="pnl pnl-lg">
    <h1></h1>
    <div class="content"></div>
    <aside class="sidebar"></aside>
</div>

Element Multiplication

Appending *n to an element multiplies the element n times.

In the example below li*3 outputs 3 <li> elements.

div.pnl.pnl-lg#wrap>h1+div.content>p+div.box+aside.sidebar>ul>li*3 [tab]

<div id="wrap" class="pnl pnl-lg">
    <h1></h1>
    <div class="content">
        <p></p>
        <div class="box"></div>
        <aside class="sidebar">
            <ul>
                <li></li>
                <li></li>
                <li></li>
            </ul>
        </aside>
    </div>
</div>

Grouping

Elements can be grouped together using parenthesis, ( ).

In the example above the parser has output the <aside> elements within the <div class=”content”> element.

The example below uses parenthesis to group elements, hence outputting the <aside> as a sibling of the <div>.

div.pnl.pnl-lg#wrap>h1+(div.content>p+div.box)+(aside.sidebar>ul>li*3) [tab]

<div id="wrap" class="pnl pnl-lg">
    <h1></h1>
    <div class="content">
        <p></p>
        <div class="box"></div>
    </div>
    <aside class="sidebar">
        <ul>
            <li></li>
            <li></li>
            <li></li>
        </ul>
    </aside>
</div>

Custom Attributes

Custom attributes can be added to an element by appending the element with the attribute name and value in square brackets, [ ].

[target=_blank] is appended to the <a> attribute below.

div.pnl.pnl-lg#wrap>h1+(div.content>p+div.box)+(aside.sidebar>ul>li*3>a#link[target=_blank]) [tab]

<div id="wrap" class="pnl pnl-lg">
    <h1></h1>
    <div class="content">
        <p></p>
        <div class="box"></div>
    </div>
    <aside class="sidebar">
        <ul>
            <li><a id="link" href="" target="_blank"></a></li>
            <li><a id="link" href="" target="_blank"></a></li>
            <li><a id="link" href="" target="_blank"></a></li>
        </ul>
    </aside>
</div>

Item Numbering

Item numbering for multiple elements can be added by applying the $ character.

Item numbering can be added to attributes as well as text.

And the numbering can be padded with zeros by applying multiple $ characters, e.g. $$

div.pnl.pnl-lg#wrap>h1+(div.content>p+div.box)+(aside.sidebar>ul>li*3>a#link$[target=_blank]) [tab]

<div id="wrap" class="pnl pnl-lg">
    <h1></h1>
    <div class="content">
        <p></p>
        <div class="box"></div>
    </div>
    <aside class="sidebar">
        <ul>
            <li><a id="link1" href="" target="_blank"></a></li>
            <li><a id="link2" href="" target="_blank"></a></li>
            <li><a id="link3" href="" target="_blank"></a></li>
        </ul>
    </aside>
</div>

 Text

Curly brackets, { }, are used to denote text.

div.wrap>h1{Zen Test}+(div.content>p{Here's some paragraph text}+div.box)+(aside.sidebar>ul>li#listItem_$*3>a#link$[target=_blank]{Link $}) [tab]

<div class="wrap">
    <h1>Zen Test</h1>
    <div class="content">
        <p>Here's some paragraph text</p>
        <div class="box"></div>
    </div>
    <aside class="sidebar">
        <ul>
            <li id="listItem_1"><a id="link1" href="" target="_blank">Link 1</a></li>
            <li id="listItem_2"><a id="link2" href="" target="_blank">Link 2</a></li>
            <li id="listItem_3"><a id="link3" href="" target="_blank">Link 3</a></li>
        </ul>
    </aside>
</div>

For more details on the Zen Coding standard, a good starting place is the Zen Coding project repository – https://code.google.com/p/zen-coding/

 

 

 

 

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 😉

 

Browser Link

Yesterday, I was debugging an MVC.NET project – attempting to work out why styles were not being rendered correctly in one of my areas. Of course it turned out to be a swivel connectivity* error – as is normally the case.

In the process of debugging I used the browser’s F12 developer tools to view the network requests and statuses. What I noticed – in addition to the expected page and resource requests – was that the browser was repeatedly polling the server at 10 second intervals.

A year or so ago, I did a demo for a bunch of SharePoint developers on SignalR. I won’t go into detail on what SignalR is as you can read all about it here –

http://www.asp.net/signalr

In summary, it’s a library  – introduced with .net 4.5 – that makes “developing real-time web functionality easy”. Assuming the server and browser are capable, it establishes a WebSockets connection, which essentially is a long running bi-directional communication channel between the browser and the server.  HTTP, in comparison, can be thought of as a uni-directional channel, as the browser initiates the communication by requesting a resource. And after some handshaking the connection is established and the response is sent by the server. Once sent, the connection is closed.

In the SignalR demo I covered the theory, looked at some sample code, and then demonstrated a real-world business application which made an asynchronous call to initiate a server intensive process, and then used SignalR to show detailed real-time progress indicators in the browser. All great stuff, but as the demo was straight after lunch – the dead zone – there were a few yawns. They did however wake up a bit when I showed how when I moved Victor Meldrew’s decapitated head around one browser window, it was moving around on all others browsers that had established connections.

Anyway, back to the unexpected polling. The requests were consistent with SignalR. My initial thought was that I had somehow merged some SignalR code with the project I was working on. It was late at night, and so my brain was getting a little befuddled. I soon ruled this out and then asked my pal Google.

To cut a long story short, Visual Studio 2013 introduced a feature called Browser Link. It creates a communication channel between Visual Studio and one or more browsers when in debug mode, enabling visual studio to refresh the content in all browsers in unison.

http://www.asp.net/visual-studio/overview/2013/using-browser-link

So a bi-directional communication channel between Visual Studio and browsers. And guess what – it uses SignalR to enable this communication. It does this by injecting a script block at the end of the Body element in each HTML page that it serves up. The script block contains functions that execute when the page is rendered by the browser, and attempt to establish the SignalR connection with the server. The script firstly does the negotiating between the browser and the server, where it determines whether both parties are capable of upgrading the HTTP connection to WebSockets. If yes, it upgrades the connection, but as you can see below, in my case it did not. Instead it went to the fall back position – of establishing a Javascipt timer interval to poll the server every 10 seconds.

W7BrowserLink

So why was the connection not upgraded to WebSockets? I was using the latest version of Chrome (v39) and targeting .NET 4.5. both of which have the capabilities. The answer is that my client was a Windows 7 client, and .NET 4.5 only enables WebSockets on Windows 8 and above.

I don’t have a Windows 8 client to prove this, but I do have a brand spanking new Windows 10 Preview environment with Visual Studio 2015 Preview begging to be christened. And here are the Browser Link calls when running the app on Windows 10 against the same version of Google Chrome –

Win10BrowserLink

This time, after the negotiation, the protocol is switched (Status 101) to WebSockets. The WebSockets connection remains open and so there is no need to repeatedly poll the server as per the Windows 7 example.

* I’m sure most of you are too young to remember the early days of t’interweb when you had to request internet access based on a business need and have it signed off by the CTO. There was a rush to get an online presence, and eCommerce sites were initially implemented with little or no integration with internal systems. An order entry clerk would have a PC and a terminal on their desk. They read an online order’s details from the PC, swiveled their chair to the terminal to enter the details on to the internal systems, and then swiveled their chair back to the PC to confirm the order. Needless to say there were lots of “swivel connectivity” errors.