Handle Unknown Actions in ASP.NET Core MVC

While working with ASP.NET Core MVC, usually you have known number of actions
and views. At times you might come across a situation where you need to deal
with actions unknown at development time. Let me briefly explain a situation I
recently stumbled upon.
There was an ASP.NET Core web application centered around electronic
products. There was ProductsController that had Index() action as shown below:
public IActionResult Index()
{
// some processing
return View(model);
}
This is quite simple and straightforward. Now, each product has associated
information pages such as a help page, technical specifications page, offers
page, store addresses page and so on. What's more - the number of information
pages for a product were unknown at the time of creating the controller. Somebody would create these
pages / views at a later stage and they may get added or removed at any time depending
on the company requirement.
Consider the following URLs :
/Home/Index
/Home/Help
/Home/Specifications
/Home/Stores
/Home/Offers
From the URLs it appears that the HomeController will have five actions
namely Index, Help, Specifications, Stores, and Offers. In reality Index() is
the only action whereas others are mere information pages to be rendered in the
browser. What if an information page gets added or removed? Obviously, you need
to add or remove an action to take care of the information page under
consideration.
Luckily, ASP.NET Core routing allows you to handle the situation easily.
Let's see how.
Begin by creating a new ASP.NET Core MVC application and add HomeController
as you normally do. Also add the Index() action as shown above. You will also
need three .cshtml files - Index.cshtml, Help.cshtml, Specifications.cshtml -
for testing purpose.
Now, open Startup.cs and go to Configure() method. Write the following code
to define the routing:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute(
name: "infoPage",
pattern: "{controller}/{*infoPage}",
defaults: new { action = "ShowInfoPage" });
});
Notice the second route definition. It contains the catch all *infoPage route
parameter. This way anything other than Index will be handled by ShowInfoPage()
action. The ShowInfoPage() looks like this:
public IActionResult ShowInfoPage(string infoPage)
{
return View(infoPage);
}
As you can see the ShowInfoPage() accepts the infoPage route parameter. In
our example this parameter will be information page name such as Help,
Specifications, and Stores. Inside, we simply return a view file that matches
the infoPage name. Alternatively, you can use inforPage parameter to dynamically
decide which page is to be displayed.
With the above routing configuration in place, the first URL (/Home/Index)
will be handled by the Index() action whereas all the other URLs will reach
ShowInfoPage() action. The ShowInfoPage() will return the specified page to the
browser. Of course, if a page doesn't exist an error will be raised.
The following figure shows a set of such "pages" residing in the Views > Home
folder.

The same requirement can be accomplished using attribute routing as follows:
[Route("[controller]")]
public class HomeController : Controller
{
[Route("[action]")]
public IActionResult Index()
{
return View();
}
[HttpGet("{infoPage}")]
public IActionResult ShowInfoPage(string infoPage)
{
return View(infoPage);
}
}
In this case the first URL will be mapped to the Index() action. All the
other GET URLs are mapped to ShowInfoPage() action. The route parameter infoPage
specifies the page name (Help, Specifications etc.).
Also modify the UseEndPoints() call to resemble this:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
The MapControllers() ensures that attribute based routing is taken into
account while building the routes.
In the above examples, you used the technique to deal with dynamically
varying information pages. However, you can use the technique to deal with any
action that is unknown at development time.
That's it for now! Keep coding!!