Copied to clipboard

Flag this post as spam?

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


  • Henrik 2 posts 93 karma points
    Feb 07, 2019 @ 13:48
    Henrik
    0

    Could not find a Surface controller route in the RouteTable for controller name Login

    I have this small login function

        [HttpPost]
        public ActionResult ValidateLogin(RCS_CustomerContact model)
        {
            Session["userID"] = null;
            using (RCS.Models.SAPContext context = dbContext)
            {
                if (model.E_MailL != null && model.Password != null)
                {
                    RCS_CustomerContact user = context.CustomerContact.Where(a => a.E_MailL == model.E_MailL && a.Password == model.Password).FirstOrDefault();
                    if (user != null)
                    {
                        Session["userID"] = user.ID;
                        Response.Redirect("/login/profile/");
                    }
                    else
                    {
                        TempData["LoginMessage"] = "Invalid user/password";
                        Session["userID"] = null;
                    }
                }
                else
                {
                    TempData["LoginMessage"] = "Fill username and password";
                    Session["userID"] = null;
                }
            }
            return RedirectToCurrentUmbracoPage();
        }
    

    And this partial

    @model RCS.Models.RCS_CustomerContact
    
    @using (Html.BeginUmbracoForm("ValidateLogin", "Login", FormMethod.Post))
    {
    <section>
        <div>
            <table>
                <tr>
                    <td>@Html.LabelFor(a => a.E_MailL):</td>
                    <td>
                        @Html.TextBoxFor(a => a.E_MailL)
                    </td>
                </tr>
                <tr>
                    <td>@Html.LabelFor(a => a.Password):</td>
                    <td>@Html.TextBoxFor(a => a.Password)</td>
                </tr>
            </table>
            <input type="submit" value="Login">**
    
            @TempData["LoginMessage"]
    
    
        </div>
    </section>
    }
    

    The user goes to www.mysite.com/login/ Here he sees the login partial, fills it out and login.

    This works perfectly fine. No problem what so ever... Subpages like "profile" and "orderhistory" also works fine.

    Now I need to add a custom route to /login/order/[orderNum] so the user can click an order in "history" and see the details.

    So I added this Route (I also have a route to product details)

    public class CustomRoutes: ApplicationEventHandler
    {
        protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
        {
            RouteTable.Routes.MapUmbracoRoute(
                "ProductDetails",
                "Product/{itemInfo}",
                new
                {
                    controller = "Product",
                    action = "ProductDetails",
                    itemID = UrlParameter.Optional
                },
                new FindProductRouteHandler()
            );
            RouteTable.Routes.MapUmbracoRoute(
                "OrderDetails",
                "login/order/{orderNum}",
                new
                {
                    controller = "Login",
                    action = "OrderDetails",
                    itemID = UrlParameter.Optional
                },
                new FindOrderRouteHandler()
            );
            base.ApplicationStarting(umbracoApplication, applicationContext);
    
        }
    }
    
    
    public class FindOrderRouteHandler : UmbracoVirtualNodeRouteHandler
    {
        protected override IPublishedContent FindContent(RequestContext requestContext, UmbracoContext umbracoContext)
        {
    
            var productPage = umbracoContext.ContentCache
                .GetAtRoot()
                .First()
                .FirstChild(a => a.Name.ToLower() == "login")
                .FirstChild(a => a.Name.ToLower() == "orderdetail");
    
            return productPage;
        }
    }
    

    So now I have a route, and can browse to /login/order/123 and see the order. Perfect...

    But now my login doesn't work. What?? I can go to /login/ and see the login form, but when I try to login I get this error:

    Could not find a Surface controller route in the RouteTable for controller name Login
    

    If I remove the route my login works again:

        //RouteTable.Routes.MapUmbracoRoute(
        //    "OrderDetails",
        //    "login/order/{orderNum}",
        //    new
        //    {
        //        controller = "Login",
        //        action = "OrderDetails",
        //        itemID = UrlParameter.Optional
        //    },
        //    new FindOrderRouteHandler()
        //);
    

    Can someone tell me why? And what to do to fix it?

    I am kind off new to Umbraco. Only worked with it for a few months.

  • Henrik 2 posts 93 karma points
    Feb 07, 2019 @ 15:36
    Henrik
    101

    Ok.. I found a workarround.

    I made a new "LoginRoute" controller, and pointed my route to that:

        RouteTable.Routes.MapUmbracoRoute(
            "OrderDetails",
            "login/order/{orderNum}",
            new
            {
                controller = "LoginRoute",
                action = "OrderDetails",
                itemID = UrlParameter.Optional
            },
            new FindOrderRouteHandler()
        );
    

    Apparently you can't have this:

    public ActionResult OrderDetails(RenderModel model, string orderNum)
    {
       //Code
    }
    
    public ActionResult ValidateLogin(RCS_CustomerContact model)
    {
      //Code
    }
    

    In the same surfacecontroller when one ActionResult is used in a custom route.

    Are the any recommended way of creating Controllers for custom routes? I will have to make a few more, so I'm wondering if I should simply make a "CustomRoute" controller, and let that handle all custom routes. But that means that my code will be spilt up, so all Actions related to Login will not be placed in the LoginController, since a few will be in the CustomRouteController.

  • Colin Wiseman 47 posts 178 karma points
    Nov 13, 2019 @ 10:05
    Colin Wiseman
    0

    I've been working on this today and I have found this is my solution too if I don't want to hack into Umbraco's source code. Not ideal, but there are worse things in the world. I kept the two controllers in the same file too to make sure it was easier to find.

    But I did hack into Umbraco's source code and can see why the error appears.

    1. When you post back to say Blah/SaveStuff, Umbraco searches for the right route.

    2. If it finds more than 1 route that matches e.g. 1. The SurfaceController you set up called Blah. 2. The CustomRoute you set up which specifies Blah. it then tries to find the route with the action you've specified.

    3. But because both the SurfaceController route is generic it hasn't named your postback Action in the route data and the CustomRoute is to work on a specific action only then neither of these match and the logic fails and throws the error.

    So, imho, the error message is wrong because it did find the controller, it just couldn't find the action you were posting to in the Route table - the route table shouldn't hold all actions otherwise it'd be unwieldy and to me this is where Umbraco falls down a bit. It is expecting all actions to be in the data it finds.

    The real solution would be for the code to say "well I found 2 (or more) routes with for this Controller but none of them are a direct match for the controller/action pair... I do however have a generic SurfaceController route I should use that and if after this the action isn't available, the developer can fix that."

    So that's what I did:

    Umbraco V8 RenderRouteHandler.cs Line 197

    I added:

    if(surfaceRoute == null)
    {
        // we couldn't find the specific one, so let's just default to the generic surface controller route which is always the first one in the list...
        surfaceRoute = surfaceRoutes.FirstOrDefault();
    }
    

    And it now works. I think I will submit a PR for this.

  • Harry Spyrou 212 posts 604 karma points
    Feb 07, 2019 @ 15:46
    Harry Spyrou
    0

    Have you checked Umbraco Route hijacking?

Please Sign in or register to post replies

Write your reply to:

Draft