Blazor: Implementing Client Side Search As You Type Using bind:event

Updated 12/1/2019 to work with Blazor 3.0+

tldr;

Use bind:event="oninput"instead of bind in order to get real feedback as you type.  bind only databinds during the onchange event which requires losing focus on the input whereas bind:event="oninput"databinds on every keystroke.  Note you will also have to add a bind="PropertyNameHere" as well. See line 3 below:


@page "/"
<input @bind="SearchTerm" @bind:event="oninput" />
<span class="text-muted ml-5">
Showing @FilteredToDos.Count out of @ToDoItems.Count
</span>
<h4 class="mt-4">To Do's</h4>
<ul>
@foreach (var toDo in FilteredToDos)
{
<li>@toDo.Name</li>
}
</ul>
@code {
// Initialize SearchTerm to "" to prevent null's
string SearchTerm { get; set; } = "";
// Imagine this was retrieved from an API, just hardcoding for demo purposes
List<ToDoItem> ToDoItems => new List<ToDoItem>
{
new ToDoItem { Name = "Garbage" },
new ToDoItem { Name = "Dishes" },
new ToDoItem { Name = "Wash clothes" },
new ToDoItem { Name = "Water flowers" }
};
List<ToDoItem> FilteredToDos => ToDoItems.Where(i => i.Name.ToLower().Contains(SearchTerm.ToLower())).ToList();
}

view raw

ToDoList.cshtml

hosted with ❤ by GitHub

 

What is Blazor?

Blazor is an experimental SPA framework, built by Microsoft, running on top of WebAssembly that lets you write C# that runs client-side in a browser.  This allows you to share logic between your server-side C# code and your client-side C# code (such as models, validation, etc.).  Blazor limits (if not entirely eliminates) your need to write JavaScript to make a SPA.  You can learn more by visiting https://blazor.net/.

 

Scenario: Searching a List Client-Side

I want to build a Search feature that allows the user to search an unordered list (<ul>), purely client-side.  I don’t want to go back to the server to search, because in my scenario my data results are fairly small, don’t update often, and I want the response time to the user to be fast and avoid a round-trip.  I was looking around and didn’t see much on the Interwebs showcasing this, which is why I wrote this post.

Note: This scenario could also be tweaked to search through a table (when pulling in a grid component is overkill), searching FAQs in an accordion, or really anything that needs a client-side search, it doesn’t have to be a list.  The same concept applies.

Let’s build a simple component that loops over a list of ToDoItems and displays them in a list.  You could imagine this could be used for a ToDo app, but I’m going to remove all the typical ToDo functionality (like checking off tasks as completed) since it will just add noise to this example.

It’ll look like this on the front-end:

ToDo

 

And the component that powers it looks like this:


@page "/"
<input bind="@SearchTerm" />
<span class="text-muted ml-5">
Showing @FilteredToDos.Count out of @ToDoItems.Count
</span>
<h4 class="mt-4">To Do's</h4>
<ul>
@foreach (var toDo in FilteredToDos)
{
<li>@toDo.Name</li>
}
</ul>
@functions {
// Initialize SearchTerm to "" to prevent null's
string SearchTerm { get; set; } = "";
// Imagine this was retrieved from an API, just hardcoding for demo purposes
List<ToDoItem> ToDoItems => new List<ToDoItem>
{
new ToDoItem { Name = "Garbage" },
new ToDoItem { Name = "Dishes" },
new ToDoItem { Name = "Wash clothes" },
new ToDoItem { Name = "Water flowers" }
};
List<ToDoItem> FilteredToDos => ToDoItems.Where(i => i.Name.ToLower().Contains(SearchTerm.ToLower())).ToList();
}

view raw

ToDoList.cshtml

hosted with ❤ by GitHub

 

On line 3 we are taking an input and using the bind attribute to databind the input’s value to the SearchTerm property.  That SearchTerm property is then used on line 30 to search through our ToDoItems and return any matches in the FilteredToDos property.  Our list loops over the FilteredToDos and displays them out.  Let’s take a look how this works in real life with this gif:

Blazor Bind

 

What’s the problem?  bind fires with onchange

By default, Blazor’s default bind fires on the onchange event.  The onchange event doesn’t fire until the input element loses focus.  This means when a user types in the Search input they need to lose focus on the input before the Search takes place.  While this is okay UX-wise, it’s not exactly ideal.  My ideal world would be to have it fire while the user is typing so they can get real-time feedback without having to do any extra work (like clicking/tabbing out of the input or clicking a search button).

 

Solution: Use bind:event=”oninput” instead of bind

A little known feature about Blazor’s data binding is that bind has options that let you bind to different events, not just onchange.  For our scenario, we want to databind to the oninput event which fires immediately every time the value has changed.  We can do this by adding bind:event="oninput" on line 3 here:


@page "/"
<input @bind="SearchTerm" @bind:event="oninput" />
<span class="text-muted ml-5">
Showing @FilteredToDos.Count out of @ToDoItems.Count
</span>
<h4 class="mt-4">To Do's</h4>
<ul>
@foreach (var toDo in FilteredToDos)
{
<li>@toDo.Name</li>
}
</ul>
@code {
// Initialize SearchTerm to "" to prevent null's
string SearchTerm { get; set; } = "";
// Imagine this was retrieved from an API, just hardcoding for demo purposes
List<ToDoItem> ToDoItems => new List<ToDoItem>
{
new ToDoItem { Name = "Garbage" },
new ToDoItem { Name = "Dishes" },
new ToDoItem { Name = "Wash clothes" },
new ToDoItem { Name = "Water flowers" }
};
List<ToDoItem> FilteredToDos => ToDoItems.Where(i => i.Name.ToLower().Contains(SearchTerm.ToLower())).ToList();
}

view raw

ToDoList.cshtml

hosted with ❤ by GitHub

 

Now when we run our app, we get a much better UX:

Blazor Bind Value OnInput

 

Hope this helps someone else in a similar scenario!

Dynamically setting Content Type in ASP.NET Core with FileExtensionContentTypeProvider

tldr;

If you have a scenario where you have multiple file types (.pdf, .docx, etc.) stored somewhere (in a database, file system, etc.), that need to be downloaded, you can automatically figure out the Content Type by newing up a FileExtensionContentTypeProvider and call TryGetContentType to get the Content Type and pass that to the Fileresult helper.  See lines 8-16 below


