Skip to main content

8 posts tagged with "asp.net"

View All Tags

ASP.NET, Serilog and Application Insights

If you're deploying an ASP.NET application to Azure App Services, there's a decent chance you'll also be using the fantastic Serilog and will want to plug it into Azure's Application Insights.

This post will show you how it's done, and it'll also build upon the build info work from our previous post. In what way? Great question. Well logs are a tremendous diagnostic tool. If you have logs which display some curious behaviour, and you'd like to replicate that in another environment, you really want to take exactly that version of the codebase out to play. Our last post introduced build info into our application in the form of our AppVersionInfo class that looks something like this:

{
"buildNumber": "20210130.1",
"buildId": "123456",
"branchName": "main",
"commitHash": "7089620222c30c1ad88e4b556c0a7908ddd34a8e"
}

We'd initially exposed an endpoint in our application which surfaced up this information. Now we're going to take that self same information and bake it into our log messages by making use of Serilog's enrichment functionality. Build info and Serilog's enrichment are the double act your logging has been waiting for.

Let's plug it together#

We're going to need a number of Serilog dependencies added to our .csproj:

<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.1.3" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
<PackageReference Include="Serilog.Sinks.ApplicationInsights" Version="3.1.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.4.0" />

The earlier in your application lifetime you get logging wired up, the happier you will be. Earlier, means more information when you're diagnosing issues. So we want to start in our Program.cs; Startup.cs would be just way too late.

public class Program {
const string APP_NAME = "MyAmazingApp";
public static int Main(string[] args) {
AppVersionInfo.InitialiseBuildInfoGivenPath(Directory.GetCurrentDirectory());
LoggerConfigurationExtensions.SetupLoggerConfiguration(APP_NAME, AppVersionInfo.GetBuildInfo());
try
{
Log.Information("Starting web host");
CreateHostBuilder(args).Build().Run();
return 0;
}
catch (Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly");
return 1;
}
finally
{
Log.CloseAndFlush();
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog((hostBuilderContext, services, loggerConfiguration) => {
loggerConfiguration.ConfigureBaseLogging(APP_NAME, AppVersionInfo.GetBuildInfo());
loggerConfiguration.AddApplicationInsightsLogging(services, hostBuilderContext.Configuration);
})
.ConfigureWebHostDefaults(webBuilder => {
webBuilder
.UseStartup<Startup>();
});
}

If you look at the code above you'll see that the first line of code that executes is AppVersionInfo.InitialiseBuildInfoGivenPath. This initialises our AppVersionInfo so we have meaningful build info to pump into our logs. The next thing we do is to configure Serilog with LoggerConfigurationExtensions.SetupLoggerConfiguration. This provides us with a configured logger so we are free to log any issues that take place during startup. (Incidentally, after startup you'll likely inject an ILogger into your classes rather than using the static Log directly.)

Finally, we call CreateHostBuilder which in turn calls UseSerilog to plug Serilog into ASP.NET. If you take a look inside the body of UserSerilog you'll see we configure the logging of ASP.NET (in the same we did for Serilog) and we hook into Application Insights as well. There's been a number of references to LoggerConfigurationExtensions. Let's take a look at it:

internal static class LoggerConfigurationExtensions {
internal static void SetupLoggerConfiguration(string appName, BuildInfo buildInfo) {
Log.Logger = new LoggerConfiguration()
.ConfigureBaseLogging(appName, buildInfo)
.CreateLogger();
}
internal static LoggerConfiguration ConfigureBaseLogging(
this LoggerConfiguration loggerConfiguration,
string appName,
BuildInfo buildInfo
) {
loggerConfiguration
.MinimumLevel.Debug()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
// AMAZING COLOURS IN THE CONSOLE!!!!
.WriteTo.Async(a => a.Console(theme: AnsiConsoleTheme.Code))
.Enrich.FromLogContext()
.Enrich.WithMachineName()
.Enrich.WithThreadId()
// Build information as custom properties
.Enrich.WithProperty(nameof(buildInfo.BuildId), buildInfo.BuildId)
.Enrich.WithProperty(nameof(buildInfo.BuildNumber), buildInfo.BuildNumber)
.Enrich.WithProperty(nameof(buildInfo.BranchName), buildInfo.BranchName)
.Enrich.WithProperty(nameof(buildInfo.CommitHash), buildInfo.CommitHash)
.Enrich.WithProperty("ApplicationName", appName);
return loggerConfiguration;
}
internal static LoggerConfiguration AddApplicationInsightsLogging(this LoggerConfiguration loggerConfiguration, IServiceProvider services, IConfiguration configuration)
{
if (!string.IsNullOrWhiteSpace(configuration.GetValue<string>("APPINSIGHTS_INSTRUMENTATIONKEY")))
{
loggerConfiguration.WriteTo.ApplicationInsights(
services.GetRequiredService<TelemetryConfiguration>(),
TelemetryConverter.Traces);
}
return loggerConfiguration;
}
}

If we take a look at the ConfigureBaseLogging method above, we can see that our logs are being enriched with the build info, property by property. We're also giving ourselves a beautifully coloured console thanks to Serilog's glorious theme support:

Take a moment to admire the salmon pinks. Is it not lovely?

Finally we come to the main act. Plugging in Application Insights is as simple as dropping in loggerConfiguration.WriteTo.ApplicationInsights into our configuration. You'll note that this depends upon the existence of an application setting of APPINSIGHTS_INSTRUMENTATIONKEY - this is the secret sauce that we need to be in place so we can pipe logs merrily to Application Insights. So you'll need this configuration in place so this works.

As you can see, we now have the likes of BuildNumber, CommitHash and friends visible on each log. Happy diagnostic days!

I'm indebted to the marvellous Marcel Michau who showed me how to get the fiddlier parts of how to get Application Insights plugged in the right way. Thanks chap!

Autofac 6, integration tests and .NET generic hosting

I blogged a little while ago around to support integration tests using Autofac. This was specific to Autofac but documented a workaround for a long standing issue with ConfigureTestContainer that was introduced into .NET core 3.0 which affects all third-party containers that use ConfigureTestContainer in their tests.

I'll not repeat the contents of the previous post - it all still stands. However, with Autofac 6 the approach documented there will cease to work. This is because the previous approach relied upon ContainerBuilder not being sealed. As of Autofac 6 it is.

Happily the tremendous Alistair Evans came up with an alternative approach which is listed below:

/// <summary>
/// Based upon https://github.com/dotnet/AspNetCore.Docs/tree/master/aspnetcore/test/integration-tests/samples/3.x/IntegrationTestsSample
/// </summary>
/// <typeparam name="TStartup"></typeparam>
public class AutofacWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
protected override IHost CreateHost(IHostBuilder builder)
{
builder.UseServiceProviderFactory<ContainerBuilder>(new CustomServiceProviderFactory());
return base.CreateHost(builder);
}
}
/// <summary>
/// Based upon https://github.com/dotnet/aspnetcore/issues/14907#issuecomment-620750841 - only necessary because of an issue in ASP.NET Core
/// </summary>
public class CustomServiceProviderFactory : IServiceProviderFactory<ContainerBuilder>
{
private AutofacServiceProviderFactory _wrapped;
private IServiceCollection _services;
public CustomServiceProviderFactory()
{
_wrapped = new AutofacServiceProviderFactory();
}
public ContainerBuilder CreateBuilder(IServiceCollection services)
{
// Store the services for later.
_services = services;
return _wrapped.CreateBuilder(services);
}
public IServiceProvider CreateServiceProvider(ContainerBuilder containerBuilder)
{
var sp = _services.BuildServiceProvider();
#pragma warning disable CS0612 // Type or member is obsolete
var filters = sp.GetRequiredService<IEnumerable<IStartupConfigureContainerFilter<ContainerBuilder>>>();
#pragma warning restore CS0612 // Type or member is obsolete
foreach (var filter in filters)
{
filter.ConfigureContainer(b => { })(containerBuilder);
}
return _wrapped.CreateServiceProvider(containerBuilder);
}
}

