DNN 9.2 introduces many new features including new routing controls for MVC Modules. Now, when building a MVC Module you can easily Redirect routes between Controllers and Actions at the Controller level. This new feature introduces flexibility that adds feature parity with Microsoft’s MVC implementation. With this change a MVC Module can contain many controllers and actions per controller that handle the complex routing scenarios associated with MVC development. Prior to 9.2 developers were limited to having one controller. While there were workarounds to this limitation until now there wasn’t an elegant way to handle routing in a MVC Module.
What Changed?
The DnnController
is the heart of the DNN MVC Module pattern with several familiar methods and properties overidden for DNN specific purposes. Yet, this was incomplete because there was no DnnUrlHelper
on the DnnController
.
Pull Request 1925 - DnnUrlHelper
Adding the DnnUrlHelper
to the DnnController
allows us to easily route between Actions and different controllers just like you would in Microsoft’s MVC implementation
Pull Request 1913 - ActionFilter
The DNN MVC Action Pipeline was updated to handle redirects in the context of an ActionFilter
. This gives developers power to handling routing scenarios from an ActionFilter
.
Action Routing
Let’s take a look at a simple routing problem. Suppose you have built a custom module that has a form where you expect user input. When the user submits the form you want to process that data and redirect to a confirmation screen to let the user know their form submission was successful.
We will have a DnnController
with the following actions:
Method | Action |
---|---|
GET | Index |
POST | Index |
GET | Confirmation |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class HomeController : DnnController
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(FormInput data)
{
return Redirect(Url.Action("Confirmation", "Home"));
}
public ActionResult Confirmation()
{
return View("Confirmation");
}
}
The glue to this code is in our HttpPost
where we redirect to the Confirmation Action
Redirect(Url.Action("Confirmation", "Home"))
Breaking down this statement there are 2 big pieces
Redirect
Url.Action
Redirect
is an ASP.NET method that forces a redirect to a specific URL provided as the parameter
Url.Action
is using the DnnUrlHelper
to generate the DNN appropriate URL to pass into the Redirect method
Controller Routing
Now that we understand a simple routing scenario let’s expand our problem to solve a more complex scenario. Building on our original problem of a simple form that helps us collect user data. Suppose the form is collecting public data that anyone can view and after the user submits their form they can view a list of submissions by other users.
We are now describing a much more complicated module let’s describe each page in a little more detail
Page | Description |
---|---|
Form Input | A simple form that takes user input and submits to the controller. |
Confirmation | A confirmation screen that tells the user their data has been submitted. This screen has a button to navigate to the result list to see all submissions in the system. |
Result List | A list of all form submissions organized by date. When the user clicks on an item it navigates to the Input Details screen. |
Input Details | Details of a previously submitted form item. |
Let’s describe our different Controllers and Actions
Controller | Action | Method |
---|---|---|
HomeController | GET | Index |
HomeController | POST | Index |
HomeController | GET | Confirmation |
ListController | GET | Index |
ListController | GET | Details |
We have 2 Controllers and multiple Actions that have different interactions. Now that we have defined what we are trying to build, let’s create our controllers
HomeController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class HomeController : DnnController
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(FormInput data)
{
return Redirect(Url.Action("Confirmation", "Home"));
}
public ActionResult Confirmation()
{
return View("Confirmation");
}
}
The HomeController is IDENTICAL to what we did earlier, nothing changes. The change that you will need to implement is routing an anchor on your view. So you should add the following code somewhere on you Confirmation.cshtml
1
<a href="@Url.Action("Index", "List")">View All Results</a>
1
2
3
4
5
6
7
8
9
10
11
12
13
public class ListController : DnnController
{
public ActionResult Index()
{
return View();
}
public ActionResult Details(int inputId)
{
// retrieve your input here and pass your model into the view
return View("Details");
}
}
To breakdown what we are doing here:
Utilizing the DnnUrlHelper
to build an apprioprate DNN url and using the Controller
method Redirect
to navigate to the new route. In our case here we are utilizing the same logic (ex: Url.Action("Index", "Home")
) in both the Controller and the View where applicable.
Filter Routing
At this point you should have a MVC Module that has mutliple controllers and handles routing at both the controller level and the view level where applicable. This is a fantastic improvement over how routing used to be handled in MVC Modules. Let’s say our module throws an exception while it is running. It would be really useful to create an ActionFilter
that intercepts the exception and processes a redirect. This will allow us to display a user friendly screen to the end-user of the module.
Let’s create an ActionFilter
for exception handling, which is exactly how you would handle this scenario in ASP.NET MVC
1
2
3
4
5
6
7
8
9
public class RedirectOnExceptionAttribute : ActionFilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
var controller = (HomeController)filterContext.Controller
filterContext.Result = controller.RedirectToError();
filterContext.ExceptionHandled = true;
}
}
Looking at the code above we need to define a special controller method to handle the redirect, let’s define that as well
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class HomeController : DnnController
{
// omitted other methods
public ActionResult Error()
{
return View();
}
public ActionResult RedirectToError()
{
return Redirect(Url.Action("Error", "Home"));
}
}
There are a couple big pieces to the ActionFilter
Scenario, let’s break them down
- The
ActionFilter
to catch the exception - Setting the
filterContext.Result
which is of type ActionResult - Configuring your
DnnController
to have a method and/or Action available to handle the error redirect
Your ActionFilter
code should be almost identical to how you would solve this same problem in an ASP.NET MVC website. The trick is having your HomeController
in our example configured to return an ActionResult
that can be processed.
Note that the HomeController
now has 2 Actions
- Error
- RedirectToError
The ActionResult
that is generated from these methods is piped into the filterContext
and when the ActionFilter
finishes processing the page will be updated.
My personal preference is setting up these 2 Actions. This allows us to specify a Redirect which will update the url in the browser and completely move the user off the page that errored out. You technically don’t have to use the 2 actions but I consider it a best practice for handling the routes.