When I start to develop a new ASP.NET Core application, one of the first things I do is to create a "Work in Progress" view (or razor page): the purpose of this view/page is to let visitors know that the website isn't ready yet - and that we are working on it.
This page has just some minimal HTML content and typically looks like this:
Now, if you are aware of how the ASP.NET Core routing system work, you know that such a page - just like any other page, view, or other requested content - will have its own routing address, which will also determine its public endpoint. For example, if we create the page using the /Pages/WorkInProgress/Index.cshtml path (or /Pages/WorkInProgress.cshtml, if you prefer) the (default) routing address will be /WorkInProgress, and its corresponding (default) public endpoint will be https://ourwebsite.com/WorkInProgress.
Assuming we want our visitors to see that page when they publicly access our website, we might think of putting that page in the /Pages/Index.cshtml path instead, so that they will see it when they navigate to our website's root (https://ourwebsite.com/): however, since we don't want to have that page as our website's root when we are actively developing it, it would be better to implement a conditional redirect within that page's PageModel in the following way:
1 2 3 4 5 6 7 |
public IActionResult OnGet() { if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == Environments.Production) return RedirectToPage("./WorkInProgress"); return Page(); } |
Or, if we're using the MVC pattern (assuming we implemented our WIP page as a view and we used the /Views/WorkInProgress/Index.cshtml path), in the following way:
1 2 3 4 5 6 7 |
public IActionResult Index() { if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == Environments.Production) return RedirectToAction("Index", "WorkInProgress"); return View(); } |
This will ensure that the redirect will only work in a production environment, assuming this is the environment we used to deploy our publicly-available version of our web application.
However, what if our visitors - instead of visiting the website's root - visit any other endpoint? For example, they might try to navigate to https://ourwebsite.com/Contacts, https://ourwebsite.com/FAQ, and so on: we didn't put any redirect to shield those endpoints (yet), meaning that they will be fully accessible to any request - assuming that those pages are meant to be publicly available, and not protected by some auth-based mechanisms.
Is there a way to conditionally redirect ALL those requests to our "Work in Progress" page (or view) in a centralized way?
As a matter of fact, there are multiple ways to do that: let's briefly see the two alternatives I like the most.
Method #1: using a Middleware
If we are aware of how the ASP.NET Core request pipeline works, we know that it basically consists of a sequence of delegates, called one after the other, in the following way:
Each delegate can perform operations before and after the next delegate. This basically means that, if we set up our own request delegate that can (conditionally) handle all requests, we can (conditionally) override the default routing system and re-route the requests wherever we want: which is precisely what we want to do!
To implement such a delegate, we can put the following anonymous function within the Program.cs file:
1 2 3 4 5 6 7 8 9 10 11 12 |
// WIP Page Middleware - remove when website is ready app.Use(async (context, next) => { var wipPath = "/WorkInProgress"; var isProd = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == Environments.Production; if (isProd && (string)context.Request.Path != wipPath) { context.Response.Redirect(wipPath); return; } await next(); }); |
That's it: thanks to that "custom middleware", any request which is different from /WorkInProgress will now be redirected to the /WorkInProgress endpoint - as long as the website is running in a Production environment. Needless to say, this will work even for the website's root endpoint, meaning that we can avoid meddling with any manual redirect such as the one we introduced early on.
Method #2: using a BasePageModel or BaseController class
If you don't like custom middlewares and anonymous functions in your Program.cs file, you might want to choose an alternative method based on base classes. In short, you'll have to override the OnPageHandlerExecutionAsync (for razor pages) or the OnActionExecutionAsync (for controllers) method and put the same logic there.
Here's an example using the OnActionExecutionAsync method:
1 2 3 4 5 6 7 8 9 |
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { var wipPath = "/WorkInProgress"; var isProd = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == Environments.Production; if (isProd && (string)context.Request.Path != wipPath) return RedirectToAction("Index", "WorkInProgress); await next(); } |
For Razor Pages it's basically the same, you just have to use the RedirectToPage() method instead. However, while the previous method will immediately work for any endpoint, this alternative approach will require us to extend all of our Controllers (or PageModels) using that base class: this can be inconvenient, and might even pose some security risks: if we forget to do that, we could easily end up in a brand-new page (or controller) being immediately accessible to our visitors. For this very reason, I strongly suggest using the first method.
Conclusion
That's it, at least for now: I hope that this post will help other ASP.NET Core developers to conditionally redirect all the HTTP requests to a single URL endpoint.