3
votes

I have a website defined in Sitecore's site definitions. The path to it is /localhost/mysite/home. And it works.

I need to create a custom controller to submit forms with an API bypassing Sitecore. So I have FormsController (inheriting from MVC controller) with an action named "Test" taking no parameters.

I defined the route in the initialize pipeline like this:

public class Initialize
{
    public void Process(PipelineArgs args)
    {
        MapRoutes();
        GlassMapperSc.Start();
    }

    private void MapRoutes()
    {
        RouteTable.Routes.MapRoute(
                "Forms.Test", 
                "forms/test", 
                new
                {
                    controller = "FormsController",
                    action = "Test"
                },
                new[] { "Forms.Controller.Namespace" });
     }
}

The route is added to the route table correctly, and it's there when I debug it. Now, when I try to call method "test", the route is not found and the debugger doesn't hit the breakpoint in the action.

I'm trying different routes:

  • /localhost/mysite/home/forms/test
  • /localhost/forms/test (default website)

But no luck so far.

---- UPDATE ---

Going deeper into it, I noticed that there's something wrong with Sitecore's behavior. The TransferRoutedRequest processor is supposed to abort the httpRequestBegin pipeline, giving control back to MVC, in case the context item is null (simplifying). It happens after some checks, among which is one on RoutTable data. But the call to RouteTable.Routes.GetRouteData returns always null, which makes the processor return without aborting the pipeline. I overrode it to make it abort the pipeline correctly, but still, even if I it calls method args.AbortPipeline(), pipeline is not aborted and route not resolved.

this is how the original TransferRoutedRequest looked like:

public class TransferRoutedRequest : HttpRequestProcessor
{
  public override void Process(HttpRequestArgs args)
  {
    Assert.ArgumentNotNull((object) args, "args");
    RouteData routeData = RouteTable.Routes.GetRouteData((HttpContextBase) new HttpContextWrapper(HttpContext.Current));
    if (routeData == null)
      return;
    RouteValueDictionary routeValueDictionary = ObjectExtensions.ValueOrDefault<Route, RouteValueDictionary>(routeData.Route as Route, (Func<Route, RouteValueDictionary>) (r => r.Defaults));
    if (routeValueDictionary != null && routeValueDictionary.ContainsKey("scIsFallThrough"))
      return;
    args.AbortPipeline();
   }
}

and this is how I overrode it:

public class TransferRoutedRequest : global::Sitecore.Mvc.Pipelines.HttpRequest.TransferRoutedRequest
{
    public override void Process(HttpRequestArgs args)
    {
        if (Context.Item == null || Context.Item.Visualization.Layout == null)
            args.AbortPipeline();
        else
            base.Process(args);
    }
}
3

3 Answers

7
votes

Here is a working example taken from one of my projects.

Custom Route Registration:

namespace Test.Project.Pipelines.Initialize
{
    public class InitRoutes : Sitecore.Mvc.Pipelines.Loader.InitializeRoutes
    {
        public override void Process(PipelineArgs args)
        {
            RegisterRoutes(RouteTable.Routes);
        }

        protected virtual void RegisterRoutes(RouteCollection routes)
        {
            routes.MapRoute(
                "Test", // Route name
                "api/test/{controller}/{action}/{id}", // URL with parameters
                 new { id = UrlParameter.Optional }
                );
        }
    }
}

Initialize Pipeline Config:

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
     <pipelines>
         <initialize>
            <processor type="Test.Project.Pipelines.Initialize.InitRoutes, Test.Project"
         patch:after="processor[@type='Sitecore.Mvc.Pipelines.Loader.InitializeRoutes, Sitecore.Mvc']" />
        </initialize>
     </pipelines>
  </sitecore>
</configuration>
0
votes

Here is the code that will create a route for you. In global.asax.cs you will call RegisterRoutes from App_Start event handler:

    protected void Application_Start()
    {
        RouteConfig.RegisterRoutes(RouteTable.Routes);
    }

And there you specify your route as:

    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.MapRoute(
             name: "test",
             url: "mvc/Forms/{action}/{id}",
             defaults: new { controller = "Forms", action = "Test", id = UrlParameter.Optional }
           );
    }

You will have /mvc/ prefix in this case that will handle your route to specifies controller, so you will call it as:

/mvc/Forms/Test/{you_may_pass_some_optional_GUID_here}

This will route to FormsController class action method Test(string id) but you may omit id parameter

0
votes

Finally I've got it working correctly. As I wrote TransferRoutedRequest wasn't working as expected, so I had to override it. Still, even if it worked as it's supposed to, the route wasn't being resolved. The problem was in the pipeline configuration. Thanks to a colleague I opened SitecoreRocks pipeline tool and it showed the pipeline was registered in a position too far away from where it should have been, so it was never hit (I had registered it after ItemResolver, as it was on the original configuration). I patched it before LayoutResolver and that did the trick. The route was resolved. Still, Sitecore wasn't able to create an instance of the type, as it was in another assembly. Even specifying the controller's namespace didn't solve the problem. So I had to make some modifications and override CreateController method of ControllerFactory class and override InitializeControllerFactory processor (which I already had modified to be able to work with a DI container), writing a new ControllerFactory and a new SitecoreControllerFactory.

The final code looks like this:

public override IController CreateController(RequestContext requestContext, string controllerName)
    {
        var controller = SC.Context.Item == null || SC.Context.Item.Visualization.Layout == null
                        ? base.GetControllerType(requestContext, controllerName)
                        : TypeHelper.GetType(controllerName);

        return GetControllerInstance(requestContext, controller);
    }

In case I'm dealing with a Sitecore item, I use TypeHelper to return current controller type from a controller rendering or layout. Otherwise I use DefaultControllerFactory.GetControllerType to resolve the custom route.

The only thing to be careful: in case 2 or more namespaces have a controller with the same name and action, it's mandatory to add a namespace in order to identify them.