After a number of attempts, cannot seem to specify a route attribute for a SurfaceController method. To avoid bashing my head into a wall, can anyone clarify? If you have it working I'd love to see an example.
I haven't used MVC Attribute Routes with Umbraco before (I have for API Routes), so I'm not sure if this will work, or what you are aiming to achieve, if you have some example code for context that would be great.
SurfaceControllers are 'auto routed',
/umbraco/surface/{controllername}/{action}/{id}
when you create a controller that inherits from SurfaceController the route is setup through Umbraco, and they are designed to sit on the surface of the underlying RenderMvcController and handle Postbacks from forms or be used as a Child Action.
Usually if you want to handle an incoming Umbraco Url in a custom way, you would either create an IContentFinder + UrlProvider to map that custom Url to an Umbraco item (https://our.umbraco.com/documentation/Reference/Routing/Request-Pipeline/) , or use RouteHijacking to map the request to a custom RenderMvcController by Document Type Convention (https://our.umbraco.com/documentation/reference/routing/custom-controllers) - or by Mapping a custom route to the RenderMvcController using a Virtual Node Route Handler.(https://our.umbraco.com/documentation/Reference/Routing/custom-routes)
That all said there is a type of MVC controller that isn't auto-routed, called a PluginController (Surface Controller inherits from this), so depending on your aims, your custom MVC controller could inherit directly from this...
... MVC attribute routing needs to be 'called' to be setup on an MVC site, so the further step would be to call
routes.MapMvcAttributeRoutes();
During the startup of the Umbraco site, which you would achieve by creating and registering a component to modify this during composition.
public class RegisterCustomRouteComponent : IComponent
{
public void Initialize()
{
RouteTable.Routes.MapMvcAttributeRoutes();
}
}
So in summary... Umbraco is handling routing in a particular way to make requests work through the CMS, yes it's using MVC but it needs to do some additional things to build the context of a PublishedContent request... there are paths for most common scenarios to change Url routing and ways to create custom MVC controllers to process requests... so check if those provide the options you are looking for... but fundamentally it is MVC so if MapMvcAttributeRoutes() is called at application startup, you should be able to make use of attribute routing... as I've done for APIAttribute routing, it will be interesting to see if it works for a plain Controller, or SurfaceController/PluginController or RenderMvcController...
Those are great options -- and tried all but the plugin approach (didn't look like it would work there). Having to setup the route using the UserComposer approach means you're not using Route attributes.....which is a bit throw back. If you've spent much time using Razor pages (basically a simplification of traditional MVC -- https://stackify.com/asp-net-razor-pages-vs-mvc/), you get a bit spoiled. You can add a view to the project setup the route, and not even have to compile anything.
Can confirm that if you call MapMvcAttributeRoutes() once in a Component at initialization time, then you are free to use attribute routing across your Controllers: PluginController, RenderMvcController or SurfaceController.
Example would be
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Web.Mvc;
namespace UmbracoV8.AttributeRoutingExample
{
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
public class RegisterAttributeRoutingComposer : ComponentComposer<RegisterAttributeRoutingComponent>
{
// nothing needed to be done here!
}
public class RegisterAttributeRoutingComponent : IComponent
{
public void Initialize()
{
RouteTable.Routes.MapMvcAttributeRoutes();
}
public void Terminate()
{
}
}
public class CustomBooksPluginController : PluginController
{
[Route("books/{isbn?}")]
public ActionResult Index(string isbn)
{
return Content("display a book with isbn no: " + isbn);
}
}
}
then a request to /books/1234 is routed to your bespoke MVC Controller...
... however if using a SurfaceController, or RenderMVCController Umbraco won't know the context of the published content associated with the request, so CurrentPage will be null... but if you are just running an MVC route alongside Umbraco then the PluginController is the one to use.
But agree this is different to pure MVC, Umbraco predates MVC and has evolve overtime, they are currently working on .net core version.. https://umbraco.com/blog/the-unicore-team/ so maybe there will be more flexibility over how the routes are defined, or how to associate a published content item with a particular route... (eg another attribute perhaps...) or maybe this is the time to raise the issue on the github tracker.
SurfaceController Route Attributes?
After a number of attempts, cannot seem to specify a route attribute for a SurfaceController method. To avoid bashing my head into a wall, can anyone clarify? If you have it working I'd love to see an example.
Hi Paul
I haven't used MVC Attribute Routes with Umbraco before (I have for API Routes), so I'm not sure if this will work, or what you are aiming to achieve, if you have some example code for context that would be great.
SurfaceControllers are 'auto routed',
/umbraco/surface/{controllername}/{action}/{id}
when you create a controller that inherits from SurfaceController the route is setup through Umbraco, and they are designed to sit on the surface of the underlying RenderMvcController and handle Postbacks from forms or be used as a Child Action.
Usually if you want to handle an incoming Umbraco Url in a custom way, you would either create an IContentFinder + UrlProvider to map that custom Url to an Umbraco item (https://our.umbraco.com/documentation/Reference/Routing/Request-Pipeline/) , or use RouteHijacking to map the request to a custom RenderMvcController by Document Type Convention (https://our.umbraco.com/documentation/reference/routing/custom-controllers) - or by Mapping a custom route to the RenderMvcController using a Virtual Node Route Handler.(https://our.umbraco.com/documentation/Reference/Routing/custom-routes)
That all said there is a type of MVC controller that isn't auto-routed, called a PluginController (Surface Controller inherits from this), so depending on your aims, your custom MVC controller could inherit directly from this...
... MVC attribute routing needs to be 'called' to be setup on an MVC site, so the further step would be to call
During the startup of the Umbraco site, which you would achieve by creating and registering a component to modify this during composition.
There is an example of using a UserComposer to add a Component to Umbraco here: https://our.umbraco.com/documentation/Implementation/Composing/#example---creating-a-component-to-listen-for-contentservicesaving-events
So in summary... Umbraco is handling routing in a particular way to make requests work through the CMS, yes it's using MVC but it needs to do some additional things to build the context of a PublishedContent request... there are paths for most common scenarios to change Url routing and ways to create custom MVC controllers to process requests... so check if those provide the options you are looking for... but fundamentally it is MVC so if MapMvcAttributeRoutes() is called at application startup, you should be able to make use of attribute routing... as I've done for APIAttribute routing, it will be interesting to see if it works for a plain Controller, or SurfaceController/PluginController or RenderMvcController...
regards
Marc
Those are great options -- and tried all but the plugin approach (didn't look like it would work there). Having to setup the route using the UserComposer approach means you're not using Route attributes.....which is a bit throw back. If you've spent much time using Razor pages (basically a simplification of traditional MVC -- https://stackify.com/asp-net-razor-pages-vs-mvc/), you get a bit spoiled. You can add a view to the project setup the route, and not even have to compile anything.
Hi Paul
Can confirm that if you call MapMvcAttributeRoutes() once in a Component at initialization time, then you are free to use attribute routing across your Controllers: PluginController, RenderMvcController or SurfaceController.
Example would be
then a request to /books/1234 is routed to your bespoke MVC Controller...
... however if using a SurfaceController, or RenderMVCController Umbraco won't know the context of the published content associated with the request, so CurrentPage will be null... but if you are just running an MVC route alongside Umbraco then the PluginController is the one to use.
But agree this is different to pure MVC, Umbraco predates MVC and has evolve overtime, they are currently working on .net core version.. https://umbraco.com/blog/the-unicore-team/ so maybe there will be more flexibility over how the routes are defined, or how to associate a published content item with a particular route... (eg another attribute perhaps...) or maybe this is the time to raise the issue on the github tracker.
regards
Marc
is working on a reply...