Copied to clipboard

Flag this post as spam?

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


  • Jacob Buus 18 posts 38 karma points
    Nov 25, 2014 @ 20:53
    Jacob Buus
    0

    Umbraco 7 setup with custom route values

    Hi guys, 

    I'm about to start a re-write of a rather large project. I'd really like to create it in Umbraco 7 - but I have some concerns about how to create a setup that will let me handle some of the data. 

    The site is simple enough. It's main focus is to display products - placed inside categories and sub-categories - as well as the different prices for these. Not a big problem. 

    However - I need the product-data (products, categories, prices etc.) to reside in a separate database and not as Umbraco nodes. I can use Surface Controllers to display some of these data. But I have a category-structure like so: 

    - ParentCategory
    - SubCategoryA
    - SubCategoryB
    - SubCategoryC
    - SubCategoryD

    - SubCategoryE
    - SubCategoryF
    - SubCategoryG
    - SubCategoryH

    So I have a lot of ParentCategories with a potential endless tree of SubCategories and products (although in reality I don't think I'll get over 3 levels of sub-categories). 

    I need to be able to create a meaningfull route for these - e.g.: 

    www.mysite.com/Categories/ParentCategoryA/SubCategoryF/SubCategoryG/Product1234/[ProductName]

    I can easily create what I want in plain MVC - but I would really like to use Umbraco for the site. 

    I've looked a bit into Route Hi-jacking - but I'm not sure if this will be the right approach? 

    The pages that display the data from my product-data-DB should basically call a MVC controller allowing me to specify a model containing my product-data and then return this to a view. However - I need to access the route values in order to retrieve the data. 

    Can I accomplish this using Umbraco 7 somehow?

    I basically need an Umbraco site with content nodes etc. but certain pages (product-details, category-view etc.) should retrieve data from custom server side code (MVC controllers) - using the route-values and returning an Umbraco view with a custom model. 

    I hope my description makes sence. If not, I'll be happy to explain it in more detail. :)

  • Nicholas Westby 2054 posts 7100 karma points c-trib
    Nov 26, 2014 @ 01:42
    Nicholas Westby
    1

    I suspect an IContentFinder is what you are looking for: http://our.umbraco.org/documentation/Reference/Request-Pipeline/IContentFinder

    You can assign a node based on a request. So, you could inspect the request URL and assign a node if it matches a category/subcategory/product.

    You can then create a RenderMvcController to return a particular view with a certain model (assuming your view is strongly typed).

    I haven't tried that myself, but seems like it would work.

  • Shannon Deminick 1524 posts 5269 karma points MVP 2x
    Nov 26, 2014 @ 09:04
    Shannon Deminick
    1

    You can have a look here on custom routes with Umbraco data : http://shazwazza.com/post/custom-mvc-routes-within-the-umbraco-pipeline/

    This routing stuff is all in the core now. This idea is used throughout the articulate blog engine https://github.com/Shazwazza/Articulate so you can find some examples there.

    This is basically using regular mvc but using Umbraco data and still operating within the Umbraco pipeline.

  • Jacob Buus 18 posts 38 karma points
    Nov 26, 2014 @ 19:52
    Jacob Buus
    0

    Thanks you guys - I'll have a look at it right away - and I might just get back to you if it doesn't make any sense to me :) 

  • Jacob Buus 18 posts 38 karma points
    Nov 26, 2014 @ 21:11
    Jacob Buus
    0

    Okay - so I had a look at Shannon's blog (great stuff by the way!) and created a small proof-of-concept. 

    I started out by registering my custom routes

    public class StartupHandler : IApplicationEventHandler
    {
    public void OnApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
    {
    //custom route
    RouteTable.Routes.MapUmbracoRoute(
    "test",
    "Products/{action}/{sku}",
    new
    {
    controller = "MyProduct",
    sku = UrlParameter.Optional
    },
    new ProductsRouteHandler(1056));
    }

    This picks up requests to my custom route and allows me to pass it to an UmbracoVirtualNodeByIdRouteHandler - which basically just returns the node with the ID I've hardcoded (1056) - BTW - does any of you have an idea about a better approach than a hardcoded ID for a multilingual setup? Maybe some kind of unique navigation name on the node? 

    My handler: 

    protected override IPublishedContent FindContent(RequestContext requestContext, UmbracoContext umbracoContext, IPublishedContent baseContent)
    {
              return base.FindContent(requestContext, umbracoContext, baseContent);
    }

    This will basically just take the current IPublishedContent (nodeID 1056 in Umbraco) and pass it to the MyProductController

    public class MyProductController : RenderMvcController
    {
    public ActionResult Details(RenderModel model, string sku)
    {
    if (!String.IsNullOrEmpty(sku))
    {
    // Render the product details page

    // TODO: RETRIEVE PRODUCT DATA FROM DATABASE

    var newModel = new ProductViewModel(model.Content)
    {
    SKU = sku,
    Name = "My product",
    Price = 129.99m
    };

    return View("Product", newModel);
    }

    return null;
    }
    }

    Where I define a ProductViewModel - which inherits from RenderModel: 

    public class ProductViewModel : RenderModel
    {
    public ProductViewModel(IPublishedContent content, CultureInfo culture) : base(content, culture)
    {}

    public ProductViewModel(IPublishedContent content) : base(content)
    {}

    public string SKU { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    }

    Which I can then return to my View - in this case "Product". BTW - any ideas as to how I can fetch the template dynamically from the node? I've tried CurrentTemplate() but it's just empty. 

    Anyway - my View then simply inherits from UmbracoViewPage<ProductViewModel> - and Bob's your uncle: 

    @inherits UmbracoViewPage<Presentation.Models.ProductViewModel>
    @{
    Layout = null;
    }

    <h1>
    @Model.Name
    </h1>

    <h2 style="color: green;">
    @Model.SKU
    </h2>

    <h4>
    @Model.Price.ToString("F2"),-
    </h4>

     

    I've now fetched data through custom code using a custom route - AND still have access to all of the Umbraco stuff of the current node - great stuff! :) 

    Can any of you guys see a problem with my approach? Something that might hit me further down the road? :) 

     

  • Matt Speakman 11 posts 44 karma points
    Aug 13, 2015 @ 12:56
    Matt Speakman
    1

    Where have you created your ProductsRouteHandler?

  • Nicholas Westby 2054 posts 7100 karma points c-trib
    Nov 26, 2014 @ 21:51
    Nicholas Westby
    0

    BTW - does any of you have an idea about a better approach than a hardcoded ID for a multilingual setup? Maybe some kind of unique navigation name on the node?

    What I typically do for things like that is find the home node by looking at the root ancestor of the current page, then I look at children nodes and their descendants by their document type alias: https://github.com/rhythmagency/rhythm.umbraco.extensions/blob/master/trunk/Rhythm.Extensions/Rhythm.Extensions/ExtensionMethods/PublishedContentExtensionMethods.cs#L579

    You may also want to create a route constraint so that only valid paths match the route: http://www.c-sharpcorner.com/UploadFile/ff2f08/custom-route-constraints-in-Asp-Net-mvc-5/

    I imagine that route constraint would check your database (or, an in-memory cache that mirrors your database) for valid categories/subcategories/products.

    Can any of you guys see a problem with my approach?

    Nope, looks good. Though, later on, you might find some other changes you'll need to make, such as creating a site map based both on the content tree and the products in your database.

  • Shannon Deminick 1524 posts 5269 karma points MVP 2x
    Nov 27, 2014 @ 02:07
    Shannon Deminick
    0

    You don't have to hard code the Ids, you don't even need to use UmbracoVirtualNodeByIdRouteHandler which is just a simple implementation of UmbracoVirtualNodeRouteHandler. The main purpose of UmbracoVirtualNodeRouteHandler is to implement your own in order to 'find' any content node that you want to associate with the specific route.

    You obviously can use the UmbracoVirtualNodeByIdRouteHandler and when creating your routes you can look up the content for the particular IDs you want to use. That is what is happening here:

    https://github.com/Shazwazza/Articulate/blob/master/Articulate/ArticulateRoutes.cs#L22

    The routes in articulate are complex because it is supporting multi-tenancy (multiple different root nodes with potential host names assigned). It's also worth nothing that articulate uses it's own virtual route handlers - because this code pre-dates the code in the Umbraco core (in fact this is where that code came from).

    Another thing to point out is that if your content changes, you might need to re-generate your routes since things might get deleted, etc... I handle that scenario in this class: https://github.com/Shazwazza/Articulate/blob/master/Articulate/UmbracoEventHandler.cs#L84

  • Jacob Buus 18 posts 38 karma points
    Nov 27, 2014 @ 07:16
    Jacob Buus
    0

    Great - I'll have a look at it.

    Thank you so much you guys - both of you have been a major help. I'll try expanding my current setup based on these ideas and see if I'll end up with a working solution that will suit my needs. As long as I can execute my own custom controller logic - while still in the Umbraco pipe - I should be good to go :)

  • Shannon Deminick 1524 posts 5269 karma points MVP 2x
    Nov 27, 2014 @ 07:17
    Shannon Deminick
    0

    Yup, you should be able to to what you like in that regard :)

  • Shannon Deminick 1524 posts 5269 karma points MVP 2x
    Nov 27, 2014 @ 07:17
    Shannon Deminick
    0

    It's worth being aware of these issues that have been fixed in 7.2

    http://issues.umbraco.org/issue/U4-5710 http://issues.umbraco.org/issue/U4-5574

  • Jacob Buus 18 posts 38 karma points
    Nov 27, 2014 @ 07:20
    Jacob Buus
    0

    Ah okay - so untill version 7.2 I'll just use regular forms with my custom code :) Thanks again Shannon.

  • Tim Brooks 27 posts 79 karma points
    Dec 05, 2014 @ 22:20
    Tim Brooks
    0

    I'm trying to implement the same thing as from Shannon's post that Nicholas was successful doing last week. I'm on version 7.1.8 and for the life of me I can't find ProductsRouteHandler. Where is this located? Do I need to reference articulate? I thought this was moved into the core. I must have overlooked something. Here are the references I'm using. All other code is nearly identical to what is posted above.

    using Umbraco.Core; using Umbraco.Web.Mvc; using Umbraco.Web;

  • Jacob Buus 18 posts 38 karma points
    Dec 05, 2014 @ 22:48
    Jacob Buus
    0

    The ProductRouteHandler isn't implemented per default in Umbraco - it's a class I've created to handle the route request :)

    So whenever my custom route is hit, the request is routed through my ProductRouteHandler:

    public class ProductsRouteHandler : UmbracoVirtualNodeByIdRouteHandler
        {
    
            public ProductsRouteHandler(int realNodeId) : base(realNodeId)
            {
            }
    
            protected override IPublishedContent FindContent(RequestContext requestContext, UmbracoContext umbracoContext, IPublishedContent baseContent)
            {
                return base.FindContent(requestContext, umbracoContext, baseContent);
            }
        }
    

    The handler is an implementation of the UmbracoVirtualNodeRouteHandler - which will allow you to retrieve a node from the Umbraco context. In my example I'm just using a hardcoded node ID - so my handler inherits from UmbracoVirtualNodeByIdRouteHandler.

    The main idea is that you use the abstract method FindContent to retrieve which ever IPublishedContent you want to associate with the route.

    Once this is done - your custom Controller (in my case the "MyProductController" should pick up your request :)

  • Tim Brooks 27 posts 79 karma points
    Dec 06, 2014 @ 00:27
    Tim Brooks
    0

    That was it! it works like a charm. Thank you Jacob.

  • Jacob Buus 18 posts 38 karma points
    Dec 06, 2014 @ 00:29
    Jacob Buus
    0

    You got it - can't take all the credit though. Shannon and Nicholas did all the heavy lifting for me :)

  • Chris C 6 posts 46 karma points
    Dec 11, 2014 @ 11:33
    Chris C
    0

    I'm having issues getting my parameter in the Controller, so far I have:

    Route:

    //Create a custom route
            RouteTable.Routes.MapUmbracoRoute(
                "test",
                "plate/{action}/{mark}",
                new
                {
                    controller = "MPlate",
                    //                    action = "Index",
                    mark = UrlParameter.Optional
                },
                 new ProductsRouteHandler(1082));
    

    Handler:

      public class ProductsRouteHandler : UmbracoVirtualNodeByIdRouteHandler
    {
    
        public ProductsRouteHandler(int realNodeId)
            : base(realNodeId)
        {
        }
    
        protected override IPublishedContent FindContent(RequestContext requestContext, UmbracoContext umbracoContext, IPublishedContent baseContent)
        {
            return base.FindContent(requestContext, umbracoContext, baseContent);
        }
    }
    

    And Controller...

      public class MPlateController : RenderMvcController
    {
        public ActionResult Index(RenderModel model, string mark)
        {
            return View(model);
        }
    }
    

    I get the correct model passed into the Controller but the mark is always null..

    I'm sure its something simple, can anyone spot something I'm missing?

    Thanks

  • Shannon Deminick 1524 posts 5269 karma points MVP 2x
    Dec 16, 2014 @ 01:24
    Shannon Deminick
    0

    Hi, your route handler seems totally unnecessary, it's just calling the underlying base class UmbracoVirtualNodeByIdRouteHandler which is not an abstract class. You've also commented out your default Action which shouldn't be done.

    What is missing from your example is the URL that you are actually trying to hit in order to populate the 'mark' parameter.

    This should work:

    /plate/Index/hello

    should map to MPlateController.Index and populate mark with the term 'hello'.

    /plate/blah/hello should map to MPlateController.Blah and populate the term with 'hello'.

    I see this over and over again but if you are only going to use a single action on your controller (which is quite common), then you don't want to have such a generic route and specify an {action} token.

    If you just want /plate/hello to work and you will not be using anything but your 'Index' action, then your route would look like:

     //Create a custom route
        RouteTable.Routes.MapUmbracoRoute(
            "test",
            "plate/{mark}",
            new
            {
                controller = "MPlate",
                action = "Index",
                mark = UrlParameter.Optional
            },
             new ProductsRouteHandler(1082));
    

    Of course if you want multiple actions to get hit, then you can include your {action} token, but in some of those cases you'll also want to specify route constraints.

    There's lots of these routing examples in Articulate

    https://github.com/Shazwazza/Articulate/blob/master/Articulate/ArticulateRoutes.cs

  • Jez Reel R. Maghuyop 20 posts 62 karma points
    Oct 23, 2015 @ 08:00
    Jez Reel R. Maghuyop
    0

    Hi, I'm having the same problem and currently stuck on making the ID dynamic and not hard coded.

    below are my sample codes.

     public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication,ApplicationContext applicationContext)
        {            
            RouteTable.Routes.MapUmbracoRoute(
                "Enrolment",
                "enquiries/enrolment/{id}",
                new
                {
                    controller = "EnquiryType",
                    action = "WiseEducationEnrolmentPage",
                    id = UrlParameter.Optional
                }, new EnrolmentPageNodeRouteHandler());         
        }
    

    and this my routeNodeHandler

    public class EnrolmentPageNodeRouteHandler : UmbracoVirtualNodeRouteHandler
    {     
        protected override IPublishedContent FindContent(RequestContext requestContext, UmbracoContext umbracoContext)
        {
            var helper = new UmbracoHelper(umbracoContext);
            string alias = typeof(EnquiryType).Name;
            return helper.TypedContentAtRoot()
                .First()
                .Descendants()
                .First(d => d.DocumentTypeAlias.InvariantEquals(alias));
        }
    }
    

    The problem with this approach is that it only gets the first content based on it's sort order in Umbraco.

    that's why I wanted to know how to achieve the same thing Shannon's code here http://shazwazza.com/post/custom-mvc-routes-within-the-umbraco-pipeline/ with out hardcoding the ID. hope someone can help me. Thanks in Advance.

  • Shannon Deminick 1524 posts 5269 karma points MVP 2x
    Oct 23, 2015 @ 08:21
    Shannon Deminick
    0

    You haven't actually explained what content you actually want assigned for your IPublishedContent.... What content are you trying to return?

  • nandoDel 8 posts 98 karma points
    Mar 20, 2017 @ 15:40
    nandoDel
    0

    Hi guys,

    I've trying to retrieve the rendermodel model into my custom hijacked method, but i always get null. The two optional parameters are correct.

    This is my custom route :

    RouteTable.Routes.MapRoute(
                "umbracoRoute",
                "token-verification/{action}/{userId}/{code}",
                new
                {
                    controller = "ExternalLinkOperations",
                    action = "",
                    userId = UrlParameter.Optional,
                    code = UrlParameter.Optional
                },
                new ConfirmEmailRouteHandler(3290)
                );
    

    this is the ConfirmEmailRouteHandler class:

    public class ConfirmEmailRouteHandler: UmbracoVirtualNodeByIdRouteHandler
    {
        public ConfirmEmailRouteHandler(int realNodeId) : base(realNodeId)
        {
        }
    
        protected override IPublishedContent FindContent(RequestContext requestContext, UmbracoContext umbracoContext, IPublishedContent baseContent)
        {
            return base.FindContent(requestContext, umbracoContext, baseContent);
        }
    }
    

    and this is the the method in the ExternalLinkOperationsController which inherit from rendermodel:

    [AllowAnonymous]
        public async Task<ActionResult> ConfirmEmail(RenderModel model, string userId, string code)
        {
    

    }

    so Im not getting the model parameter only the two optional parameter, what i could be doing wrong, I also tried to make this

     new UmbracoVirtualNodeByIdRouteHandler(3290) instead of  new ConfirmEmailRouteHandler(3290), 
    

    but without success, I'm using umbraco v 7.5.3. Even when debugging the code in any moment i see it gets into the overrided method FindContent

    Thanks in advance for any help

  • Shannon Deminick 1524 posts 5269 karma points MVP 2x
    Mar 30, 2017 @ 00:04
    Shannon Deminick
    0

    You aren't creating the route property, you have RouteTable.Routes.MapRoute which is incorrect, you are supposed to be using RouteTable.Routes.MapUmbracoRoute as the examples above and in the docs.

    What does your controller inherit from? This should be RenderController

  • nandoDel 8 posts 98 karma points
    Mar 30, 2017 @ 12:50
    nandoDel
    0

    Oops! I didn't realize it. Very thanks Shannon!

  • Michael Wang 2 posts 71 karma points
    Sep 21, 2017 @ 11:46
    Michael Wang
    0

    Hi Shannon, by RenderController you actually mean RenderMvcController?

  • Shannon Deminick 1524 posts 5269 karma points MVP 2x
    Sep 21, 2017 @ 11:55
    Shannon Deminick
    0

    yes sorry, Typo, though in recent versions (within a year or so) you can interface IRenderController too

Please Sign in or register to post replies

Write your reply to:

Draft