Using this in place of the previous approach should allow you continue running your integration tests with Autofac 6. Thanks Alistair!

Concern for third-party containers#

Whilst this gets us back up and running, Alistair pointed out that this approach depends upon a deprecated interface. This is the IStartupConfigureContainerFilter which has been marked as Obsolete since mid 2019. What this means is, at some point, this approach will stop working.

The marvellous David Fowler has said that ConfigureTestContainer issue should be resolved in .NET. However it's worth noting that this has been an issue since .NET Core 3 shipped and unfortunately the wonderful Chris Ross has advised that it's not likely to be fixed for .NET 5.

I'm very keen this does get resolved in .NET. Building tests upon an Obsolete attribute doesn't fill me with confidence. I'm a long time user of Autofac and I'd like to continue to be. Here's hoping that's made possible by a fix landing in .NET. If this is something you care about, it may be worth upvoting / commenting on the issue in GitHub so the team are aware of desire around this being resolved.

A tale of Angular, html5mode, ASP.Net MVC and ASP.Net Web API

So. You want to kick hash based routing to the kerb. You want real URLs. You've read the HTML5 mode section of the Angular $location docs and you're good to go. It's just a matter of dropping $locationProvider.html5Mode(true) into your app initialisation right?

Wrong.

You want your URLs to be shareable. If, when you copy the URL out of your browser and send it someone else, they do not get taken to the same position in the application as you do then I've got news for you: THAT'S NOT REALLY A URL. And just using $locationProvider.html5Mode(true) has done nothing useful for you. You want to ensure that, if the URL entered in the browser does not relate to a specific server-side end-point, the self-same HTML root page is always served up. Then Angular can load the correct resources for the URL you have entered and get you to the required state.

There are tips to be found in Angular UI's How to: Configure your server to work with html5Mode doc. However they required a little extra fiddling to get my ASP.Net back end working quite as I wanted. To save you pain, here are my cultural learnings.

ASP.Net MVC#

I had an ASP.Net MVC app which I wanted to use html5mode with. To do this is simply a matter of tweaking your RouteConfig.cs like so:

public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// Here go the routes that you still want to be able to hit
routes.MapRoute(
name: "IAmARouteThatYouStillWantToHit",
url: "ThatsWhyIAmRegisteredFirst",
defaults: new { controller = "Hittable", action = "Index" }
);
// Everything else will hit Home/Index which serves up the root angular app page
routes.MapRoute(
name: "Default",
url: "{*anything}", // THIS IS THE MAGIC!!!!
defaults: new { controller = "Home", action = "Index" }
);
}

With this in place my existing routes work just as I would hope. Any route that doesn't fit that registered can be assumed to be html5mode related and will serve up the root angular app page as I'd hope.

ASP.Net Web API#

Later I realised that the app in question was mostly static content. Certainly the root angular app page was and so it seemed wasteful to require an ASP.Net MVC controller to serve up that static content. So I stripped out MVC from the app entirely, choosing to serve raw HTML instead. For the dynamic parts I switched to using Web API. This was "hittable" as long as I had my WebApiConfig.cs and my system.webServer section in my web.config lined up correctly, viz:

public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// other stuff
}
}
<configuration>
<system.webServer>
<defaultDocument>
<files>
<clear />
<add value="build/index.html" /> <!-- This is the root document for the Angular app -->
</files>
</defaultDocument>
<rewrite>
<rules>
<rule name="Main Rule" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<!-- Allows "api/" prefixed URLs to still hit Web API controllers
as defined in WebApiConfig -->
<add input="{REQUEST_URI}" pattern="api/" ignoreCase="true" negate="true" />
<!-- Static files and directories can be served so partials etc can be loaded -->
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="/" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>

With this in place I can happily hit "api" prefixed URLs and still land on my Web API controllers whilst other URLs will serve up the root angular app page. Lovely.

Using Gulp to inject scripts and styles tags directly into your HTML

This is very probably the dullest title for a blog post I've ever come up with. Read on though folks - it's definitely going to pick up...

I wrote last year about my first usage of Gulp in an ASP.Net project. I used Gulp to replace the Web Optimization functionality that is due to disappear when ASP.Net v5 ships. What I came up with was an approach that provided pretty much the same functionality; raw source in debug mode, bundling + minification in release mode.

It worked by having a launch page which was straight HTML. Embedded within this page was JavaScript that would, at runtime, load the required JavaScript / CSS and inject it dynamically into the document. This approach worked but it had a number of downsides:

  1. Each time you fired up the app the following sequence of events would happen: - jQuery would load (purely there to simplify the making of various startup AJAX calls)
  • the page would make an AJAX call to the server to load various startup data, including whether the app is running in debug or release mode
  • Depending on the result of the startup data either the debug or release package manifest would be loaded.
  • For each entry in the package manifest script and link tags would be created and added to the document. These would generate further requests to the server to load the resources.

Quite a lot going on here isn't there? Accordingly, initial startup time was slower than you might hope. 2. The "F" word: FOUC. Flash Of Unstyled Content - whilst all the hard work of the page load was going on (before the CSS had been loaded) the page would look rather ... bare. Not a terrible thing but none too slick either. 3. The gulpfile built both the debug and the release package each time it was run. This meant the gulp task generally did double the work that it needed to do.

I wanted to see if I could tackle these issues. I've recently been watching John Papa's excellent Pluralsight course on Gulp and picked up a number of useful tips. With that in hand let's see what we can come up with...

Death to dynamic loading#

The main issue with the approach I've been using is the dynamic loading. It makes the app slower and more complicated. So the obvious solution is to have my gulpfile inject scripts and css into the template. To that end it's wiredep & gulp-inject to the rescue!

gulp-inject (as the name suggests) is used to inject script and link tags into source code. I'm using Bower as my client side package manager and so I'm going to use wiredep to determine the vendor scripts I need. It will determine what packages my app is using from looking at my bower.json, and give me a list of file paths in dependency order (which I can then pass on to gulp-inject in combination with my own app script files). This means I don't have to think about ordering bower dependencies myself and I no longer need to separately maintain a list of these files within my gulpfile.

