Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save michael-wolfenden/7a20f9811cb5da7143143e8265392693 to your computer and use it in GitHub Desktop.
Save michael-wolfenden/7a20f9811cb5da7143143e8265392693 to your computer and use it in GitHub Desktop.
ASP.NET Core One Hour Makeover

https://aka.ms/aspnetcore-makeover

Outline

  • Intro (goals/non-goals/what you'll learn)
  • Pick The Right Starting Template
    • Web Application (no auth)
    • Web API
    • SPA
    • Other templates (teaser)
  • Source Control and Solution Structure
    • editorconfig
    • gitignore
  • Maintainability
    • Tests
    • Health Checks
    • Debug Footer
  • Front End
    • Bootstrap
    • Front end build?
    • Libman
    • SEO
  • Performance (no way we'll get to this in 50 minutes...)
    • ANCM
    • Tiered Compilation
    • Language feature opt-in
    • Caching
    • Miniprofiler
  • Creating your own templates

Code Snippets

FunctionalTests.cs

using Microsoft.AspNetCore.Mvc.Testing;
using OneHour.Web;
using System;
using System.Threading.Tasks;
using Xunit;

namespace WebApp.Tests
{
	public class BasicTests
		: IClassFixture<WebApplicationFactory<Startup>>
	{
		private readonly WebApplicationFactory<Startup> _factory;

		public BasicTests(WebApplicationFactory<Startup> factory)
		{
			_factory = factory;
		}

		[Theory]
		[InlineData("/")]
		[InlineData("/Index")]
		[InlineData("/Privacy")]
		public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
		{
			// Arrange
			var client = _factory.CreateClient();

			// Act
			var response = await client.GetAsync(url);

			// Assert
			response.EnsureSuccessStatusCode(); // Status Code 200-299
			Assert.Equal("text/html; charset=utf-8",
				response.Content.Headers.ContentType.ToString());
		}

		[Fact]
		public async Task Get_HealthCheckReturnsHealthy()
		{
			// Arrange
			var client = _factory.CreateClient();

			// Act
			var response = await client.GetAsync("/health");

			// Assert
			response.EnsureSuccessStatusCode(); // Status Code 200-299
			Assert.Equal("Healthy",
				response.Content.ReadAsStringAsync().Result);

		}
	}
}

libman.json

{
  "version": "1.0",
  "defaultProvider": "cdnjs",
  "libraries": [
  {
   "library": "[email protected]",
   "files": [
    "jquery.min.js",
    "jquery.js",
    "jquery.min.map"
   ],
   "destination": "wwwroot/lib/jquery/dist"
  },
  {
   "provider": "unpkg",
   "library": "[email protected]",
   "destination": "wwwroot/lib/bootstrap/",
   "files": [
    "dist/css/bootstrap.css",
    "dist/css/bootstrap-grid.css",
    "dist/css/bootstrap-reboot.css",
    "dist/js/bootstrap.js"
   ]
  },
  {
   "provider": "unpkg",
   "library": "[email protected]",
   "destination": "wwwroot/lib/ionicons",
   "files": [ "dist/css/ionicons.min.css" ]
  }
  ]
}  

Sitemap Extension

using Ducksoft.NetCore.Razor.Sitemap.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;

namespace OneHour.Web.Utility
{
	public static class SitemapExtensions
	{
		public static IServiceCollection ConfigureMvcRazorPages(this IServiceCollection services,
			  CompatibilityVersion version, string startPageUrl = "", string startPageArea = "")
		{
			services.AddMvc()
				.SetCompatibilityVersion(version)
				.AddRazorPagesOptions(options =>
				{
					var isSupportAreas = !string.IsNullOrWhiteSpace(startPageArea);
					options.AllowAreas = isSupportAreas;
					options.AllowMappingHeadRequestsToGetHandler = true;
					if (isSupportAreas)
					{
						options.Conventions.AddAreaPageRoute(startPageArea, startPageUrl, string.Empty);
					}
					else if (!string.IsNullOrWhiteSpace(startPageUrl))
					{
						options.Conventions.AddPageRoute(startPageUrl, string.Empty);
					}
				})
				.AddRazorPagesOptions(options =>
				{
					options.Conventions.Add(new SitemapRouteConvention());
				})
				.AddRazorPagesOptions(options =>
				{
					options.Conventions.AddPageRoute("/Sitemap", "sitemap.xml");
				});

			return services;
		}
	}
}

.template.config/template.json

{
"$schema": "http://json.schemastore.org/template",
"author": "YOUR NAME",
"classifications": [ "ASP.NET Core", "Solution" ],
"identity": "[YOUR NAME].AspNetCoreSolutionTemplateTemplate.CSharp",
"name": "ASP.NET Core One Hour",
"shortName": "aspnetonehour",
"sourceName": "OneHour"
}

References and Other Good Stuff

An Opinionated Approach to ASP.NET Core (Scott Allen)

Grab Bag

Existing Template Solutions

Stuff I Wish I'd Had Time For

Outline

  • Intro (goals/non-goals/what you'll learn)
  • Pick The Right Starting Template
    • Web Application (no auth)
    • Web API
    • SPA
    • Other templates (teaser)
  • Front End
    • Bootstrap
    • Front end build?
    • Libman
    • SEO
  • Maintainability
    • Tests
    • Health Checks
    • Debug Footer
  • Source Control and Solution Structure
    • editorconfig
    • gitignore
  • Database (? - probably no)
    • Data seeding?
    • Migrations?
  • Security?
    • Set up Roles (IsAdmin)
  • Performance
    • ANCM
    • Tiered Compilation
    • Language feature opt-in
    • Caching
    • Miniprofiler

Code Snippets

libman.json

{
  "version": "1.0",
  "defaultProvider": "cdnjs",
  "libraries": [
  {
   "library": "[email protected]",
   "files": [
    "jquery.min.js",
    "jquery.js",
    "jquery.min.map"
   ],
   "destination": "wwwroot/lib/jquery/dist"
  },
  {
   "provider": "unpkg",
   "library": "[email protected]",
   "destination": "wwwroot/lib/bootstrap/",
   "files": [
    "dist/css/bootstrap.css",
    "dist/css/bootstrap-grid.css",
    "dist/css/bootstrap-reboot.css",
    "dist/js/bootstrap.js"
   ]
  },
  {
   "provider": "unpkg",
   "library": "[email protected]",
   "destination": "wwwroot/lib/ionicons",
   "files": [ "dist/css/ionicons.min.css" ]
  }
  ]
}  

template.json

{
"$schema": "http://json.schemastore.org/template",
"author": "YOUR NAME",
"classifications": [ "ASP.NET Core", "Solution" ],
"identity": "[YOUR NAME].AspNetCoreSolutionTemplateTemplate.CSharp",
"name": "ASP.NET Core One Hour",
"shortName": "aspnetonehour",
"sourceName": "OneHour"
}

Sitemap Extension

using Ducksoft.NetCore.Razor.Sitemap.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;

namespace OneHour.Web.Utility
{
	public static class SitemapExtensions
	{
		public static IServiceCollection ConfigureMvcRazorPages(this IServiceCollection services,
			  CompatibilityVersion version, string startPageUrl = "", string startPageArea = "")
		{
			services.AddMvc()
				.SetCompatibilityVersion(version)
				.AddRazorPagesOptions(options =>
				{
					var isSupportAreas = !string.IsNullOrWhiteSpace(startPageArea);
					options.AllowAreas = isSupportAreas;
					options.AllowMappingHeadRequestsToGetHandler = true;
					if (isSupportAreas)
					{
						options.Conventions.AddAreaPageRoute(startPageArea, startPageUrl, string.Empty);
					}
					else if (!string.IsNullOrWhiteSpace(startPageUrl))
					{
						options.Conventions.AddPageRoute(startPageUrl, string.Empty);
					}
				})
				.AddRazorPagesOptions(options =>
				{
					options.Conventions.Add(new SitemapRouteConvention());
				})
				.AddRazorPagesOptions(options =>
				{
					options.Conventions.AddPageRoute("/Sitemap", "sitemap.xml");
				});

			return services;
		}
	}
}

FunctionalTests.cs

using Microsoft.AspNetCore.Mvc.Testing;
using OneHour.Web;
using System;
using System.Threading.Tasks;
using Xunit;

namespace WebApp.Tests
{
	public class BasicTests
		: IClassFixture<WebApplicationFactory<Startup>>
	{
		private readonly WebApplicationFactory<Startup> _factory;

		public BasicTests(WebApplicationFactory<Startup> factory)
		{
			_factory = factory;
		}

		[Theory]
		[InlineData("/")]
		[InlineData("/Index")]
		[InlineData("/Privacy")]
		public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
		{
			// Arrange
			var client = _factory.CreateClient();

			// Act
			var response = await client.GetAsync(url);

			// Assert
			response.EnsureSuccessStatusCode(); // Status Code 200-299
			Assert.Equal("text/html; charset=utf-8",
				response.Content.Headers.ContentType.ToString());
		}

		[Fact]
		public async Task Get_HealthCheckReturnsHealthy()
		{
			// Arrange
			var client = _factory.CreateClient();

			// Act
			var response = await client.GetAsync("/health");

			// Assert
			response.EnsureSuccessStatusCode(); // Status Code 200-299
			Assert.Equal("Healthy",
				response.Content.ReadAsStringAsync().Result);

		}
	}
}

References and Other Good Stuff

An Opinionated Approach to ASP.NET Core (Scott Allen)

Grab Bag

Existing Template Solutions

Stuff I Wish I'd Had Time For

Outline

  • Intro (goals/non-goals/what you'll learn)
  • Pick The Right Starting Template
    • Web Application (no auth)
    • Web API
    • SPA
    • Other templates (teaser)
  • Front End
    • Bootstrap
    • Front end build?
    • Libman
    • SEO
  • Maintainability
    • Tests
    • Health Checks
    • Debug Footer
  • Source Control and Solution Structure
    • editorconfig
    • gitignore
  • Database (? - probably no)
    • Data seeding?
    • Migrations?
  • Security?
    • Set up Roles (IsAdmin)
  • Performance
    • ANCM
    • Tiered Compilation
    • Language feature opt-in
    • Caching
    • Miniprofiler

Code Snippets

libman.json

{
  "version": "1.0",
  "defaultProvider": "cdnjs",
  "libraries": [
  {
   "library": "[email protected]",
   "files": [
    "jquery.min.js",
    "jquery.js",
    "jquery.min.map"
   ],
   "destination": "wwwroot/lib/jquery/dist"
  },
  {
   "provider": "unpkg",
   "library": "[email protected]",
   "destination": "wwwroot/lib/bootstrap/",
   "files": [
    "dist/css/bootstrap.css",
    "dist/css/bootstrap-grid.css",
    "dist/css/bootstrap-reboot.css",
    "dist/js/bootstrap.js"
   ]
  },
  {
   "provider": "unpkg",
   "library": "[email protected]",
   "destination": "wwwroot/lib/ionicons",
   "files": [ "dist/css/ionicons.min.css" ]
  }
  ]
}  

References and Other Good Stuff

An Opinionated Approach to ASP.NET Core (Scott Allen)

Grab Bag

Existing Template Solutions

Stuff I Wish I'd Had Time For

Session Video

ASP.NET Core: The One Hour Makeover - Jon Galloway (YouTube)

Checklist

Integration Testing

Health Checks

API

Hosting Optimizations

So you want a front end build...

Next Steps

General Web Stuff

Web Developer Checklist

Other Opinionated Lists / Templates

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment