Skip to content

Instantly share code, notes, and snippets.

@paulczy
Last active April 26, 2018 15:32
Show Gist options
  • Save paulczy/4f13bc5ab7ac34eaa6a7da4d0ba16958 to your computer and use it in GitHub Desktop.
Save paulczy/4f13bc5ab7ac34eaa6a7da4d0ba16958 to your computer and use it in GitHub Desktop.
Add ETags to your ASP.NET Core responses.
using System;
using System.IO;
using System.Security.Cryptography;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Newtonsoft.Json;
using Newtonsoft.Json.Bson;
// ReSharper disable once CheckNamespace
public class ETagAttribute : ResultFilterAttribute
{
public override Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
var result = context.Result as OkObjectResult;
if (result == null) return base.OnResultExecutionAsync(context, next);
string token;
using (var ms = new MemoryStream())
{
using (var writer = new BsonWriter(ms))
{
var serializer = new JsonSerializer();
serializer.Serialize(writer, result);
token = GetToken(ms.ToArray());
}
}
var clientToken = context.HttpContext.Request.Headers["If-None-Match"];
if (token != clientToken)
{
context.HttpContext.Response.Headers["ETag"] = token;
}
else
{
context.Result = new StatusCodeResult(304);
}
return base.OnResultExecutionAsync(context, next);
}
private static string GetToken(byte[] bytes)
{
var checksum = MD5.Create().ComputeHash(bytes);
return Convert.ToBase64String(checksum, 0, checksum.Length);
}
}
@Kricke242
Copy link

Nice, thanks!

But perhaps you should dispose the hash-algo?

private static string GetToken(byte[] bytes) { using (var hashAlg = MD5.Create()) { var checksum = hashAlg.ComputeHash(bytes); return Convert.ToBase64String(checksum, 0, checksum.Length); } }

Another modification I did was to be able to use it with FileContentResult (could be better, but worked for me right now):

IActionResult result = context.Result as OkObjectResult; if (result == null) { result = context.Result as FileContentResult; if (result == null) return base.OnResultExecutionAsync(context, next); }

It would be nice if one could avoid hitting the database for file (using a saved hash from the database), but that would require more than this type of filter. This atleast saves network traffic.

Thanks again!

@definitelynotsoftware
Copy link

super helpful - thanks for making this!

Copy link

ghost commented May 14, 2017

This code totally wrong from point of view of it purpose...
IActionResult is not always OkObjectResult. There are a lot of results including own ones:

JsonResult
ContentResult
ObjectResult
OkObjectResult
NotFoundObjectResult
FileContentResult
FileStreamResult
VirtualFileResult
PhysicalFileResult

Only ObjectResult is related to OkObjectResult... All other will not be cached... For example if you want to serve static html templates with ViewResult or some own static file it will not work...

So maybe to avoid misunderstanding it should be named something like ETagObjectResultAttribute... because as universal etag attribute it is wrong.

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