So, let's get the launch page (index.html) ready for gulp-inject:

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" />
<style>
.ng-hide {
display: none !important;
}
</style>
<title ng-bind="title">Proverb</title>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
<!-- inject:css -->
<!-- endinject -->
<link rel="icon" type="image/png" href="content/images/icon.png">
</head>
<body>
<div>
<div ng-include="'app/layout/shell.html'"></div>
<div id="splash-page" ng-show="false" class="dissolve-animation">
<div class="page-splash">
<div class="page-splash-message">
Proverb
</div>
<div class="progress">
<div class="progress-bar progress-bar-striped active" role="progressbar" style="width: 20%;">
<span class="sr-only">loading...</span>
</div>
</div>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="/build/jquery.min.js">\x3C/script>')</script>
<!-- inject:js -->
<!-- endinject -->
<script>
(function () {
// Load startup data from the server
$.getJSON("api/Startup")
.done(function (startUpData) {
angularApp.start({
thirdPartyLibs: {
moment: window.moment,
toastr: window.toastr,
underscore: window._
},
appConfig: startUpData
});
});
})();
</script>
</body>
</html>

The important thing to notice here are the &lt;!-- inject:css --&gt; and &lt;!-- inject:js --&gt; injection placeholders. It's here that our script and style tags will be injected into the template. You'll notice that jQuery is not being injected - and that's because I've opted to use a CDN for jQuery and then only fallback to serving jQuery myself if the CDN fails.

The other thing to notice here is that our launch page has become oh so much simpler in comparison with the dynamic loading approach. Which is fab.

Now before we start looking at our gulpfile I want to split out the configuration into a standalone file called gulpfile.config.js:

var tsjsmapjsSuffix = ".{ts,js.map,js}";
var bower = "bower_components/";
var app = "app/";
var config = {
base: ".",
buildDir: "./build/",
debug: "debug",
release: "release",
css: "css",
bootFile: app + "index.html",
bootjQuery: bower + "jquery/dist/jquery.min.js",
// The fonts we want Gulp to process
fonts: [bower + "fontawesome/fonts/*.*"],
images: "images/**/*.{gif,jpg,png}",
// The scripts we want Gulp to process
scripts: [
// Bootstrapping
app + "app" + tsjsmapjsSuffix,
app + "config.route" + tsjsmapjsSuffix,
// common Modules
app + "common/common" + tsjsmapjsSuffix,
app + "common/logger" + tsjsmapjsSuffix,
app + "common/spinner" + tsjsmapjsSuffix,
// common.bootstrap Modules
app + "common/bootstrap/bootstrap.dialog" + tsjsmapjsSuffix,
// directives
app + "directives/**/*" + tsjsmapjsSuffix,
// services
app + "services/**/*" + tsjsmapjsSuffix,
// controllers
app + "about/**/*" + tsjsmapjsSuffix,
app + "admin/**/*" + tsjsmapjsSuffix,
app + "dashboard/**/*" + tsjsmapjsSuffix,
app + "layout/**/*" + tsjsmapjsSuffix,
app + "sages/**/*" + tsjsmapjsSuffix,
app + "sayings/**/*" + tsjsmapjsSuffix
],
// The styles we want Gulp to process
styles: [
"content/styles.css"
],
wiredepOptions: {
exclude: [/jquery/],
ignorePath: ".."
}
};
config.debugFolder = config.buildDir + config.debug + "/";
config.releaseFolder = config.buildDir + config.release + "/";
config.templateFiles = [
app + "**/*.html",
"!" + config.bootFile // Exclude the launch page
];
module.exports = config;

Now to the meat of the matter - let me present the gulpfile:

/// <vs AfterBuild='default' />
var gulp = require("gulp");
// Include Our Plugins
var concat = require("gulp-concat");
var ignore = require("gulp-ignore");
var minifyCss = require("gulp-minify-css");
var uglify = require("gulp-uglify");
var rev = require("gulp-rev");
var del = require("del");
var path = require("path");
var templateCache = require("gulp-angular-templatecache");
var eventStream = require("event-stream");
var order = require("gulp-order");
var gulpUtil = require("gulp-util");
var wiredep = require("wiredep");
var inject = require("gulp-inject");
// Get our config
var config = require("./gulpfile.config.js");
/**
* Get the scripts or styles the app requires by combining bower dependencies and app dependencies
*
* @param {string} jsOrCss Should be "js" or "css"
*/
function getScriptsOrStyles(jsOrCss) {
var bowerScriptsAbsolute = wiredep(config.wiredepOptions)[jsOrCss];
var bowerScriptsRelative = bowerScriptsAbsolute.map(function makePathRelativeToCwd(file) {
return path.relative('', file);
});
var appScripts = bowerScriptsRelative.concat(jsOrCss === "js" ? config.scripts : config.styles);
return appScripts;
}
/**
* Get the scripts the app requires
*/
function getScripts() {
return getScriptsOrStyles("js");
}
/**
* Get the styles the app requires
*/
function getStyles() {
return getScriptsOrStyles("css");
}
/**
* Get the scripts and the templates combined streams
*
* @param {boolean} isDebug
*/
function getScriptsAndTemplates(isDebug) {
var options = isDebug ? { base: config.base } : undefined;
var appScripts = gulp.src(getScripts(), options);
//Get the view templates for $templateCache
var templates = gulp.src(config.templateFiles)
.pipe(templateCache({ module: "app", root: "app/" }));
var combined = eventStream.merge(appScripts, templates);
return combined;
}
gulp.task("clean", function (cb) {
gulpUtil.log("Delete the build folder");
return del([config.buildDir], cb);
});
gulp.task("boot-dependencies", ["clean"], function () {
gulpUtil.log("Get dependencies needed for boot (jQuery and images)");
var jQuery = gulp.src(config.bootjQuery);
var images = gulp.src(config.images, { base: config.base });
var combined = eventStream.merge(jQuery, images)
.pipe(gulp.dest(config.buildDir));
return combined;
});
gulp.task("inject-debug", ["styles-debug", "scripts-debug"], function () {
gulpUtil.log("Inject debug links and script tags into " + config.bootFile);
var scriptsAndStyles = [].concat(getScripts(), getStyles());
return gulp
.src(config.bootFile)
.pipe(inject(
gulp.src([
config.debugFolder + "**/*.{js,css}",
"!build\\debug\\bower_components\\spin.js" // Exclude weird spin js path
], { read: false })
.pipe(order(scriptsAndStyles))
))
.pipe(gulp.dest(config.buildDir));
});
gulp.task("inject-release", ["styles-release", "scripts-release"], function () {
gulpUtil.log("Inject release links and script tags into " + config.bootFile);
return gulp
.src(config.bootFile)
.pipe(inject(gulp.src(config.releaseFolder + "**/*.{js,css}", { read: false })))
.pipe(gulp.dest(config.buildDir));
});
gulp.task("scripts-debug", ["clean"], function () {
gulpUtil.log("Copy across all JavaScript files to build/debug");
return getScriptsAndTemplates(true)
.pipe(gulp.dest(config.debugFolder));
});
gulp.task("scripts-release", ["clean"], function () {
gulpUtil.log("Concatenate & Minify JS for release into a single file");
return getScriptsAndTemplates(false)
.pipe(ignore.exclude("**/*.{ts,js.map}")) // Exclude ts and js.map files - not needed in release mode
.pipe(concat("app.js")) // Make a single file
.pipe(uglify()) // Make the file titchy tiny small
.pipe(rev()) // Suffix a version number to it
.pipe(gulp.dest(config.releaseFolder)); // Write single versioned file to build/release folder
});
gulp.task("styles-debug", ["clean"], function () {
gulpUtil.log("Copy across all CSS files to build/debug");
return gulp
.src(getStyles(), { base: config.base })
.pipe(gulp.dest(config.debugFolder));
});
gulp.task("styles-release", ["clean"], function () {
gulpUtil.log("Copy across all files in config.styles to build/debug");
return gulp
.src(getStyles())
.pipe(concat("app.css")) // Make a single file
.pipe(minifyCss()) // Make the file titchy tiny small
.pipe(rev()) // Suffix a version number to it
.pipe(gulp.dest(config.releaseFolder + "/" + config.css)); // Write single versioned file to build/release folder
});
gulp.task("fonts-debug", ["clean"], function () {
gulpUtil.log("Copy across all fonts in config.fonts to debug location");
return gulp
.src(config.fonts, { base: config.base })
.pipe(gulp.dest(config.debugFolder));
});
gulp.task("fonts-release", ["clean"], function () {
gulpUtil.log("Copy across all fonts in config.fonts to release location");
return gulp
.src(config.fonts)
.pipe(gulp.dest(config.releaseFolder + "/fonts"));
});
gulp.task("build-debug", [
"boot-dependencies", "inject-debug", "fonts-debug"
]);
gulp.task("build-release", [
"boot-dependencies", "inject-release", "fonts-release"
]);
// Use the web.config to determine whether the default task should create a debug or a release build
// If the web.config contains this: '<compilation debug="true"' then we do a default build, otherwise
// we do a release build. It's a little hacky but generally works
var fs = require('fs');
var data = fs.readFileSync(__dirname + "/web.config", "UTF-8");
var inDebug = !!data.match(/<compilation debug="true"/);
gulp.task("default", [(inDebug ? "build-debug" : "build-release")]);