[HttpPost("Download/{id:guid}")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Download(Guid id)
{
// Retrieve some file from some service
ApplicationFile file = await _fileService.GetFileAsync(id);
var fileProvider = new FileExtensionContentTypeProvider();
// Figures out what the content type should be based on the file name.
if (!fileProvider.TryGetContentType(file.NameWithExtension, out string contentType))
{
throw new ArgumentOutOfRangeException($"Unable to find Content Type for file name {file.NameWithExtension}.");
}
// contentType will be application/pdf for .pdf's, application/vnd.openxmlformats-officedocument.presentationml.presentation for .pptx's, etc.
return File(file.Contents, contentType);
}

 

What is a Content Type?

A Content Type is how the server tells the browser what type of file the resource being served is.  That way the browser knows how to render whether it’s HTML, CSS, JSON, PDF, etc.  The way the server does this is by passing a Content-Type HTTP Header.  This value of the header will look like “text/html” for HTML, “text/css” for CSS, “application/json” for JSON, “application/pdf” for PDF’s, and so on.  A complete list can be found on the IANA official docs.

Note: A Content Type can also be called a MIME type, but because the header is called Content-Type, and ASP.NET Core calls it the Content Type in the code I’m going to be showing, I’m going to call it Content Type for consistency throughout this post.

 

How do I set the Content Type in ASP.NET Core?

The good news is, for a vast majority of the static files you’re going to serve, the Static Files Middleware will set the Content Type for you.  For scenarios where you need to set the Content Type yourself, you can use the FileContentResult in your Controllers or PageModels via the File helper method used on line 11 below.


[Route("File")]
public class FileController : Controller
{
[HttpPost("Download/{id:guid}")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Download(Guid id)
{
// Grab a test.pdf back one directory. Look ma – it even runs on Linux with Path.DirectorySeparatorChar!
var fileContent = await File.ReadAllBytesAsync($"..{Path.DirectorySeparatorChar}test.pdf");
return File(fileContent, "application/pdf")
}
}

 

What happens if I set the wrong Content Type?

If you set the wrong Content Type, then you may cause issues for your application.  For example, the PDF rendered in the code above will render a PDF in the browser like this:

pdf returned

But what happens if I replace that “application/pdf” string with “application/json” to try to tell the browser the PDF is really JSON?  Well… let’s find out:

returned json

Well that’s not good.  So, setting the correct Content Type is pretty important.  (Also, yes I know I need to update Chrome…. don’t judge me.  I have a bunch of tabs open in another window that I’m totally going to look at some day, ok?)

 

Scenario

Let’s say you have a scenario where you allow admin users to upload files that allows some customer users to download those files.  Those admin users can uploads all sorts of file extensions such as a pdf, pptx, docx, xlsx, etc. that customers can then download themselves.  This means that you can’t assume and be sure what the Content Type should be, so you need to inspect the file extension to figure it out.  No big deal, there are a few ways to solve this, but the simplest is to just write a trusty ol’ switch statement like lines 11-25 below to handle every file type we allow.


[HttpPost("Download/{id:guid}")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Download(Guid id)
{
// Retrieve some file from some service
ApplicationFile file = await _fileService.GetFileAsync(id);
string extension = Path.GetExtension(file.NameWithExtension);
string contentType = "";
switch (extension)
{
case ".pdf":
contentType = "application/pdf";
break;
case ".xslx":
contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
break;
case ".docx":
contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
break;
// and so on
default:
throw new ArgumentOutOfRangeException($"Unable to find Content Type for file name {file.NameWithExtension}.");
}
return File(file.Contents, contentType);
}

The problem with that is trying to maintain a list of all those mappings yourself is annoying, and likely leads you to adding “just one more” when your users want to support another file type you didn’t previously have.  It’s also prone to typo’s, because some of these content types are ridiculously convoluted.

 

Solution

Luckily, ASP.NET Core already has maintained this list for us via the FileExtensionContentTypeProvider.  So all you have to do is new it up, and call TryGetContentType which acts like a lot of the TryX-out pattern you see sprinkled throughout .NET.  It returns a bool and an out variable with the content type.  Usage looks like lines 8-16 below:


[HttpPost("Download/{id:guid}")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Download(Guid id)
{
// Retrieve some file from some service
ApplicationFile file = await _fileService.GetFileAsync(id);
var fileProvider = new FileExtensionContentTypeProvider();
// Figures out what the content type should be based on the file name.
if (!fileProvider.TryGetContentType(file.NameWithExtension, out string contentType))
{
throw new ArgumentOutOfRangeException($"Unable to find Content Type for file name {file.NameWithExtension}.");
}
// contentType will be application/pdf for .pdf's, application/vnd.openxmlformats-officedocument.presentationml.presentation for .pptx's, etc.
return File(file.Contents, contentType);
}

 

So there you have it, now you don’t have to maintain some crazy list of Content Type mappings, you can just lean on ASP.NET Core which maintains those mappings for you.

 

Hope this helps.

Announcing: Blazor Snippets for VS Code

I wrote some Blazor Snippets for VS Code. Download it here!

 

Now that VS Code has Razor support built-in to the C# extension, I wanted to write some Blazor in VS Code.  I was surprised to not see any Blazor-specific snippets created so I created my first VS Code extension for the Blazor Snippets.

 

Examples

  • Using bc to create a blank Blazor Component, bpara to create a parameter, boi to create OnInitAsync, and binjhttpto inject an HttpClient.

Snippets used together

  • Using bchttp to scaffold out a Blazor Component with an HttpClient call.

Snippets used together

 

There are over 20 snippets total right now and the full list of snippets is documented out on the GitHub readme.

 

Closing

Log an issue on the GitHub repo if you want to see new snippets or if you see any issues.

 

Hope this helps!

Strongly Typing Dapper Queries Using nameof

tldr;

Use nameof in order to strongly type your Dapper queries, so renaming a property on your C# doesn’t break your Dapper query or require you to remember to go fix them.

 

Before Stringly Typed (note lines 12-14):

 

 

 


public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string ZipCode { get; set; }
}
public List<Customer> GetCustomersByState(string state)
{
var dbConnection = new SqlConnection("SomeConnectionString");
string sql = @"SELECT F_Name AS FirstName,
L_Name AS LastName,
Zip_Code AS ZipCode
FROM Customers
WHERE State = @State";
var parameters = new DynamicParameters();
parameters.Add("State", state);
return dbConnection.Query<Customer>(sql, parameters).ToList();
}

 

After Strongly Typed (note lines 12-14):

 

 


public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string ZipCode { get; set; }
}
public List<Customer> GetCustomersByState(string state)
{
var dbConnection = new SqlConnection("SomeConnectionString");
string sql = $@"SELECT F_Name AS {nameof(Customer.FirstName)},
L_Name AS {nameof(Customer.LastName)},
Zip_Code AS {nameof(Customer.ZipCode)}
FROM Customers
WHERE State = @State";
var parameters = new DynamicParameters();
parameters.Add("State", state);
return dbConnection.Query<Customer>(sql, parameters).ToList();
}

 

What is Dapper?

Dapper is a lightweight micro-ORM, made by the Stack Exchange team, that transforms your SQL into strongly typed objects.  No longer do you have to deal with DataTables, DataSets, DataReaders, and the like.  You can just deal with your objects like a Customer object with FirstName, LastName, and ZipCode properties.

Dapper is a great alternative to a more full-featured ORM like Entity Framework.  Sometimes, Entity Framework is too much for a query (because the LINQ is hard/impossible to write) or sometimes it’s just overkill.

Dapper is implemented as a set of extension methods hanging off of DbConnection and a simple example is below:

 

 

 

 

 


public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string ZipCode { get; set; }
}
public List<Customer> GetCustomersByState(string state)
{
var dbConnection = new SqlConnection("SomeConnectionString");
string sql = @"SELECT FirstName,
LastName,
ZipCode
FROM Customers
WHERE State = @State";
var parameters = new DynamicParameters();
parameters.Add("State", state);
return dbConnection.Query<Customer>(sql, parameters).ToList();
}

Dapper will open and close the connection for you, parameterize your SQL query, and then it will hydrate the Customer object’s FirstNameLastName, and ZipCode properties, because they match the SQL column names.

But let’s say you have SQL column names that quite don’t match your C# properties like F_NameL_Name, and Zip_Code.  No big deal, all you do is alias the column name to match the C# property like below (lines 12-14):

 

 

 


public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string ZipCode { get; set; }
}
public List<Customer> GetCustomersByState(string state)
{
var dbConnection = new SqlConnection("SomeConnectionString");
string sql = @"SELECT F_Name AS FirstName,
L_Name AS LastName,
Zip_Code AS ZipCode
FROM Customers
WHERE State = @State";
var parameters = new DynamicParameters();
parameters.Add("State", state);
return dbConnection.Query<Customer>(sql, parameters).ToList();
}

