Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Nick Bennett 14 posts 84 karma points
    Mar 11, 2019 @ 12:36
    Nick Bennett
    0

    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:

    1. Where do I create this class? Anywhere?
    2. 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.
    3. 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.

    How do I go about registering a custom route?

  • seanrock 240 posts 461 karma points
    Mar 17, 2019 @ 13:41
    seanrock
    0

    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

  • seanrock 240 posts 461 karma points
    Mar 17, 2019 @ 14:57
    seanrock
    0

    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.

  • Antony Meyn 2 posts 71 karma points hq
    May 19, 2019 @ 21:22
  • Marc Goodson 2141 posts 14344 karma points MVP 8x c-trib
    Mar 17, 2019 @ 20:31
    Marc Goodson
    1

    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!

    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();
                    }
                }
            }
    

    Anyway hope that make sense!

    regards

    Marc

  • Mark Drake 133 posts 457 karma points c-trib
    Apr 19, 2020 @ 05:03
    Mark Drake
    0

    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 parameter id?

    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,

  • Marc Goodson 2141 posts 14344 karma points MVP 8x c-trib
    Apr 19, 2020 @ 08:43
    Marc Goodson
    0

    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

  • Mark Drake 133 posts 457 karma points c-trib
    Apr 19, 2020 @ 13:47
    Mark Drake
    0

    Thank you Marc, your explanation does help! Much appreciated mate.

  • Nick Bennett 14 posts 84 karma points
    Mar 17, 2019 @ 20:50
    Nick Bennett
    0

    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.

  • Marc Goodson 2141 posts 14344 karma points MVP 8x c-trib
    Mar 17, 2019 @ 22:38
    Marc Goodson
    0

    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:

    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:

    /umbraco/backoffice/SuperTimeTables/TimeTableApi/GetTimeTableDetails

    regards

    Marc

  • Nick Bennett 14 posts 84 karma points
    Mar 21, 2019 @ 14:49
    Nick Bennett
    0

    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?

  • Marc Goodson 2141 posts 14344 karma points MVP 8x c-trib
    Mar 21, 2019 @ 16:08
    Marc Goodson
    0

    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

  • Marc Goodson 2141 posts 14344 karma points MVP 8x c-trib
    Mar 26, 2019 @ 20:32
    Marc Goodson
    0

    Hi Nick

    Update on this.

    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/

    Apologies it took a while to work out!

    regards

    Marc

  • Sam 22 posts 114 karma points
    Feb 20, 2020 @ 12:32
    Sam
    0

    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

Please Sign in or register to post replies

Write your reply to:

Draft