That's a big old lump of code. So let's go through this a task by task...

clean#

Deletes the build folder so we have a clean slate to build into.

boot-dependencies#

Copy across all files that are needed to allow the page to "boot" / startup. At present this is only jQuery and images.

inject-debug and inject-release#

This is the magic. This picks up the launch page (index.html), takes the JavaScript and CSS and injects the corresponding script and link tags into the page and writing it to the build folder. Either the original source code or the bundled / minified equivalent will be used depending on whether it's debug or release.

scripts-debug and scripts-release#

Here we collect up the following:

  • the Bower specified JavaScript files
  • the TypeScript + associated JavaScript files
  • and we use our template files to construct a templates.js file to prime the Angular template cache

If it's the scripts-debug task we copy all these files into the build/debug folder. If it's the scripts-release task we also bundle, minify and strip the TypeScript out too and copy into the build/release folder.

styles-debug and styles-release#

Here we collect up the following:

  • the Bower specified CSS files
  • our own app CSS

If it's the styles-debug task we copy all these files into the build/debug folder. If it's the styles-release task we also bundle and minify and copy into the build/release folder.

fonts-debug and fonts-release#

Whether it's the debug or the release build we copy across the font-awesome assets and place them in a location which works for the associated CSS (as the CSS will depend upon font-awesome).

build-debug, build-release and default#

build-debug and build-release (as their name suggests) either perform a build for release or a build for debug. If you remember, the web optimization library in ASP.Net serves up the raw code ("debug" code) if the compilation debug flag in the web.config is set to true. If it is set to false then we get the bundled and minified code ("release" code) instead. Our default task tries its best to emulate this behaviour by doing a very blunt regex against the web.config. Simply, if it can match &lt;compilation debug="true" then it runs the debug build. Otherwise, the release build. It could be more elegant but there's a dearth of XML readers on npm that support synchronous parsing (which you kinda need for this scenario).

What I intend to do soon is switch from using the web.config to drive the gulp build to using the approach outlined here. Namely plugging the build directly into Visual Studio's build process and using the type of build there.

Hopefully what I've written here makes it fairly clear how to use Gulp to directly inject scripts and styles directly into your HTML. If you want to look directly at the source then check out the Proverb.Web folder in this repo.

AngularJS meet ASP.Net Server Validation

So. You're using AngularJS to build your front end with ASP.Net running on the server side. You're a trustworthy dev - you know that validation on the client will only get you so far. You need to validate on the server.

My particular scenario is where you have a form which you are saving. Angular serves you well when it comes to hooking in your own client side validation. But it doesn't really ship with anything that supports nicely presenting server side validation on the client. Invariably when you look around you find people duplicating their server side validation on the client and presenting all their server side validation in a &lt;div&gt; at the top of the screen.

This works but it's not as helpful to the user as it might be. It groups together all the validation from the server into one place. What I want is field level validation from the server that's presented on a field level basis on the screen. Like this:

I know. A thing of beauty is a joy forever. Let us travel together to this promised land...

What do we need client side?#

Well, let's start with a directive which I'll call serverError. This plants a validation message just after the element being validated which is displayed when that element is declared invalid by the server. (That is to say when the ngModel has a $error.server set.) When the element is changed then the $error.server is unset in order that validation can be hidden and the form can be revalidated against the server.

I'm using TypeScript with Angular so for my JavaScript examples I'll give you both the TypeScript which I originally wrote and the generated JavaScript as well.

TypeScript#

interface serverErrorScope extends ng.IScope {
name: string;
serverError: { [field: string]: string };
}
app.directive("serverError", [function () {
// Usage:
// <input class="col-xs-12 col-sm-9"
// name="sage.name" ng-model="vm.sage.name" server-error="vm.errors" />
var directive = {
link: link,
restrict: "A",
require: "ngModel", // supply the ngModel controller as the 4th parameter in the link function
scope: { // Pass in name and serverError to the scope
name: "@",
serverError: "="
}
};
return directive;
function link(scope: serverErrorScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes, ngModelController: ng.INgModelController) {
// Bootstrap alert template for error
var template = '<div class="alert alert-danger" role="alert">' +
'<i class="glyphicon glyphicon-warning-sign"></i> ' +
'%error%</div>';
// Create an element to hold the validation message
var decorator = angular.element('<div></div>');
element.after(decorator);
// Watch ngModelController.$error.server & show/hide validation accordingly
scope.$watch(safeWatch(() => ngModelController.$error.server), showHideValidation);
function showHideValidation(serverError: boolean) {
// Display an error if serverError is true otherwise clear the element
var errorHtml = "";
if (serverError) {
// Aliasing serverError and name to make it more obvious what their purpose is
var errorDictionary = scope.serverError;
var errorKey = scope.name;
errorHtml = template.replace(/%error%/, errorDictionary[errorKey] || "Unknown error occurred...");
}
decorator.html(errorHtml);
}
// wipe the server error message upon keyup or change events so can revalidate with server
element.on("keyup change", (event) => {
scope.$apply(() => { ngModelController.$setValidity("server", true); });
});
}
}]);
// Thanks @Basarat! http://stackoverflow.com/a/24863256/761388
function safeWatch<t extends="" function="">(expression: T) {
return () => {
try {
return expression();
}
catch (e) {
return null;
}
};
}
</t>