As you can see, it’s pretty simple to use and results in super clean code.  Dapper is also much faster than Entity Framework, because it’s closer to the metal, but you also give up some features compared to something like Entity Framework such as LINQ queries, Change Tracking, Query Filters, Migrations, etc.

It’s really common for the projects I work on to use Dapper and EF together depending on what the best tool is for the job.

 

Downsides of Dapper

However, there are of course downsides to Dapper compared to Entity Framework.  With Entity Framework, you can be almost 100% guaranteed that your query won’t blow up when you go to run it (short of a schema problem, but that would be an issue in any data access library).

With Dapper, it’s up to you to write the correct SQL in a string.  If you misspell a column name, the C# compiler won’t yell at you, and you won’t find out until you go to run it.  Same goes for all the normal SQL goof ups like inserting too many or too few commas, forgetting a space, etc.

To  mitigate this, I like to write my queries in SQL Management Studio, with the help of RedGate’s SQL Prompt extension, and then copy and paste that query into my C# code to use with Dapper.

 

Problem When Renaming C# Properties

Unfortunately, using SQL Management Studio to build your queries doesn’t solve all the problems.  Let’s say I want to rename the ZipCode property to ZipCode9 to be more obvious that this is a 9 digit Zip Code and not a 5 or 4 digit one.  The problem is when I rename my property, if I don’t remember to go fix the Dapper query, then the ZipCode9 property will be null and everything will fail silently.  This is…. less than ideal.

 

Solution

To fix this, we can use the C# 6 feature nameof which lets you get the name of a variable, property, etc.  So if I change the query to look like below:

 

 

 

 


public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string ZipCode9 { get; set; }
}
public List<Customer> GetCustomersByState(string state)
{
var dbConnection = new SqlConnection("SomeConnectionString");
string sql = $@"SELECT F_Name AS {nameof(Customer.FirstName)},
L_Name AS {nameof(Customer.LastName)},
Zip_Code AS {nameof(Customer.ZipCode9)}
FROM Customers
WHERE State = @State";
var parameters = new DynamicParameters();
parameters.Add("State", state);
return dbConnection.Query<Customer>(sql, parameters).ToList();
}

This will drop the Customer. at the beginning and just leave you with ZipCode9.  So it ends up being the exact same query that we had before, but now it’s strongly typed.

Now when you go to rename a property on your C# model, either your editor (like Visual Studio, Rider, etc.) will rename the property for you.  Or… if you forget to let your editor do the hard work for you, then your app won’t even compile and it’ll tell you this field is broken.  This effectively gives you strongly typed queries for your Dapper code and eliminates one of the headaches that most people have with Dapper.

Obviously, it comes at some cost of readability, but after using this approach for a few months, I can sift through the noise really quick and it’s saved me a handful of times.

 

Conclusion

A few months ago I tweeted this:

 

This was in reference to this nameof solution.  I fully recognize some of you will love this, some of you will hate this, and some of you will sit in the middle like I did originally.

That said, members of my team and I have started using this approach as our default and I’ve actually been fairly happy with it.  Like I said earlier, I can sift through the nameof noise quickly and the strongly typedness has already saved me a handful of times.

 

Hope this helps!

Walkthrough: Creating an HTML Email Template with Razor and Razor Class Libraries

Updated 2021-12-31: Updated to .NET 6.0 and Mailkit 3.0

Updated 2021-02-14: Updated to .NET 5.0 and latest Mailkit

Updated 2020-06-20: Update for new Razor Class Library options for “support pages and views” option that is required for this to work.

Updated 2020-04-18: As of .NET Core 3, ASP.NET Core targets .NET Core directly, instead of .NET Standard.  This means you cannot use a .NET Standard Class Library and reference ASP.NET Core.  Please see this repo for an example of getting Razor Emails working with .NET Core 3.1.  This post has been updated to just reference a .NET Core Library instead of .NET Standard Library.

tldr;

HtmlEmailExample

Full Code: https://github.com/scottsauber/RazorHtmlEmails

 

Usually I don’t blog walkthroughs and instead prefer to go a little deeper on a small topic, but I thought it would be useful to blog our approach on generating HTML emails using Razor for an ASP.NET Core insurance application at work.

 

