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:
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.:
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. :)
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.
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?
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:
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? :)
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?
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.
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:
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).
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 :)
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;
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 :)
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
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
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. :)
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.
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.
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 :)
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:
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:
This will basically just take the current IPublishedContent (nodeID 1056 in Umbraco) and pass it to the MyProductController:
Where I define a ProductViewModel - which inherits from RenderModel:
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:
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? :)
Where have you created your ProductsRouteHandler?
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.
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.
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
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 :)
Yup, you should be able to to what you like in that regard :)
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
Ah okay - so untill version 7.2 I'll just use regular forms with my custom code :) Thanks again Shannon.
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;
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:
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 :)
That was it! it works like a charm. Thank you Jacob.
You got it - can't take all the credit though. Shannon and Nicholas did all the heavy lifting for me :)
I'm having issues getting my parameter in the Controller, so far I have:
Route:
Handler:
And Controller...
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
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:
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
Hi, I'm having the same problem and currently stuck on making the ID dynamic and not hard coded.
below are my sample codes.
and this my routeNodeHandler
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.
You haven't actually explained what content you actually want assigned for your IPublishedContent.... What content are you trying to return?
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 :
this is the ConfirmEmailRouteHandler class:
and this is the the method in the ExternalLinkOperationsController which inherit from rendermodel:
}
so Im not getting the model parameter only the two optional parameter, what i could be doing wrong, I also tried to make this
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
You aren't creating the route property, you have
RouteTable.Routes.MapRoute
which is incorrect, you are supposed to be usingRouteTable.Routes.MapUmbracoRoute
as the examples above and in the docs.What does your controller inherit from? This should be
RenderController
Oops! I didn't realize it. Very thanks Shannon!
Hi Shannon, by
RenderController
you actually meanRenderMvcController
?yes sorry, Typo, though in recent versions (within a year or so) you can interface IRenderController too
is working on a reply...