JavaScript#

app.directive("serverError", [function () {
// Usage:
// <input class="col-xs-12 col-sm-9"
// name="sage.name" ng-model="vm.sage.name" server-error="vm.errors" />
var directive = {
link: link,
restrict: "A",
require: "ngModel",
scope: {
name: "@",
serverError: "="
}
};
return directive;
function link(scope, element, attrs, ngModelController) {
// Bootstrap alert template for error
var template = '<div class="alert alert-danger" role="alert">' +
'<i class="glyphicon glyphicon-warning-sign"></i> ' +
'%error%</div>';
// Create an element to hold the validation message
var decorator = angular.element('<div></div>');
element.after(decorator);
// Watch ngModelController.$error.server & show/hide validation accordingly
scope.$watch(safeWatch(function () {
return ngModelController.$error.server;
}), showHideValidation);
function showHideValidation(serverError) {
// Display an error if serverError is true otherwise clear the element
var errorHtml = "";
if (serverError) {
// Aliasing serverError and name to make it more obvious what their purpose is
var errorDictionary = scope.serverError;
var errorKey = scope.name;
errorHtml = template.replace(/%error%/, errorDictionary[errorKey] || "Unknown error occurred...");
}
decorator.html(errorHtml);
}
// wipe the server error message upon keyup or change events so can revalidate with server
element.on("keyup change", function (event) {
scope.$apply(function () {
ngModelController.$setValidity("server", true);
});
});
}
}]);
// Thanks @Basarat! http://stackoverflow.com/a/24863256/761388
function safeWatch(expression) {
return function () {
try {
return expression();
} catch (e) {
return null;
}
};
}

If you look closely at this directive you'll see it is restricted to be used as an attribute and it depends on 2 things:

  1. The value that the server-error attribute is set to should be an object which will contain key / values where the keys represent fields that are being validated.
  2. The element being validated must have a name property (which will be used to look up the validation message in the server-error error "dictionary".

Totally not clear, right? Let's have an example. Here is my "sageEdit" screen which you saw the screenshot of earlier:

<section class="mainbar" ng-controller="sageEdit as vm">
<section class="matter">
<div class="container-fluid">
<form name="form" novalidate role="form">
<div>
<button class="btn btn-info"
ng-click="vm.save()"
ng-disabled="!vm.canSave">
<i class="glyphicon glyphicon-save"></i>Save
</button>
<span ng-show="vm.hasChanges"
class="dissolve-animation ng-hide">
<i class="glyphicon glyphicon-asterisk orange"></i>
</span>
</div>
<div class="widget wblue">
<div data-cc-widget-header title="{{vm.title}}"></div>
<div class="widget-content form-horizontal">
<div class="form-group">
<label class="col-xs-12 col-sm-2">Name</label>
<input class="col-xs-12 col-sm-9"
name="sage.name" ng-model="vm.sage.name"
server-error="vm.errors" />
</div>
<div class="form-group">
<label class="col-xs-12 col-sm-2">Username</label>
<input class="col-xs-12 col-sm-9"
name="sage.userName" ng-model="vm.sage.userName"
server-error="vm.errors" />
</div>
<div class="form-group">
<label class="col-xs-12 col-sm-2">Email</label>
<input class="col-xs-12 col-sm-9"
type="email"
name="sage.email" ng-model="vm.sage.email"
server-error="vm.errors" />
</div>
</div>
</div>
</form>
</div>
</section>
</section>

If you look closely at where server-error is used we have a name attribute set (eg "sage.email") and we're passing in something called <em>vm.</em>errors as the server-error attribute value. That's because we're using the "controller as" syntax and our controller is called vm.

On that controller we're going to have a dictionary style object called errors. If you wanted to you could put that object on the scope instead and omit the "vm." prefix. You could call it wrongThingsWhatISpottedWithYourModel or barry - whatever floats your boat really. You get my point; it's flexible.

Let's take a look at our sageEdit Angular controller:

TypeScript#

module controllers {
"use strict";
interface sageEditRouteParams extends ng.route.IRouteParamsService {
id: number;
}
interface sageEditScope extends ng.IScope {
form: ng.IFormController;
}
class SageEdit {
errors: { [field: string]: string };
log: loggerFunction;
logError: loggerFunction;
logSuccess: loggerFunction;
sage: sage;
title: string;
private _isSaving: boolean;
static $inject = ["$location", "$routeParams", "$scope", "common", "datacontext"];
constructor(
private $location: ng.ILocationService,
private $routeParams: sageEditRouteParams,
private $scope: sageEditScope,
private common: common,
private datacontext: datacontext
) {
this.errors = {};
this.log = common.logger.getLogFn(controllerId);
this.logError = common.logger.getLogFn(controllerId, "error");
this.logSuccess = common.logger.getLogFn(controllerId, "success");
this.sage = undefined;
this.title = "Sage Edit";
this._isSaving = false;
this.activate();
}
// Prototype methods
activate() {
var id = this.$routeParams.id;
var dataPromises: ng.IPromise<any>[] = [this.getSage(id)];
this.common.activateController(dataPromises, controllerId, this.title)
.then(() => {
this.log("Activated Sage Edit View");
this.title = "Sage Edit: " + this.sage.name;
});
}
getSage(id: number) {
return this.datacontext.sage.getById(id).then(sage => {
this.sage = sage;
});
}
save() {
this.errors = {}; //Wipe server errors
this._isSaving = true;
this.datacontext.sage.save(this.sage).then(response => {
if (response.success) {
this.sage = response.entity;
this.logSuccess("Saved " + this.sage.name + " [" + this.sage.id + "]");
this.$location.path("/sages/detail/" + this.sage.id);
}
else {
this.logError("Failed to save", response.errors);
angular.forEach(response.errors, (errors, field) => {
(<ng.INgModelController>this.$scope.form[field]).$setValidity("server", false);
this.errors[field] = errors.join(",");
});
}
this._isSaving = false;
});
}
// Properties
get hasChanges(): boolean {
return this.$scope.form.$dirty;
}
get canSave(): boolean {
return this.hasChanges && !this._isSaving && this.$scope.form.$valid;
}
}
var controllerId = "sageEdit";
angular.module("app").controller(controllerId, SageEdit);
}

JavaScript#

var controllers;
(function (controllers) {
"use strict";
var SageEdit = (function () {
function SageEdit($location, $routeParams, $scope, common, datacontext) {
this.$location = $location;
this.$routeParams = $routeParams;
this.$scope = $scope;
this.common = common;
this.datacontext = datacontext;
this.errors = {};
this.log = common.logger.getLogFn(controllerId);
this.logError = common.logger.getLogFn(controllerId, "error");
this.logSuccess = common.logger.getLogFn(controllerId, "success");
this.sage = undefined;
this.title = "Sage Edit";
this._isSaving = false;
this.activate();
}
// Prototype methods
SageEdit.prototype.activate = function () {
var _this = this;
var id = this.$routeParams.id;
var dataPromises = [this.getSage(id)];
this.common.activateController(dataPromises, controllerId, this.title).then(function () {
_this.log("Activated Sage Edit View");
_this.title = "Sage Edit: " + _this.sage.name;
});
};
SageEdit.prototype.getSage = function (id) {
var _this = this;
return this.datacontext.sage.getById(id).then(function (sage) {
_this.sage = sage;
});
};
SageEdit.prototype.save = function () {
var _this = this;
this.errors = {}; //Wipe server errors
this._isSaving = true;
this.datacontext.sage.save(this.sage).then(function (response) {
if (response.success) {
_this.sage = response.entity;
_this.logSuccess("Saved " + _this.sage.name + " [" + _this.sage.id + "]");
_this.$location.path("/sages/detail/" + _this.sage.id);
} else {
_this.logError("Failed to save", response.errors);
angular.forEach(response.errors, function (errors, field) {
_this.$scope.form[field].$setValidity("server", false);
_this.errors[field] = errors.join(",");
});
}
_this._isSaving = false;
});
};
Object.defineProperty(SageEdit.prototype, "hasChanges", {
// Properties
get: function () {
return this.$scope.form.$dirty;
},
enumerable: true,
configurable: true
});
Object.defineProperty(SageEdit.prototype, "canSave", {
get: function () {
return this.hasChanges && !this._isSaving && this.$scope.form.$valid;
},
enumerable: true,
configurable: true
});
SageEdit.$inject = ["$location", "$routeParams", "$scope", "common", "datacontext"];
return SageEdit;
})();
var controllerId = "sageEdit";
angular.module("app").controller(controllerId, SageEdit);
})(controllers || (controllers = {}));

Okay - this is a shedload of code and most of it isn't relevant to you. I share it as I like to see things in context. Let's focus in on the important bits that you should take away. Firstly, our controller has a property called errors.

Secondly, when we attempt to save our server sends back a JSON payload which, given a validation failure, looks something like this:

{
"success":false,
"errors":{
"sage.name":["The Name field is required."],
"sage.userName":[
"The UserName field is required.",
"The field UserName must be a string with a minimum length of 3 and a maximum length of 30."
],
"sage.email":["The Email field is not a valid e-mail address."]
}
}

So let's pare back our save function to the bare necessities (those simple bare necessities, forget about your worries and your strife...):

TypeScript#

save() {
this.errors = {}; //Wipe server errors
this.datacontext.sage.save(this.sage).then(response => {
if (response.success) {
this.sage = response.entity;
}
else {
angular.forEach(response.errors, (errors, field) => {
(<ng.INgModelController>this.$scope.form[field]).$setValidity("server", false);
this.errors[field] = errors.join(",");
});
}
});
}

JavaScript#

SageEdit.prototype.save = function () {
var _this = this;
this.errors = {}; //Wipe server errors
this.datacontext.sage.save(this.sage).then(function (response) {
if (response.success) {
_this.sage = response.entity;
} else {
angular.forEach(response.errors, function (errors, field) {
_this.$scope.form[field].$setValidity("server", false);
_this.errors[field] = errors.join(",");
});
}
});
};

At the point of save we wipe any server error messages that might be stored on the client. Then, if we receive back a payload with errors we store those errors and set the validity of the relevant form element to false. This will trigger the display of the message by our directive.

That's us done for the client side. You're no doubt now asking yourself this question:

How can I get ASP.Net to send me this information?#

So glad you asked. We've a simple model that looks like this which has a number of data annotations:

public class Sage
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
[StringLength(30, MinimumLength = 3)]
public string UserName { get; set; }
[EmailAddress]
public string Email { get; set; }
}