If you’re looking to generate HTML Emails outside of ASP.NET Core (such as a Console app), I also recommend checking out Derek Comartin‘s post: Using Razor in a Console Application (outside of ASP.NET Core MVC).

 

Purpose

HTML emails are the absolute worst.  Inlined styles.  Nested tables.  Different apps and platforms render the markup differently.  It’s an absolute train wreck.  However, sending plain text emails isn’t a great way to impress your users and often makes emails look fake or like spam.  So fine, HTML emails.  Let’s do it.

 

What I would like to do is create an HTML Email Template library, and let developers focus on the content of their email, and not worry about the extra HTML and CSS voodoo that is HTML Emails.

 

Also, I want to be able to generate these HTML Emails from within a .NET Class Library, so that the sending of the emails happens right next to all my other business logic.  That way I can re-purpose this logic into an ASP.NET Core app or a .NET Console app (such as a Worker Service).

 

So at a high level, the requirements are:

  1. Create a base Email Layout to enforce a consistent layout (Header, Footer, base styles, etc.) and to hide the complexity of HTML Email Layouts.
  2. Create re-usable HTML Email components (such as buttons) to enforce consistent styling and hide the complexity of the implementation of the HTML Email components.
  3. Use Razor.
  4. Be able to call it from a .NET Core Class Library.

 

Razor checks the box for #1, because it already has the concept of a Layout view and a child view.  It also is a good fit for #2, because it lets you re-use UI components via Partials (among other methods).  In fact, you can actually achieve #1, #2, and #3 in regular ASP.NET 4.x fairly easily.  However, I haven’t ever been able to achieve #4 in regular ASP.NET or pre-2.1 ASP.NET Core.  That is, I want to use Razor in a non-ASP.NET/ASP.NET Core setting such as Class Libraries.

 

It’s super common for applications to put their business logic in a Class Library to remove any dependency on the UI project and to allow that logic to be re-used across other applications.  However, when I tried to send HTML Emails from a Class Library in ASP.NET 4.x and ASP.NET Core pre-2.1, I couldn’t figure out how to get the Class Library to find the Views I was creating and ultimately I gave up.

 

Enter Razor Class Libraries

I won’t go into much detail about Razor Class Libraries, when the documentation already does a fantastic job, but the basic idea behind them is that you can share UI between multiple ASP.NET Core applications.  This UI can be Views, Controllers, Razor Pages, etc.

 

The simplest way to think about Razor Class Libraries is if you add a View in your Razor Class Library, it essentially gets copied down into that same relative path into your main application.  So if you add an Index.cshtml file to the /Views/Home path of your Razor UI Class Library, then that file will be available at /Views/Home/Index.cshtml of your ASP.NET Core Application.  That’s pretty sweet and useful. But the question is would it find those files in a normal .NET Core Class Library?  The answer is – yes.

 

One potential gotcha: Make sure that your Views have unique names/paths.  If you make an HTML Email view that matches the path of an actual like MVC View or Razor Page, then they will conflict.  Therefore, I try to make my folder and/or view names clearly unique like “ConfirmAccountEmail.cshtml” which is unlikely to have a matching route on my ASP.NET Core application.

 

Alright then, let the walkthrough begin!

Again – all code can be found here on GitHub

Create an ASP.NET Core Web Application

First – create an ASP.NET Core Web Application or you can use an existing ASP.NET Core Web App.  It doesn’t really matter what it is.

With Visual Studio:

  1. File
  2. New Project
  3. ASP.NET Core Web Application
    1. I just called it RazorHtmlEmails.AspNetCore
  4. Web Application
  5. OK

 

Or with the command line:

  1. dotnet new sln -n RazorHtmlEmails
  2. dotnet new razor -n RazorHtmlEmails.AspNetCore
  3. dotnet sln RazorHtmlEmails.sln add RazorHtmlEmails.AspNetCore

 

 

Create a Razor Class Library

Next – create an ASP.NET Core Razor Class Library.

With Visual Studio:

  1. Right-click the Solution
  2. Add
  3. New Project
  4. Razor Class Library
  5. Next
  6. Give it a name, I just called it RazorHtmlEmails.RazorClassLib
  7. Create
  8. Check the box for “Support pages and views” in the bottom right
    1. rcl
  9. Create

 

Or… with the command line (NOTE: the -s for “support pages and views”):

  1. dotnet new razorclasslib -n RazorHtmlEmails.RazorClassLib -s
  2. dotnet sln RazorHtmlEmails.sln add RazorHtmlEmails.RazorClassLib

 

After you’ve created your Razor Class Library, delete out the Areas folder, because we won’t need it.

 

Create the RazorViewToStringRenderer class

Tucked away in the aspnet/Entropy GitHub repo is the RazorViewToStringRenderer which shows you how to take a Razor View and render it to an HTML string.  This will be perfect for taking our Razor View, converting it to a string of HTML, and being able to stick the HTML into the body of an email.

 

Add this class to your Razor Class Library you just created.  I tucked mine under a folder called Services and then created an Interface for it called IRazorViewToStringRenderer:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace RazorHtmlEmails.RazorClassLib.Services;
// Code from: https://github.com/aspnet/Entropy/blob/master/samples/Mvc.RenderViewToString/RazorViewToStringRenderer.cs
public class RazorViewToStringRenderer : IRazorViewToStringRenderer
{
private readonly IRazorViewEngine _viewEngine;
private readonly ITempDataProvider _tempDataProvider;
private readonly IServiceProvider _serviceProvider;
public RazorViewToStringRenderer(
IRazorViewEngine viewEngine,
ITempDataProvider tempDataProvider,
IServiceProvider serviceProvider)
{
_viewEngine = viewEngine;
_tempDataProvider = tempDataProvider;
_serviceProvider = serviceProvider;
}
public async Task<string> RenderViewToStringAsync<TModel>(string viewName, TModel model)
{
var actionContext = GetActionContext();
var view = FindView(actionContext, viewName);
await using var output = new StringWriter();
var viewContext = new ViewContext(
actionContext,
view,
new ViewDataDictionary<TModel>(
metadataProvider: new EmptyModelMetadataProvider(),
modelState: new ModelStateDictionary())
{
Model = model
},
new TempDataDictionary(
actionContext.HttpContext,
_tempDataProvider),
output,
new HtmlHelperOptions());
await view.RenderAsync(viewContext);
return output.ToString();
}
private IView FindView(ActionContext actionContext, string viewName)
{
var getViewResult = _viewEngine.GetView(executingFilePath: null, viewPath: viewName, isMainPage: true);
if (getViewResult.Success)
{
return getViewResult.View;
}
var findViewResult = _viewEngine.FindView(actionContext, viewName, isMainPage: true);
if (findViewResult.Success)
{
return findViewResult.View;
}
var searchedLocations = getViewResult.SearchedLocations.Concat(findViewResult.SearchedLocations);
var errorMessage = string.Join(
Environment.NewLine,
new[] { $"Unable to find view '{viewName}'. The following locations were searched:" }.Concat(searchedLocations));
throw new InvalidOperationException(errorMessage);
}
private ActionContext GetActionContext()
{
var httpContext = new DefaultHttpContext
{
RequestServices = _serviceProvider
};
return new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
}
}
public interface IRazorViewToStringRenderer
{
Task<string> RenderViewToStringAsync<TModel>(string viewName, TModel model);
}

 

