The default route in MVC {controller}/{action}/{id} is for the most part quite helpful as is being able to set a default if the incoming url doesn’t include a parameter but is there also a way to specify a default action for when an action doesn’t exist on a controller?
What I want to achieve is being able to have controllers with several specific actions and then its own catchall which uses the url to grab content from a basic CMS.
For example a products controller would be something like:
public class ProductsController: Controller{
public ActionResult ProductInfo(int id){...}
public ActionResult AddProduct(){...}
public ActionResult ContentFromCms(string url){...}
}
Where the default route would handle /Products/ProductInfo/54 etc but a request url of /Products/Suppliers/Acme would return ContentFromCms("Suppliers/Acme"); (sending the url as a parameter would be nicer but not needed and a parameterless method where I get it from Request would be fine).
Currently I can think of two possible ways to achieve this, either:
Create a new constraint which reflects over a controller to see if it does have an action of a given name and use this in the {controller}/{action}/{id} route thus allowing me to have a more general catchall like {controller}/{*url}.
Override HandleUnknownAction on the controller.
The first approach seems like it would be quite a roundabout way of checking this while for the second I don’t know the internals of MVC and Routing well enough to know how to proceed.
Update
There’s not been any replies but I thought I’d leave my solution incase anyone finds this in future or for people to suggest improvements/better ways
For the controllers I that wanted to have their own catchall I gave them an interface
interface IHasDefaultController
{
public string DefaultRouteName { get; }
System.Web.Mvc.ActionResult DefaultAction();
}
I then derived from the ControllerActionInvoker and overrode FindAction. This calls the base FindAction then, if the base returns null and the controller impliments the interface I call FindAction again with the default actionname.
protected override ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName)
{
ActionDescriptor foundAction = base.FindAction(controllerContext, controllerDescriptor, actionName);
if (foundAction == null && controllerDescriptor.ControllerType.GetInterface("Kingsweb.Controllers.IWikiController") != null)
{
foundAction = base.FindAction(controllerContext, controllerDescriptor, "WikiPage");
}
return foundAction;
}
As I also want parameters from the routing I also replace the RouteData at the start of the default Actionresult on the controller
ControllerContext.RouteData = Url.RouteCollection[DefaultRouteName].GetRouteData(HttpContext);
You approach is quite fine. As a side-note:
replace
with
this is more strongly-typed way then passing in the name of the interface via string: what if you change the namespace tomorrow?..