When we save we post back to a Web API controller that looks like this:

public class SageController : ApiController
{
// ...
public IHttpActionResult Post(User sage)
{
if (!ModelState.IsValid) {
return Ok(new
{
Success = false,
Errors = ModelState.ToErrorDictionary()
});
}
sage = _userService.Save(sage);
return Ok(new
{
Success = true,
Entity = sage
});
}
// ...
}

As you can see, when ModelState is not valid we send back a dictionary of the ModelState error messages keyed by property name. We generate this with an extension method I wrote called ToErrorDictionary:

public static class ModelStateExtensions
{
public static Dictionary<string, IEnumerable<string>> ToErrorDictionary(
this System.Web.Http.ModelBinding.ModelStateDictionary modelState, bool camelCaseKeyName = true)
{
var errors = modelState
.Where(x => x.Value.Errors.Any())
.ToDictionary(
kvp => CamelCasePropNames(kvp.Key),
kvp => kvp.Value.Errors.Select(e => e.ErrorMessage)
);
return errors;
}
private static string CamelCasePropNames(string propName)
{
var array = propName.Split('.');
var camelCaseList = new string[array.Length];
for (var i = 0; i < array.Length; i++)
{
var prop = array[i];
camelCaseList[i] = prop.Substring(0, 1).ToLower() + prop.Substring(1, prop.Length - 1);
}
return string.Join(".", camelCaseList);
}
}

That's it - your solution front to back. It would be quite easy to hook other types of validation in server-side (database level checks etc). I hope you find this useful.

Caching and cache-busting with RequireJS

Having put together a demo of using TypeScript with RequireJS my attention turned quickly to caching. Or rather, IE forced me to think about caching.

Everyone has their own workflow, their own tools. The things they like to use as they put things together. And for me I鈥檓 a Visual Studio man 鈥 it鈥檚 not everyone鈥檚 bag but I really like it. I find the JavaScript tooling is now really solid combined with IE and it (generally) makes me more productive. I want to use it. But, as you know, nothing is perfect...

So there I was, delighted with the TypeScript / RequireJS demo. It was working just lovely. I started investigating the debugging story. What would happen if I change a script file on the fly? When I refresh IE does it pick up the tweaks?

Let鈥檚 find out. I'll open up alerter.ts and change this:

var name = "John";

to this:

var name = "Bobby";

And *boom*! Nothing. I鈥檝e refreshed IE and I鈥檓 expecting to see 鈥淲elcome to Code Camp, Bobby鈥. But I鈥檓 still reading 鈥淲elcome to Code Camp, John鈥... I bet Chrome wouldn鈥檛 do this to me... And I鈥檓 right! It doesn鈥檛. I don鈥檛 want to get too much into the details of this but it looks like it comes down to Chrome sending an "If-Modified-Since" request header where IE does not. I鈥檓 pretty sure that IE could be configured to behave likewise but I鈥檇 rather not have to remember that. (And furthermore I don鈥檛 want to have to remind every person that works on the app to do that as well.)

This raises a number of issues but essentially it gets me to think about the sort of caching I want. Like most of you I have 2 significant use cases:

  1. Development
  2. Production

For Development I want any changes to JavaScript files to be picked up 鈥 I do *not* want caching. For Production I want caching in order that users have better performance / faster loading. If I ship a new version of the app to Production I also want users to pick up the new versions of a file and cache those.

