In the documentation I see that the way to register a custom route is to create a class inheriting from Umbraco.Web.Mvc.UmbracoVirtualNodeRouteHandler and override its ApplicationStarted method.
There are three issues:
Where do I create this class? Anywhere?
I need to override the FindContent method of the base class - but I'm not dealing with content, just trying to register a route: content doesn't come into it.
The base class doesn't have an ApplicationStarted virtual method I can override.
It looks as if this is no longer the correct base class to use for registering routes.
Just FYI, i started down this path also and i'm not sure it's the correct way.
Seems you have to implement IComponent, then using composition (implement IUserComposer) add the component to the list of components. I created a folder Compositions and put the classes there.
The IComponent has an Initialize() method you can use to setup the UmbracoApplication_ApplicationInit. Within that event handler you can specify your custom route and specify your custom UmbracoVirtualNodeRouteHandler (it doesn't have an ApplicationStarted method to override btw).
In the route handler's FindContent i did use a node id for the umbracoContent.ContentCache.GetById() method. I just used a random node (home) but it did hit my custom controller and it did pass in my query parameters that i was expecting.
Correction, i used the node Id of a node that used a document type with the same name as the controller. Anything else and it doesn't hit the controller.
You need to create a component, and register the route in the Initialize() method using the new MapUmbracoRoute RouteCollection extension method, then use a composer to add the component to the Umbraco composition.
An Example:
Add a c# class file in your project as per below, this example would map the custom route /products/details/123 to a custom controller called SuperProductController (that would need to inherit from RenderMvcController). The IPublishedContent model associated with the route comes from the UmbracoVirtualNodeRouteHandler (you can create your own logic as to what this should be based upon your request, but here I'm just hardcoding it to be the Id, of the products node in the starter kit!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Umbraco.Core.Composing;
using Umbraco.Web;
using Umbraco.Web.Mvc;
namespace Umbraco8.Components
{
public class RegisterCustomRouteComposer : ComponentComposer<RegisterCustomRouteComponent>
{
}
public class RegisterCustomRouteComponent : IComponent
{
public void Initialize()
{
RouteTable.Routes.MapUmbracoRoute("Product Details", "product/details/{id}", new
{
controller = "SuperProduct",
action = "Details",
id = UrlParameter.Optional
}, new UmbracoVirtualNodeByIdRouteHandler(1105));
}
public void Terminate()
{
throw new NotImplementedException();
}
}
}
UmbracoVirtualNodeByIdRouteHandler is just 'one implementation' of UmbracoVirtualNodeRouteHandler that takes in a hardcoded Id, there is another that takes in a Udi...
to introduce your own logic for how an IPublishedContent item is associated with the incoming request, and then use your own class in the 'MapUmbracoRoute' registration.
I think the mechanism was conceived for the scenario where you have content outside of Umbraco for example a database table of Products, and you want to 'pretend' that these items 'vitually' exist in Umbraco, and to pull this off you need to associate an IPublishedContent item with the request so that Umbraco has context for executing through it's incoming pipeline.
So I'm not sure if it fits your preview purpose, but be interesting to see your results - but in short, yes, you can implement your own logic here instead of hardcoding an id.
I know the existing preview implementation sets a preview token, so that the existing implementation knows it's in preview - and therefore knows not to cache macros, and knows to show 'saved' but not published content...
Yes, this seems to work. I am able to use a custom controller for front end functionality, but what I about backoffice functions? What I am trying to do is to create/edit some complicated data structures (railway timetables, which I am storing in the database as JSON) in the back office. I haven't yet managed to get a controller derived from UmbracoAuthorizedController to be instantiated and executed.
Sorry I thought your question was regarding the changes in V8 for mapping a custom route through Umbraco using a VirtualNodeRoute handler...
Yes, creating an api controller for use in the backoffice is different to this, but I don't think it has changed from V7, you would create an API controller that inherits from UmbracoAuthorizedApiController, with a PluginController attribute, and then this would be automatically routed via /umbraco/backoffice/{pluginname}/{controllername}/{action} etc
For example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using Umbraco.Web.Mvc;
using Umbraco.Web.WebApi;
namespace Umbraco8.Controllers
{
///umbraco/backoffice/SuperTimeTables/TimeTableApi/GetTimeTableDetails
[PluginController("SuperTimeTables")]
public class TimeTableApiController : UmbracoAuthorizedApiController
{
[HttpGet]
public TimeTable GetTimeTableDetails()
{
return new TimeTable() { Name = "test", Times = "9:00am" };
}
[HttpPost]
public IHttpActionResult SaveTimetableInfo(TimeTable timetable)
{
if (timetable == null)
{
return BadRequest("Timetable is null etc");
}
// do something to persist timetable
return Ok();
}
}
public class TimeTable
{
public string Name { get; set; }
public string Times { get; set; }
}
}
in this example the autorouting would be set up to the following url:
It wasn't an API controller I had in mind. I was thinking in terms of a user interface allowing users to create and edit timetables.
A timetable is a complicated thing with a grid of cells, with a variable number of columns (the UI should allow the user to insert columns). Not only are there times in the cells, but there are references to notes, and there is a cell at the top of each column just for references to notes. Actually, two grid - one for each direction. And then there's the notes themselves!
Really struggling to see how to incorporate this sort of data into Umbraco! At the moment I've got it in tables in a separate database, but I've had to create the data programatically in a console app.
Yes I sort of worked out what you meant after posting the API example ... :-(
It's an unusual way to extend the backoffice functionality, general recommended way is AngularJS + APIControllers, but it is possible in V7! - I have a site that does just this, as long as controller inherits from UmbracoAuthorizedController, and you supply the route to work with it, then it's all ok.
However I tried to replicate in V8 to provide you a further example and hit an error! (probably the same as you are seeing) so I've raised an issue on the Umbraco Issue Tracker:
If you create your MVC Controller like so, inheriting from UmbracoAuthorizedController
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Umbraco.Web.Mvc;
namespace Umbraco8.Controllers
{
public class TimeTablesController : UmbracoAuthorizedController
{
// GET: TimeTables
public ActionResult Index()
{
return View();
}
}
}
Then create a component to map the route and a composer to register the component with the Umbraco composition, and additionally register your controller with the DI implementation. eg:
using System;
using System.Web.Mvc;
using System.Web.Routing;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco8.Controllers;
namespace Umbraco8.Components
{
public class RegisterTimetableBackofficeMvcRouteComposer : IUserComposer
{
public void Compose(Composition composition)
{
composition.Register<TimeTablesController>(Lifetime.Request);
composition.Components().Append<RegisterTimetableBackofficeMvcRouteComponent>();
}
}
public class RegisterTimetableBackofficeMvcRouteComponent : IComponent
{
public void Initialize()
{
RouteTable.Routes.MapRoute("TimetableRoute", "umbraco/backoffice/timetables/{action}/{id}", new
{
controller = "TimeTables",
action = "index",
id = UrlParameter.Optional
});
}
public void Terminate()
{
throw new NotImplementedException();
}
}
}
you can then request your backoffice MVC controller via /umbraco/backoffice/timetables/
I have copied your reply, creating a controller and a custom route config, however when i go to: /umbraco/backoffice/timetables/ i get:
Server Error in '/' Application.
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /umbraco/backoffice/timetables/
Do you know if I'm missing something?
I just want to add custom routing to controllers and actions.
Registering Custom Routes in Umbraco 8
In the documentation I see that the way to register a custom route is to create a class inheriting from Umbraco.Web.Mvc.UmbracoVirtualNodeRouteHandler and override its ApplicationStarted method.
There are three issues:
It looks as if this is no longer the correct base class to use for registering routes.
How do I go about registering a custom route?
Just FYI, i started down this path also and i'm not sure it's the correct way.
Seems you have to implement IComponent, then using composition (implement IUserComposer) add the component to the list of components. I created a folder Compositions and put the classes there.
The IComponent has an Initialize() method you can use to setup the UmbracoApplication_ApplicationInit. Within that event handler you can specify your custom route and specify your custom UmbracoVirtualNodeRouteHandler (it doesn't have an ApplicationStarted method to override btw).
In the route handler's FindContent i did use a node id for the umbracoContent.ContentCache.GetById() method. I just used a random node (home) but it did hit my custom controller and it did pass in my query parameters that i was expecting.
hth
Correction, i used the node Id of a node that used a document type with the same name as the controller. Anything else and it doesn't hit the controller.
Could you help with this question? https://stackoverflow.com/questions/56211510/how-to-make-custom-routes-in-umbraco8
Hi Nick
You need to create a component, and register the route in the Initialize() method using the new MapUmbracoRoute RouteCollection extension method, then use a composer to add the component to the Umbraco composition.
An Example:
Add a c# class file in your project as per below, this example would map the custom route /products/details/123 to a custom controller called SuperProductController (that would need to inherit from RenderMvcController). The IPublishedContent model associated with the route comes from the UmbracoVirtualNodeRouteHandler (you can create your own logic as to what this should be based upon your request, but here I'm just hardcoding it to be the Id, of the products node in the starter kit!
Anyway hope that make sense!
regards
Marc
Hi Marc,
Thank you for this code sample.
Above, you hard code the ID of the real node to
1105
. Is there a way to make this dynamic, so it uses the same value as the optional URL parameterid
?I'm basically building (my own version) of the preview link in the backoffice and having a hard time conceptualizing the best way to go about it.
Thanks,
Hi Mark
UmbracoVirtualNodeByIdRouteHandler
is just 'one implementation' of UmbracoVirtualNodeRouteHandler that takes in a hardcoded Id, there is another that takes in a Udi...https://github.com/umbraco/Umbraco-CMS/blob/3bfd9b71e290354744d677440983ecd06c5c0788/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs
you can create a new implementation by inheriting from UmbracoVirtualNodeRouteHandler, and implement:
protected abstract IPublishedContent FindContent(RequestContext requestContext, UmbracoContext umbracoContext);
to introduce your own logic for how an IPublishedContent item is associated with the incoming request, and then use your own class in the 'MapUmbracoRoute' registration.
I think the mechanism was conceived for the scenario where you have content outside of Umbraco for example a database table of Products, and you want to 'pretend' that these items 'vitually' exist in Umbraco, and to pull this off you need to associate an IPublishedContent item with the request so that Umbraco has context for executing through it's incoming pipeline.
So I'm not sure if it fits your preview purpose, but be interesting to see your results - but in short, yes, you can implement your own logic here instead of hardcoding an id.
I know the existing preview implementation sets a preview token, so that the existing implementation knows it's in preview - and therefore knows not to cache macros, and knows to show 'saved' but not published content...
https://github.com/umbraco/Umbraco-CMS/blob/8b7418164d112e7247e901f18c486ff37e1fa611/src/Umbraco.Web/UmbracoContext.cs#L280
if that helps!
regards
Marc
Thank you Marc, your explanation does help! Much appreciated mate.
Yes, this seems to work. I am able to use a custom controller for front end functionality, but what I about backoffice functions? What I am trying to do is to create/edit some complicated data structures (railway timetables, which I am storing in the database as JSON) in the back office. I haven't yet managed to get a controller derived from UmbracoAuthorizedController to be instantiated and executed.
Hi Nick
Sorry I thought your question was regarding the changes in V8 for mapping a custom route through Umbraco using a VirtualNodeRoute handler...
Yes, creating an api controller for use in the backoffice is different to this, but I don't think it has changed from V7, you would create an API controller that inherits from UmbracoAuthorizedApiController, with a PluginController attribute, and then this would be automatically routed via /umbraco/backoffice/{pluginname}/{controllername}/{action} etc
For example:
in this example the autorouting would be set up to the following url:
/umbraco/backoffice/SuperTimeTables/TimeTableApi/GetTimeTableDetails
regards
Marc
It wasn't an API controller I had in mind. I was thinking in terms of a user interface allowing users to create and edit timetables.
A timetable is a complicated thing with a grid of cells, with a variable number of columns (the UI should allow the user to insert columns). Not only are there times in the cells, but there are references to notes, and there is a cell at the top of each column just for references to notes. Actually, two grid - one for each direction. And then there's the notes themselves!
Really struggling to see how to incorporate this sort of data into Umbraco! At the moment I've got it in tables in a separate database, but I've had to create the data programatically in a console app.
What's the usual pattern for this sort of thing?
Hi Nick
Yes I sort of worked out what you meant after posting the API example ... :-(
It's an unusual way to extend the backoffice functionality, general recommended way is AngularJS + APIControllers, but it is possible in V7! - I have a site that does just this, as long as controller inherits from UmbracoAuthorizedController, and you supply the route to work with it, then it's all ok.
However I tried to replicate in V8 to provide you a further example and hit an error! (probably the same as you are seeing) so I've raised an issue on the Umbraco Issue Tracker:
https://github.com/umbraco/Umbraco-CMS/issues/5005
So hopefully we'll get an answer there, as it's one on my list to update the docs for - and of course you are trying to actually make it work!
If you've got any further info to add, be ace if you could stick it on the issue.
regards
Marc
Hi Nick
Update on this.
If you create your MVC Controller like so, inheriting from UmbracoAuthorizedController
Then create a component to map the route and a composer to register the component with the Umbraco composition, and additionally register your controller with the DI implementation. eg:
you can then request your backoffice MVC controller via /umbraco/backoffice/timetables/
Apologies it took a while to work out!
regards
Marc
Hi Marc,
I have copied your reply, creating a controller and a custom route config, however when i go to: /umbraco/backoffice/timetables/ i get:
Server Error in '/' Application. The resource cannot be found. Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /umbraco/backoffice/timetables/
Do you know if I'm missing something?
I just want to add custom routing to controllers and actions.
Many thanks, Sam
is working on a reply...