Create your base HTML Email Layout

The next step in the process is we are going to leverage Razor’s Layout system to create a base HTML Email Layout.  There are many HTML Email Layout templates out there so you don’t have to write one yourself (and trust me… if you’ve never seen HTML Email code before, you’re not going to want to write this yourself).

I’m picking this one from Litmus, which is the leading vendor  (as far as I know) for testing your HTML Emails on multiple different platforms, devices, and applications.  No they didn’t pay me to say that (although if someone from Litmus is reading this, it’d be cool if you did).

The layout looks like this:

HtmlEmailExample

 

However, all I really care about for the layout is everything outside of the white box for my layout.  Inside the white box will change based on whatever I’m sending (Email Registration Confirmations, Forgot Password requests, Password Updated Alerts, etc.).

 

HtmlEmailLayout

Steps:

  1. In your Razor UI Class Library, create a /Views/Shared folder
  2. Add an EmailLayout.cshtml to that folder
  3. Add the following code and try not to pass out

 

<!DOCTYPE html>
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<style type="text/css">
/* FONTS */
@@media screen {
@@font-face {
font-family: 'Lato';
font-style: normal;
font-weight: 400;
src: local('Lato Regular'), local('Lato-Regular'), url(https://fonts.gstatic.com/s/lato/v11/qIIYRU-oROkIk8vfvxw6QvesZW2xOQ-xsNqO47m55DA.woff) format('woff');
}
@@font-face {
font-family: 'Lato';
font-style: normal;
font-weight: 700;
src: local('Lato Bold'), local('Lato-Bold'), url(https://fonts.gstatic.com/s/lato/v11/qdgUG4U09HnJwhYI-uK18wLUuEpTyoUstqEm5AMlJo4.woff) format('woff');
}
@@font-face {
font-family: 'Lato';
font-style: italic;
font-weight: 400;
src: local('Lato Italic'), local('Lato-Italic'), url(https://fonts.gstatic.com/s/lato/v11/RYyZNoeFgb0l7W3Vu1aSWOvvDin1pK8aKteLpeZ5c0A.woff) format('woff');
}
@@font-face {
font-family: 'Lato';
font-style: italic;
font-weight: 700;
src: local('Lato Bold Italic'), local('Lato-BoldItalic'), url(https://fonts.gstatic.com/s/lato/v11/HkF_qI1x_noxlxhrhMQYELO3LdcAZYWl9Si6vvxL-qU.woff) format('woff');
}
}
/* CLIENT-SPECIFIC STYLES */
body, table, td, a {
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
table, td {
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
}
img {
-ms-interpolation-mode: bicubic;
}
/* RESET STYLES */
img {
border: 0;
height: auto;
line-height: 100%;
outline: none;
text-decoration: none;
}
table {
border-collapse: collapse !important;
}
body {
height: 100% !important;
margin: 0 !important;
padding: 0 !important;
width: 100% !important;
}
/* iOS BLUE LINKS */
a[x-apple-data-detectors] {
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
/* MOBILE STYLES */
@@media screen and (max-width:600px) {
h1 {
font-size: 32px !important;
line-height: 32px !important;
}
}
/* ANDROID CENTER FIX */
div[style*="margin: 16px 0;"] {
margin: 0 !important;
}
</style>
</head>
<body style="background-color: #f4f4f4; margin: 0 !important; padding: 0 !important;">
<!– HIDDEN PREHEADER TEXT –>
<div style="display: none; font-size: 1px; color: #fefefe; line-height: 1px; font-family: 'Lato', Helvetica, Arial, sans-serif; max-height: 0px; max-width: 0px; opacity: 0; overflow: hidden;">
We're thrilled to have you here! Get ready to dive into your new account.
</div>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<!– LOGO –>
<tr>
<td bgcolor="#539be2" align="center">
<!–[if (gte mso 9)|(IE)]>
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600">
<tr>
<td align="center" valign="top" width="600">
<![endif]–>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;">
<tr>
<td align="center" valign="top" style="padding: 40px 10px 40px 10px;">
<a href="http://litmus.com" target="_blank">
<img alt="Logo" src="http://litmuswww.s3.amazonaws.com/community/template-gallery/ceej/logo.png" width="40" height="40" style="display: block; width: 40px; max-width: 40px; min-width: 40px; font-family: 'Lato', Helvetica, Arial, sans-serif; color: #ffffff; font-size: 18px;" border="0">
</a>
</td>
</tr>
</table>
<!–[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]–>
</td>
</tr>
<!– HERO –>
<tr>
<td bgcolor="#539be2" align="center" style="padding: 0px 10px 0px 10px;">
<!–[if (gte mso 9)|(IE)]>
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600">
<tr>
<td align="center" valign="top" width="600">
<![endif]–>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;">
<tr>
<td bgcolor="#ffffff" align="center" valign="top" style="padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #111111; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; letter-spacing: 4px; line-height: 48px;">
<h1 style="font-size: 48px; font-weight: 400; margin: 0;">@ViewData["EmailTitle"]</h1>
</td>
</tr>
</table>
<!–[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]–>
</td>
</tr>
<!– COPY BLOCK –>
<tr>
<td bgcolor="#f4f4f4" align="center" style="padding: 0px 10px 0px 10px;">
<!–[if (gte mso 9)|(IE)]>
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600">
<tr>
<td align="center" valign="top" width="600">
<![endif]–>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;">
<!– COPY –>
<tr>
<td bgcolor="#ffffff" align="left" style="padding: 20px 30px 40px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;">
@RenderBody()
</td>
</tr>
</table>
<!–[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]–>
</td>
</tr>
<!– FOOTER –>
<tr>
<td bgcolor="#f4f4f4" align="center" style="padding: 0px 10px 0px 10px;">
<!–[if (gte mso 9)|(IE)]>
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600">
<tr>
<td align="center" valign="top" width="600">
<![endif]–>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;">
<!– NAVIGATION –>
<tr>
<td bgcolor="#f4f4f4" align="left" style="padding: 30px 30px 30px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: 400; line-height: 18px;">
<p style="margin: 0;">
<a href="http://litmus.com" target="_blank" style="color: #111111; font-weight: 700;">Dashboard</a> –
<a href="http://litmus.com" target="_blank" style="color: #111111; font-weight: 700;">Billing</a> –
<a href="http://litmus.com" target="_blank" style="color: #111111; font-weight: 700;">Help</a>
</p>
</td>
</tr>
<!– UNSUBSCRIBE –>
<tr>
<td bgcolor="#f4f4f4" align="left" style="padding: 0px 30px 30px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: 400; line-height: 18px;">
<p style="margin: 0;">If these emails get annoying, please feel free to <a href="http://litmus.com" target="_blank" style="color: #111111; font-weight: 700;">unsubscribe</a>.</p>
</td>
</tr>
<!– ADDRESS –>
<tr>
<td bgcolor="#f4f4f4" align="left" style="padding: 0px 30px 30px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: 400; line-height: 18px;">
<p style="margin: 0;">Contoso – 1234 Main Street – Anywhere, MA – 56789</p>
</td>
</tr>
</table>
<!–[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]–>
</td>
</tr>
</table>
</body>
</html>

When you finish throwing up in your mouth, let’s look at a couple important bits. Line 165 has the RenderBody() call which is where the white box is and where our child view will be placed dynamically based on the email I’m sending.

 

Another interesting spot is line 142 where I’m dynamically pulling an EmailTitle property from ViewDataViewData allows us to pass messages from a child view up to the parent view.  In the email screenshot above, this is the “Welcome” hero text.

 

ViewData

 

In a real application, I would have pulled that Welcome text down to the child view as well, but I left that as a demonstration of the ability for the child view to dynamically change the parent EmailLayout.  Some more examples of what you could do with ViewData could be the child view wants to dictate which Logo to use or what color the background is, or literally anything you want.  This is simply just leveraging a feature that’s been part of MVC for a long time.

 

Now that we’ve finished the Email Layout, let’s look at adding a custom button component.

 

 

Create a HTML Email Button Partial for re-usability

The next thing I want to do is start to make reusable components via partial views in order to abstract away the complexity of certain HTML Email components, as well as always providing a consistent look to the end user.

 

A good example of this is the button:

EmailButton

 

All that really needs to be dynamic about this is the text and the link. That should be easy enough.

Steps:

  1. Under /Views/Shared add a EmailButtonViewModel.cs record
  2. Add the following code:

namespace RazorHtmlEmails.RazorClassLib.Views.Shared;
public record EmailButtonViewModel(string Text, string Url);

3. Add an EmailButton.cshtml file
4. Add the following code:

@using RazorHtmlEmails.RazorClassLib.Views.Shared
@model EmailButtonViewModel
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td bgcolor="#ffffff" align="center" style="padding: 20px 30px 60px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 3px;" bgcolor="#539be2">
<a href="@Model.Url" target="_blank" style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 15px 25px; border-radius: 2px; border: 1px solid #539be2; display: inline-block;">
@Model.Text
</a>
</td>
</tr>
</table>
</td>
</tr>
</table>

 

Now we can re-use buttons in our child views by simply doing this:

@await Html.PartialAsync("EmailButton", new EmailButtonViewModelModel("Confirm Account", "https://google.com&quot;))

That saves us from copying and pasting that crazy table code around every time we need a button for our Emails, which is useful.

 

Create your HTML Email Content

Now we’ve got all of our foundation pieces in place, let’s start building on top of them. The last thing we need to do in our Razor Class Library is add our custom email.

 

  1. Add a new folder under Views called Emails. This folder will house all of our custom HTML Emails.
  2. Add a _ViewStart.cshtml and add the following code so that every HTML Email has the EmailLayout.cshtml we created above as its parent layout

@{
Layout = "EmailLayout";
}

3. Add a ConfirmAccount folder under /Views/Emails.  This folder will house our ConfirmAccount email logic.

4. Add a ConfirmAccountEmailViewModel.cs record with the following code:

namespace RazorHtmlEmails.RazorClassLib.Views.Emails.ConfirmAccount;
public record ConfirmAccountEmailViewModel(string ConfirmEmailUrl);

5. Add a ConfirmAccount.cshtml file with the following code:

@using RazorHtmlEmails.RazorClassLib.Views.Emails.ConfirmAccount
@using RazorHtmlEmails.RazorClassLib.Views.Shared
@model ConfirmAccountEmailViewModel
@{
ViewData["EmailTitle"] = "Welcome!";
}
<p>
We're excited to have you get started. First, you need to confirm your account. Just press the button below.
</p>
<br />
@await Html.PartialAsync("EmailButton", new EmailButtonViewModel("Confirm Account", Model.ConfirmEmailUrl))
<br />
<p>
If you have any questions, just reply to this email—we're always happy to help out.
</p>
<br />
<p>
The Contoso Team
</p>

Right now is where it all starts to come together, and you can see the power of being able to use Razor to build out our HTML emails.  On our day-to-day emails that we build out for the rest of our application, we no longer have to worry about gross table syntax or inline style craziness, we can just focus on the custom content that makes up that HTML Email.

 

Create a .NET Core Class Library

But one of the coolest things about this, is the fact that we can call this code from regular .NET Core) Class Libraries, and have it all “just work.”  This means we can share our email templates across multiple applications to provide the same look and feel across all of them, and have all that logic live right next to the rest of our business logic.

 

So let’s create a .NET Core Class Library.  If you want you can create a .NET Core Library and it’ll work as well.

With Visual Studio:

  1. Right-click the Solution
  2. Add
  3. New Project
  4. Class Library (.NET Core)
    1. I just called it RazorHtmlEmails.Common
  5. OK

 

Or with the command line:

  1. dotnet new classlib -n RazorHtmlEmails.Common
  2. dotnet sln RazorHtmlEmails.sln add RazorHtmlEmails.Common

 

Then delete out Class1.cs

 

Call the RazorViewToStringRenderer in your business logic

Now it’s time to hook up the .NET Core Class Library to the Razor Class Library.

  1. In the .NET Core Class Library (.Common), add a reference to the Razor Class Library (.RazorEmailLib)
  2. Install MailKit, a fantastic cross-platform, .NET Core-friendly email library
  3. Create a class called RegisterAccountService that will house our business logic for creating the account and sending the email.

using MailKit.Net.Smtp;
using MimeKit;
using MimeKit.Text;
using RazorHtmlEmails.RazorClassLib.Services;
using RazorHtmlEmails.RazorClassLib.Views.Emails.ConfirmAccount;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace RazorHtmlEmails.Common;
public class RegisterAccountService : IRegisterAccountService
{
private readonly IRazorViewToStringRenderer _razorViewToStringRenderer;
public RegisterAccountService(IRazorViewToStringRenderer razorViewToStringRenderer)
{
_razorViewToStringRenderer = razorViewToStringRenderer;
}
public async Task Register(string email, string baseUrl)
{
// TODO: Validation + actually add the User to a DB + whatever else
// TODO: Base URL off of ASP.NET Core Identity's logic or some other mechanism, rather than hard coding to creating a random guid
var confirmAccountModel = new ConfirmAccountEmailViewModel($"{baseUrl}/{Guid.NewGuid()}");
string body = await _razorViewToStringRenderer.RenderViewToStringAsync("/Views/Emails/ConfirmAccount/ConfirmAccountEmail.cshtml", confirmAccountModel);
var toAddresses = new List<string> { email };
SendEmail(toAddresses, "donotreply@contoso.com", "Confirm your Account", body);
}
// TODO: In reality, you probably want to make an EmailService that houses this code, but #Demoware
private void SendEmail(List<string> toAddresses, string fromAddress, string subject, string body)
{
var message = new MimeMessage();
message.From.Add(new MailboxAddress("SenderFirstName SenderLastName", fromAddress));
foreach (var to in toAddresses)
{
message.To.Add(new MailboxAddress("RecipientFirstName RecipientLastName", to));
}
message.Subject = subject;
message.Body = new TextPart(TextFormat.Html)
{
Text = body
};
using var client = new SmtpClient
{
// For demo-purposes, accept all SSL certificates
ServerCertificateValidationCallback = (_, _, _, _) => true
};
client.Connect("127.0.0.1", 25, false);
client.Send(message);
client.Disconnect(true);
}
}
public interface IRegisterAccountService
{
Task Register(string email, string baseUrl);
}

The interesting bits are lines 25, where we create our ConfirmAccountEmailViewModel with the link we want to use and line 27 where we pass that model into our RazorViewToStringRenderer, and get back our Email’s HTML body.

As an aside, for testing local emails without an email server, I love using Papercut.  It’s a simple install and then you’re up and going with an email server that runs locally and also provides an email client.  No Papercut didn’t pay me to say that either.  It’s free.  If some paid service that does the same thing as Papercut does and wants to sponsor this blog, feel free to reach out to me and get rejected because you will never get me to give up Papercut.

 

Hook it up to the UI

The last step we have to do is to hook this up to the UI.  I’m just going to hook this up on a GET request of the /Index page just for demo purposes.  In reality, you’d have an input form that takes registration and have this happen on the POST.

  1. In your .AspNetCore project, add a project reference to .Common
  2. Wire up the IRegisterAccountService and IRazorViewToStringRenderer to ther implementations.
  3. If using the Minimal Hosting Model, then add the following: 

builder.Services.AddScoped<IRegisterAccountService, RegisterAccountService>();
builder.Services.AddScoped<IRazorViewToStringRenderer, RazorViewToStringRenderer>();
view raw Program.cs hosted with ❤ by GitHub

      4. If using the Startup.cs Hosting Model then add the following to the ConfigureServices method:

// In Startup.cs in the ConfigureServices method
services.AddScoped<IRegisterAccountService, RegisterAccountService>();
services.AddScoped<IRazorViewToStringRenderer, RazorViewToStringRenderer>();
view raw Startup.cs hosted with ❤ by GitHub

5. In /Pages/Index.cshtml.cs, call the IRegisterAccountService in the OnGetAsync method

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorHtmlEmails.Common;
namespace RazorHtmlEmails.AspNetCore.Pages;
public class IndexModel : PageModel
{
private readonly IRegisterAccountService _registerAccountService;
public IndexModel(IRegisterAccountService registerAccountService)
{
_registerAccountService = registerAccountService;
}
public async Task OnGetAsync()
{
// In reality, you would have this on a POST and pass along user input and not just have the Confirm Account link be the Index page… but #Demoware
await _registerAccountService.Register("testmctestyface@contoso.com", Url.Page("./Index"));
}
}
view raw Index.cshtml.cs hosted with ❤ by GitHub

 

 

And you’re done!  When I run the app, and open up Papercut, I get my email in all its glory.

papercut

Going forward, anytime I need an email, I can simply leverage the EmailLayout and EmailButton infrastructure I’ve created to make creating HTML emails incredibly easy and less table-y which is a huge win.

 

Just a reminder if somehow you’ve made it this far, all the code is out here on GitHub.

 

Hope this helps!

 

Increasing Password Hashing Iterations with ASP.NET Core Identity

tldr;

It’s extremely easy to increase the number of iterations in the default ASP.NET Core Identity PasswordHasher.  ASP.NET Core Identity will also take care of rehashing the password if it was previously hashed with a lower iteration count, so you can increase this at any time.  However, test the performance of the login page of your application before changing this number, to make sure you don’t set it too high.

 


public void ConfigureServices(IServiceCollection services)
{
services.Configure<PasswordHasherOptions>(options => options.IterationCount = 100_000);
// Note: the number 100,000 is not a formal recommendation. Test the performance of your login page before changing this.
// Do what makes sense for your application, the hardware it runs on, and the hardware climate at the time when you read this.
// Other entries remove for brevity.
}

view raw

Startup.cs

hosted with ❤ by GitHub

 

Password Hashing and Salting Basics

I’m not going to cover why you should be hashing passwords in your application.  Presumably, if you’ve landed on this blog post, you already know why hashing passwords is infinitely more secure than encrypting them (or worse… storing them in plaintext).  Andrew Lock goes into detail why if you’re looking for that information.  Instead, I’m going to talk about hashing iterations, specifically.

 

ASP.NET Core Identity Default – 10,000 iterations

As Andrew mentions in his blog post linked above, the default password hashing algorithm is PBKDF2 with HMAC-256, a 128-bit salt, 256-bit subkey, and 10,000 iterations.  The 10,000 iterations is the part we’re interested in for this blog post.  In June 2017, the NIST recommended at least 10,000 iterations on the 25th page of their report under section 5.1.1.2, so this is a reasonable default.

 

Why do iterations matter?

Iterations is the “work factor” for how many times you hash a password before you store it in your database (if you’re not using something like Auth0 that stores your application’s password hashes for you).  In the default ASP.NET Core Identity implementation, you would take a password and hash it once.  Then you hash the hash, then you hash that double hash, and so on until you’ve hashed the password 10,000 times.

The more times you hash the password, the longer it takes your CPU to complete that operation, and therefore the longer it would take someone to brute force the password, if someone were to ever get your password hash.  So if someone malicious were to ever compromise your database containing your application’s usernames and password hashes (it’s not like that’s ever happened before), the higher your iteration count is, the harder it is for them to crack your password hashes.  The goal here is to buy time, because eventually (given enough time), they will crack a user’s password hash.  The more time you have to notify a user and give them a chance to change their password, the better.

So if you increase the number of iterations from 10,000 to 100,000, you are making it 10x harder for someone to crack your hash, because their CPU/GPU has 10x more work to do.

While 10,000 iterations may (or may not) be enough for your application today, as hardware gets faster and faster, there will come a day when 10,000 iterations with this algorithm will certainly not be enough.  We’ve already seen weaker algorithms get cracked like MD-5 and SHA-1.   Therefore, it is critical that we’re able to change this at some point to maintain the proper security for our application.

 

How can I change it?

As mentioned in the tldr; – it’s just a one-liner to change it in ASP.NET Core Identity, which is a HUGE upgrade from regular ASP.NET Identity where you had to provide a whole new PasswordHasher implementation.  Simply go to ConfigureServices and configure PasswordHasherOptions.IterationCount and that’s it!

 


public void ConfigureServices(IServiceCollection services)
{
services.Configure<PasswordHasherOptions>(options => options.IterationCount = 100_000);
// Note: the number 100,000 is not a formal recommendation. Test the performance of your login page before changing this.
// Do what makes sense for your application, the hardware it runs on, and the hardware climate at the time when you read this.
// Other entries remove for brevity.
}

view raw

Startup.cs

hosted with ❤ by GitHub

 

But won’t this break my existing users?

Nope, it won’t break your existing users!  That’s the cherry on top.  The iteration count is also stored as part of the password hash in the Identity database (more details here).

For example, let’s say you started your application with the default of 10,000 iterations, and then later you decided to increase it to 25,000.  When a user provides a valid username and password combination to your login page/endpoint, the PasswordHasher checks to see how many iterations the current password is hashed with.  It then checks to see if that database-stored iteration count is less than PasswordHasherOptions.IterationCount.  In this case, the hash is stored in the database with an iteration count of 10,000 and the PasswordHasherOptions.IterationCount is 25,000.  Therefore, the PasswordHasher will rehash the password using 25,000 iterations and save it back to the database.  This allows you to progressively upgrade your site to a stronger iteration count.

If you’re curious how this works, check out the source code.

Obviously, if a user never logs in again, their password will be stored at the lower iteration count forever, which may be a problem.  There are ways to solve this problem, but that’s outside the scope of this post.

NOTE: It was mentioned subtly, so I’m going to call it out again.  It will only re-hash the password if the database-stored iteration count is LESS than the  PasswordHasherOptions.IterationCount.  So if you do something silly like set the PasswordHasherOptions.IterationCount to 1 trillion temporarily, and then reset it back to 10,000 later, every user who logged into your site when it was 1 trillion will NEVER be downgraded to 10,000.  This will likely be a performance problem and leave you in a world of hurt.  More on performance next.

 

Performance considerations

Like anything in software, there are always trade-offs.  There is no free lunch.  If you increase the number of iterations, then your server(s) will have to do extra work every time you log someone in.  So you probably don’t want to set this number to something like 1 trillion, because your server probably won’t return in a timely manner.  As mentioned in bold above, if you set this iteration count to a high number and then later lower it, the PasswordHasher will NOT downgrade the password hash for you.  So you will need to do appropriate stress testing on what your environment can handle.

As always, there’s a trade-off between security and convenience.  You can’t have both.  You will likely need to find a happy medium between a strong iteration count and how long it takes a user to login.  I’m not a security expert, but Brock Allen (who is a security expert) said in 2014 that – “The general consensus is that it should take about one second to compute a password hash.”  In that same article, Brock gives a formula that says we should be using 512,000 iterations in 2018.  Using 512,000 iterations running on my laptop’s 8th gen i7 results in a login process that takes ~900ms on average.  Again – do what makes sense for your scenario and make sure you test appropriately.

 

Final thoughts

If you’re using something like Auth0, Okta, Azure B2C, any other cloud identity-as-a-service solution, or you’re exclusively using Social Logins, then you likely won’t need to worry about any of this.  They are choosing an iteration count/work factor/algorithm for you.  However, there are still many apps who store password hashes in their own proprietary databases, and I didn’t see a post on how to configure this, so that’s why I typed this up.

As mentioned a few times in this post, I am not a security expert and I’ve tried to stray away from giving specific advice in this post.  Do what makes sense for your application.

 

Hope this helps!

Clean Up Your bundleconfig.json By Embracing The Defaults

If you’re using the Bundler & Minifier package in ASP.NET Core (instead of something like WebpackGulp, or the new hotness Parcel) and your bundleconfig.json has these properties set on your JavaScript files:


"minify": {
"enabled": true,
"renameLocals": true
},
"sourceMap": false

You can remove those, because those are the default.  On a project I’m working on that uses it,  it shaved ~50 LOC on our bundleconfig.json which kept things less noisy.

Before:


[
{
"outputFileName": "wwwroot/js/site.min.js",
"inputFiles": [
"wwwroot/js/site.js"
],
"minify": {
"enabled": true,
"renameLocals": true
},
"sourceMap": false
}
]

After:


[
{
"outputFileName": "wwwroot/js/site.min.js",
"inputFiles": [
"wwwroot/js/site.js"
]
}
]

 

If you have those options listed, that shouldn’t come as much of a surprise.  The ASP.NET Core templates have these options explicitly listed by default, so I would expect most people who use the Bundler & Minifier to follow suit.

 

If you want to verify that the bundles are the same, it’s easy to test it out in Visual Studio.

  1. Go to the Task Runner Explorer in Visual Studio
  2. Go to bundleconfig.json
  3. Right-click on Update all files
  4. Click Run

Then check to see if your version control picks up any changes.  Spoiler: it won’t.  🙂

 

Hope this helps!