CSS Only Dropdown Menu
As someone who basically gets by when it comes to writing Javascript, I tend to look at things from a “I bet I could do this with CSS” st...
CSS SCSS Mobile UX UI/UX frontendIdeally, we would never have to take our site offline to do maintenance. All of our deployments would happen smoothly, and we could transition seamlessly into our new set of features. Once in a while it just isn’t possible, and we have to stop our users from interacting with our application. With IIS, you can drop an app_offline.htm
file in the root of your site, but this requires the deployment of a file to your production environment. It works, but it isn’t great.
Maintenance mode doesn’t just affect your users, but also may impact potential users via SEO crawlers. According to this post from Yoast.com, crawlers expect to see an HTTP status code of 503
.
you have to send a 503 status code in combination with a Retry-After header. Basically you’re saying: hang on, we’re doing some maintenance, please come back in X minutes. That sounds a lot better than what a 404 error says: “Not Found”. A 404 means that the server can’t find anything to return for the URL that was given.
Given that information and the necessary maintenance of a site, we decided to write middleware for support instances.
public class MaintenanceMiddleware
{
private readonly RequestDelegate next;
private readonly ILogger logger;
private readonly MaintenanceWindow window;
public MaintenanceMiddleware(RequestDelegate next, MaintenanceWindow window, ILogger<MaintenanceMiddleware> logger)
{
this.next = next;
this.logger = logger;
this.window = window;
}
public async Task Invoke(HttpContext context)
{
if (window.Enabled)
{
// set the code to 503 for SEO reasons
context.Response.StatusCode = (int)HttpStatusCode.ServiceUnavailable;
context.Response.Headers.Add("Retry-After", window.RetryAfterInSeconds.ToString());
context.Response.ContentType = window.ContentType;
await context
.Response
.WriteAsync(Encoding.UTF8.GetString(window.Response), Encoding.UTF8);
}
await next.Invoke(context);
}
}
public class MaintenanceWindow
{
private Func<bool> enabledFunc;
private byte[] response;
public MaintenanceWindow(Func<bool> enabledFunc, byte[] response)
{
this.enabledFunc = enabledFunc;
this.response = response;
}
public bool Enabled => enabledFunc();
public byte[] Response => response;
public int RetryAfterInSeconds { get; set; } = 3600;
public string ContentType { get; set; } = "text/html";
}
public static class MaintenanceWindowExtensions
{
public static IServiceCollection AddMaintenance(this IServiceCollection services, MaintenanceWindow window)
{
services.AddSingleton(window);
return services;
}
public static IServiceCollection AddMaintenance(this IServiceCollection services, Func<bool> enabler, byte[] response, string contentType = "text/html", int retryAfterInSeconds = 3600)
{
AddMaintenance(services, new MaintenanceWindow(enabler, response)
{
ContentType = contentType,
RetryAfterInSeconds = retryAfterInSeconds
});
return services;
}
public static IApplicationBuilder UseMaintenance(this IApplicationBuilder builder)
{
return builder.UseMiddleware<MaintenanceMiddleware>();
}
}
Using the middleware is straight forward. First register the MaintenanceWindow
class.
services.AddMaintenance(() => true,
Encoding.UTF8.GetBytes("<div>Doing Maintenance Yo!</div>"));
Then register the MaintenanceMiddleware
.
app.UseMaintenance();
Things to take into account when using this middleware:
StaticFileMiddleware
.HTML
is the default response, but this could also work for an API that returns JSON
.UTF8
, but the code can be modified to support any other encoding type.Retry-After
period is. By default, we chose 3600
seconds.MaintenanceWindow
class needs to be registered with the IoC in ASP.NET Core.There you have it, may all your maintenance tasks go smoothly and may your SEO be unaffected.