Research#

I did a little digging. The most useful information I found was a StackOverflow post on RequireJS and caching. Actually I鈥檇 recommend anyone reading this to head over and read that from top to bottom. Read the question and all of the answers as well 鈥 pretty much everything will add to your understanding of RequireJS.

As with any set of answers there are different and conflicting views. Phil McCull鈥檚 (accepted) answer was for my money the most useful. It pointed back to the RequireJS documentation.

*"urlArgs: Extra query string arguments appended to URLs that RequireJS uses to fetch resources. Most useful to cache bust when the browser or server is not configured correctly. Example cache bust setting for urlArgs:

urlArgs: "bust=" + (new Date()).getTime()

During development it can be useful to use this, however be sure to remove it before deploying your code."

Phil鈥檚 answer suggests using urlArgs *both* for Production and for Development in 2 different ways. Using what amounts to a random number in the Development environment (as in the official docs) for cache-busting. For the Production environment he suggests using a specific version number which allows for client-side caching between different build versions.

Full disclosure, this is not the approach favoured by James Burke (author of RequireJS). He doesn鈥檛 go into why in the RequireJS docs but has elsewhere expounded on this:

For deployed assets, I prefer to put the version or hash for the whole build as a build directory, then just modify the baseUrl config used for the project to use that versioned directory as the baseUrl. Then no other files change, and it helps avoid some proxy issues where they may not cache an URL with a query string on it.

I鈥檓 not so worried about the proxy caching issue. My users tend to be people who use the application repeatedly and so the caching I most care about is their local machine caching. From what I understand urlArgs will work fine in this scenario. Yes the downside of this approach is that some proxy servers may not cache these assets. That鈥檚 a shame but it鈥檚 not a dealbreaker for me. As I said, I still have client side caching.

If you want to go a little deeper I recommend reading Steve Souders post on the topic (in case you鈥檙e not aware Steve is Google鈥檚 Mr Performance). Interestingly, looking at the comments on the post it sounds like the lack of support for proxy caching with querystrings may that may be starting to change.

But either way, I鈥檓 happy with this approach. As I always say, if it鈥檚 good enough for Stack Overflow then it鈥檚 good enough for me:

Implementation#

I鈥檓 going to start off using the demo from my last blog post as a basis. Let鈥檚 take that and evolve it. As a result my solution is going to work with TypeScript and RequireJS (since the previous demo was about that) but the implementation I鈥檓 going to come up with would work as well with vanilla JS as it would with TypeScript compiled JS.

Let鈥檚 take a look at our index.html. First we鈥檒l drop our usage of main.ts / main.js (our bootstrapper file that defines config and kicks off the "app"). We鈥檒l pull out the use of data-main and instead, just after the reference to require we鈥檒l add the contents of main.js much in the style of the RequireJS docs. We鈥檒l also include a urlArgs that as a cache-buster that uses the approach outlined in the RequireJS docs:

<script src="/scripts/require.js"></script>
<script>
require.config({
baseUrl: "/scripts",
paths: {
"jquery": "jquery-2.1.0"
},
urlArgs: "v=" + (new Date()).getTime()
});
require(["alerter"], function (alerter) {
alerter.showMessage();
});
</script>

Spinning up the site all runs as you would expect. The question is: does this work as a cache-buster? Let鈥檚 tweak alerter.ts / alerter.js. And:

Oh yeah! We鈥檙e cache-busting like gangbusters!

So now let鈥檚 comment out our existing urlArgs (which represents the Development solution from Phil鈥檚 answer) and replace it with a fixed value like this:

//urlArgs: "v=" + (new Date()).getTime()
urlArgs: "v=1"

This represents the Production solution from Phil鈥檚 answer. Now let鈥檚 run, refresh a couple of times and ensure that our fixed querystring value results in a 304 status code (indicating 鈥淣ot Modified鈥 and cached item used):

It does! Now let鈥檚 increment the value:

urlArgs: "v=2"

When we refresh the browser this should result in 200 status codes (indicating the cached version has not been used and the client has picked up a new version from the server).

Success! That鈥檚 our premise tested 鈥 both Development and Production scenarios. Now we want to turn this into a slightly more sophisticated reusable solution like this:

<script src="/scripts/require.js"></script>
<script>
var inDevelopment = true,
version = "1";
require.config({
baseUrl: "/scripts",
paths: {
"jquery": "jquery-2.1.0"
},
urlArgs: "v=" + ((inDevelopment)
? (new Date()).getTime()
: version)
});
require(["alerter"], function (alerter) {
alerter.showMessage();
});
</script>

In the tweaked script above 2 variables are defined. The first is inDevelopment which models whether you are in the Development scenario (true) or the Production scenario (false). The second is version which represents the application version number. With this in place I can simply flip between the Development and Production scenario by changing the value of inDevelopment. And when a new version ships I can change the version number to force a cache refresh on the users.

What drives the values of inDevelopment / version is down to you. You could load the inDevelopment / version values from some application endpoint. You could hardcode them in your screen. The choices are yours. I鈥檓 going to finish off with a simple approach that I've found useful.

Let鈥檚 get the server involved!#

I want the server to drive my urlArgs value. Why? Well this project happens to be an ASP.NET project which handily has the concept of Development / Production scenarios nicely modelled by the web.config鈥檚 compilation debug flag.

<configuration>
<system.web>
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5" />
</system.web>
</configuration>

If debug is true then that reflects the Development scenario. If debug is false then that reflects the Production scenario.

So bearing that in mind I want to use the value of debug to drive my urlArgs. If I have my debug flag set to true I want to cache-bust all the way. Likewise, if debug is set to false then I want to serve up the version number so that caching is used until the version number changes. Time to break out the C#:

namespace RequireJSandCaching
{
public static class RequireJSHelpers
{
private static readonly bool _inDebug;
private static readonly string _version;
static RequireJSHelpers()
{
_inDebug = System.Web.HttpContext.Current.IsDebuggingEnabled;
_version = (_inDebug)
? "InDebug"
: System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
}
public static string Version
{
get
{
return (_inDebug)
? System.DateTime.Now.Ticks.ToString()
: _version;
}
}
}
}

This is a static helper class called RequireJSHelpers. It has a static constructor which initialises 2 fields. _inDebug is taken from System.Web.HttpContext.Current.IsDebuggingEnabled which exposes the compilation debug value. _version is initialised, when debug is false, to the version number of the dll (driven by this AssemblyInfo.cs [assembly: AssemblyVersion("1.0.*")] attribute)

There鈥檚 1 property on this helper class called version. Depending on whether the app is in debug mode or not this attribute either exposes the application version or effectively the C# equivalent to JavaScript鈥檚 (new Date()).getTime(). (Well strictly speaking they have a different starting point in history but that鈥檚 by-the-by... Both are of equal value as cache-busters.)

You probably see where this is all going.

Let鈥檚 clone our index.html page and call it serverUrlArgs.cshtml (note the suffix). Let鈥檚 replace the script section with this:

<script>
require.config({
baseUrl: "/scripts",
paths: {
"jquery": "jquery-2.1.0"
},
urlArgs: "[email protected]"
});
require(["alerter"], function (alerter) {
alerter.showMessage();
});
</script>

Which drives urlArgs from the RequireJSHelpers.Version property. If we fire it up now (with debug set to true in our web.config) then we see requests like this:

And if we set debug to false in our web.config then (after the initial requests have been cached) we see requests like this:

This leaves us with a simple mechanism to drive our RequireJS caching. If debug is set to true in our web.config then Require will perform cache-busting. If debug is set to false then RequireJS will perform only version-changing cache-busting and will, whilst the version remains constant, support client-side caching.

Finished. In case it helps I鈥檝e put the code for this up on GitHub.

Using Web Optimization with MVC 3

A while ago I wrote about optimally serving up JavaScript in web applications. I mentioned that Microsoft had come up with a NuGet package called Microsoft ASP.NET Web Optimization which could help with that by minifying and bundling CSS and JavaScript. At the time I was wondering if I would be able to to use this package with pre-existing MVC 3 projects (given that the package had been released together with MVC 4). Happily it turns out you can. But it's not quite as straightforward as I might have liked so I've documented how to get going with this here...

Getting the Basics in Place#

To keep it simple I'm going to go through taking a "vanilla" MVC 3 app and enhancing it to work with Web Optimization. To start, follow these basic steps:

  1. Open Visual Studio (bet you didn't see that coming!)
  2. Create a new MVC 3 application (I called mine "WebOptimizationWithMvc3" to demonstrate my imaginative flair). It doesn't really matter which sort of MVC 3 project you create - I chose an Intranet application but really that's by the by.
  3. Update pre-existing NuGet packages
  4. At the NuGet console type: "Install-Package Microsoft.AspNet.Web.Optimization"

Whilst the NuGet package adds the necessary references to your MVC 3 project it doesn't add the corresponding namespaces to the web.configs. To fix this manually add the following child XML element to the &lt;namespaces&gt; element in your root and Views web.config files:

&lt;add namespace="System.Web.Optimization" /&gt;

This gives you access to Scripts and Styles in your views without needing the fully qualified namespace. For reasons best known to Microsoft I had to close down and restart Visual Studio before intellisense started working. You may need to do likewise.

Next up we want to get some JavaScript / CSS bundles in place. To do this, create a folder in the root of your project called "App_Start". There's nothing magical about this to my knowledge; this is just a convention that's been adopted to store all the bits of startup in one place and avoid clutterage. (I think this grew out of Nuget; see David Ebbo talking about this here.) Inside your new folder you should add a new class called BundleConfig.cs which looks like this:

The above is what you get when you create a new MVC 4 project (as it includes Web Optimization out of the box). All it does is create some JavaScript and CSS bundles relating to jQuery, jQuery UI, jQuery Validate, Modernizr and the standard site CSS. Nothing radical here but this example should give you an idea of how bundling can be configured and used. To make use of BundleConfig.cs you should modify your Global.asax.cs so it looks like this:

Once you've done this you're ready to start using Web Optimization in your MVC 3 application.

Switching over _Layout.cshtml to use Web Optimization#

With a "vanilla" MVC 3 app the only use of CSS and JavaScript files is found in _Layout.cshtml. To switch over to using Web Optimization you should replace the existing _Layout.cshtml with this: (you'll see that the few differences that there are between the 2 are solely around the replacement of link / script tags with references to Scripts and Styles instead)

Do note that in the above Scripts.Render call we're rendering out 3 bundles; jQuery, jQuery UI and jQuery Validate. We're not using any of these in _Layout.cshtml but rendering these (and their associated link tags) gives us a chance to demonstrate that everything is working as expected.

In your root web.config file make sure that the following tag is in place: &lt;compilation debug="<b>true</b>" targetFramework="4.0"&gt;. Then run, the generated HTML should look something like this:

This demonstrates that when the application has debug set to true you see the full scripts / links being rendered out as you would hope (to make your debugging less painful).

Now go back to your root web.config file and chance the debug tag to false: &lt;compilation debug="<b>false</b>" targetFramework="4.0"&gt;. This time when you run, the generated HTML should look something like this:

This time you can see that in non-debug mode (ie how it would run in Production) minified bundles of scripts and css files are being served up instead of the raw files. And that's it; done.

Standing on the Shoulders of Giants...

It started with Scott Hanselman. I had no particular plans to start a blog at all. However, I was reading Scott Hanselman's turn of the year post and I was struck with an idea.

First, let me give a little background about myself. I'm a software developer. I've been in the industry for coming up to 15 years. I started out professionally writing call centre software. I moved on to code in a variety of different industries from straight IT to marketing and, for the last 7 years, finance.

Though I initially started out writing in Delphi I fast found myself moving toward the Microsoft "stack of love". I should say that this move was not because I instinctively liked Microsofts stuff (in fact in the beginning I actively disliked it - moving from Delphi 3.0 to Visual Studio 5 left me finding Microsoft's offering very much wanting). Rather it was pragmatic. I needed a job and at the time VB was a far more transferable skill than Delphi. What with the all encompassing dot-com bubble of the late 90's I soon found myself working in the webtastic world of classic ASP (weep) and VB server components (remember them?).

Though things can improve - and in my opinion they really did when Microsoft coughed up the first furball of ASP.NET Beta in (I think) 2001. I grabbed on with both hands. Since that point I've been earning my bread pretty much, though not exclusively, in the ASP.NET universe.

The one thing that might not be clear from the above curriculum vitae is this: I AM A COMPLETE AMATEUR. I mean this in both senses of the word:

  1. I have no formal training to speak of - I didn't do a computer sciences degree. In fact my first real coding experience was writing a program in Locomotive Basic for my father on our humble Amstrad CPC.
  2. That said, I love it. I find writing code an intellectually, emotionally, creatively satisfying act. And whilst I undoubtedly have less of the theoretical knowledge which most professional developers seem to have, I probably counter-balance that with a hunger to keep learning and keep trying new things. And since software never sits still that's probably just as well. Keep watching the horizon - there will be something coming over it! And it's worth saying, I have an instinct for developing which serves me pretty well. I'm good at coming up with elegant and pragmatic solutions. Put simply: I'm good at making code work.

So back to the point. In my daily work life, like any other developer, I am repeatedly called on to turn someones requirement into a reality. Very rarely do I achieve this on my own. Like most of us I'm a dwarf standing on the shoulders of giants. There's a lot of people out there who come up with useful tools / components / plug-ins that make it possible for me to deliver much more than I would given my own abilities.

So that's what I want to do: I want to talk about the tools, components and techniques that I have found useful in the everyday working life of a developer. It's likely to be quite a "webby" blog as I probably find that the most interesting area of development at the moment.

I don't know how often I will write but my plan is that when I do, each time I'll talk about something I've found useful - why I found it useful, what problems it solved, what issues it still presented me with and so on. This is probably not going to be a "techie techie" blog. Rather a blog that deals with the situations that can confront a developer and how I've responded to them. I hope you find it interesting. And if you don't; please keep it to yourself :-)