Copied to clipboard

Flag this post as spam?

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


  • Jason Prothero 422 posts 1243 karma points c-trib
    Dec 17, 2015 @ 16:42
    Jason Prothero
    0

    Getting an error with custom routes: Could not find a Surface controller route in the RouteTable for controller name Authorization

    I'm getting an error on my 7.2.8 Umbraco instance:

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

    I have a custom route defined:

            routes.MapUmbracoRoute(
               "auth",
               firstUrlSegment + "/{action}",
               new
               {
                   controller = "Authorization",
                   action = "Index",
               },
               new PortalAuthorizationRouteHandler());
    

    The PortalAuthorizationRouteHandler inherits from UmbracoVirtualNodeRouteHandler and implements FindContent and that's all it does:

    public class PortalAuthorizationRouteHandler : UmbracoVirtualNodeRouteHandler
    {
        protected override Umbraco.Core.Models.IPublishedContent FindContent(System.Web.Routing.RequestContext requestContext, Umbraco.Web.UmbracoContext umbracoContext)
        {
            var settingsnodeid = int.Parse(WebConfigurationManager.AppSettings["Portal:SettingsNode"]);
            var settings = umbracoContext.ContentCache.GetById(settingsnodeid);
            if (settings == null) return null;
    
            var actionName = "login";
            var routeValues = requestContext.RouteData.Values;
            if (routeValues != null) 
            {
                if (routeValues.ContainsKey("action"))
                {
                    actionName = requestContext.RouteData.Values["action"].ToString();
                }
            }
    
            var propertyAlias = "";
            switch (actionName.ToLower())
            {
                case "index":
                case "login":
                    propertyAlias = "login";
                    break;
                case "forgottenpassword":
                    propertyAlias = "forgotpassword";
                    break;
                case "registration":
                    propertyAlias = "registration";
                    break;
                case "resetpassword":
                    propertyAlias = "forgotpassword";
                    break;
            }
    
    
            var content = settings.GetPropertyValue<IPublishedContent>(propertyAlias);
            if (content == null) return null;
    
            return content;
        }
    }
    

    My controller gets called properly on the initial "Get" of the page:

        [HttpGet]
        public ActionResult ForgottenPassword()
        {
            var model = BuildModel<AuthorizationForgotPasswordFormModel>();
    
            return View(model);
        }
    

    But when I try to "Post" the form, I get the error:

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult ForgottenPassword(AuthorizationForgotPasswordFormModel model)
        {
            if (ModelState.IsValid)
            {
                 ... (Never gets to this action)
            }
        }
    

    My view is using UmbracoBeginForm and is passing in a specific controller to handle the POST:

            @using (Html.BeginUmbracoForm<AuthorizationController>("ForgottenPassword", FormMethod.Post, new { @class = "slide-toggle forgot-password-slide" }))
            {
                @Html.ValidationSummary(true, "ValidationSummary")
                <div>
                    <label>
                        @Html.TextBoxFor(x => x.EmailAddress, new { placeholder = Umbraco.Field("#Common.Email", altText: "Email address"), @type = "text", @class = "email" })
                        @Html.ValidationMessageFor(x => x.EmailAddress)
                    </label>
                    <button type="submit" class="btn violet">@Umbraco.Field("#Common.Submit", altText: "Submit")</button>
                </div>
    
                @Html.Raw(TempData["CustomMessage_ForgotPassword"])
    
                @Html.AntiForgeryToken()
            }
    

    I'm not sure why the error is occurring. Here's the stack trace:

    [InvalidOperationException: Could not find a Surface controller route in the RouteTable for controller name Authorization]
       Umbraco.Web.Mvc.RenderRouteHandler.HandlePostedValues(RequestContext requestContext, PostedDataProxyInfo postedInfo) +622
           Umbraco.Web.Mvc.UmbracoVirtualNodeRouteHandler.GetHttpHandler(RequestContext requestContext) +618
           System.Web.Routing.UrlRoutingModule.PostResolveRequestCache(HttpContextBase context) +12411255
           System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +92
           System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +165
    

    I am using the Hybrid methodology with a RenderModel and the base SurfaceController that my AuthoizationController inherits from inherits from "SurfaceController, IRenderMvcController"

    Anyone have any ideas? I do get a breakpoint to hit in my PortalAuthorizationRouteHandler FindContent() method and the controller and action are correct. I never get a breakpoint hit in my controller action method because the error hits first.

    Thanks, Jason

  • Charles Afford 1163 posts 1709 karma points
    Dec 17, 2015 @ 18:12
    Charles Afford
    0

    Hello :)

    I don't suppose you have any other routes defined do you? I imagine it's an 'Umbraco' thing. But I had the same sort of problem with a Sitecore project today

  • Charles Afford 1163 posts 1709 karma points
    Dec 17, 2015 @ 18:16
    Charles Afford
    0

    What is the model type you are passing from the view? It could be because the type or name of the controller is wrong?

  • Jason Prothero 422 posts 1243 karma points c-trib
    Dec 17, 2015 @ 18:30
    Jason Prothero
    0

    I do have one other route:

       routes.MapUmbracoRoute(
                    "authResetPassword",
                    firstUrlSegment + "/reset-password/{token}/{memberId}",
                    new
                    {
                        controller = "Authorization",
                        action = "ResetPassword",
                    },
                    new UmbracoVirtualNodeByIdRouteHandler(nodeid));
    

    My model inherits from an Umbraco.Web.Models.RenderModel. I was wondering if that is the issue, but I can't seem to get it to post to a plain POCO model even with just using plain old BeginForm...

    Still trying things.

    Thanks for the responses!

  • Charles Afford 1163 posts 1709 karma points
    Dec 17, 2015 @ 18:44
    Charles Afford
    0

    No worries :). I remember having the same sort of problem. Isn't there something else you can inherit from in the model or the controller. What model do you have in your view? What type is it? I have a hunch it's your parameters, not sure why

  • Jason Prothero 422 posts 1243 karma points c-trib
    Dec 17, 2015 @ 19:21
    Jason Prothero
    0

    I'm debugging the Umbraco Source and it fails here:

    https://github.com/umbraco/Umbraco-CMS/blob/7.2.8/src/Umbraco.Web/Mvc/RenderRouteHandler.cs#L231

    There are three surfaceRoutes in the collection -- my two I listed above, plus one for:

    "umbraco/Surface/Authorization/{action}/{id}"

    Since the code isn't sure what to do it checks against the Defaults to try to match that route. Since none of my defaults match that Action, it returns null and throws the error.

    Now what to do about that...

  • Jason Prothero 422 posts 1243 karma points c-trib
    Dec 17, 2015 @ 19:40
    Jason Prothero
    0

    Tried adding a new route:

       routes.MapUmbracoRoute(
               "authForgottenPassword",
               firstUrlSegment + "/{action}",
               new
               {
                   controller = "Authorization",
                   action = "ForgottenPassword",
               },
               new PortalAuthorizationRouteHandler());
    

    Now I'm getting a new error. Apparently, the UmbracoVirtualNodeRouteHandler GetHttpHandler() method is getting called twice and the DataTokens are being added to twice which causes an exception that the key already exists.

    https://github.com/umbraco/Umbraco-CMS/blob/7.2.8/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs#L34

    Trying so see now if there's a workaround or if I need to file a bug on the issues.

  • Charles Afford 1163 posts 1709 karma points
    Dec 17, 2015 @ 19:45
    Charles Afford
    0

    Do you need that route?

    With the ignoreroute. There is an overload that takes a collection (that are allow route paths) You could ignore the Umbraco route and pass in your routes as parameters. ? :)

  • Jason Prothero 422 posts 1243 karma points c-trib
    Dec 17, 2015 @ 19:48
    Jason Prothero
    1

    I cannot override that method... I think the only option is to tweak the core. I'll need to get Shannon's eye's on this.

    I'll create an issue on YouTrack

  • Jason Prothero 422 posts 1243 karma points c-trib
    Dec 17, 2015 @ 19:54
    Jason Prothero
    0

    Tried a quick fix to simply not add the keys if they already exist (in the Umbraco.Web project) and pushed that to my site. Now I'm getting this error:

    Recursive read lock acquisitions not allowed in this mode. Stack Trace:

    [LockRecursionException: Recursive read lock acquisitions not allowed in this mode.]
       System.Threading.ReaderWriterLockSlim.TryEnterReadLockCore(TimeoutTracker timeout) +5944348
       System.Threading.ReaderWriterLockSlim.TryEnterReadLock(TimeoutTracker timeout) +42
       System.Threading.ReaderWriterLockSlim.TryEnterReadLock(Int32 millisecondsTimeout) +77
       System.Web.Routing.RouteCollection.GetReadLock() +21
       Umbraco.Web.Mvc.RenderRouteHandler.HandlePostedValues(RequestContext requestContext, PostedDataProxyInfo postedInfo) +750
       Umbraco.Web.Mvc.UmbracoVirtualNodeRouteHandler.GetHttpHandler(RequestContext requestContext) +2387
       Umbraco.Web.Mvc.RenderRouteHandler.HandlePostedValues(RequestContext requestContext, PostedDataProxyInfo postedInfo) +2310
       Umbraco.Web.Mvc.UmbracoVirtualNodeRouteHandler.GetHttpHandler(RequestContext requestContext) +2387
       System.Web.Routing.UrlRoutingModule.PostResolveRequestCache(HttpContextBase context) +12411255
       System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +92
       System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +165
    

    I may be hitting a wall here...

  • Jason Prothero 422 posts 1243 karma points c-trib
    Dec 17, 2015 @ 20:05
    Jason Prothero
    1

    I edited one more thing in the core:

    I changed this line:

    handler = surfaceRoute.RouteHandler.GetHttpHandler(requestContext);
    

    (https://github.com/umbraco/Umbraco-CMS/blob/7.2.8/src/Umbraco.Web/Mvc/RenderRouteHandler.cs#L266)

    TO:

     handler = new MvcHandler(requestContext);
    

    When I did that, I didn't need to check for duplicates in the UmbracoVirtualNodeRouteHandler GetHttpHandler() method because its no longer called twice.

    So, one little change to the core makes it all work.

    Heading to YouTrack now...

  • Shannon Deminick 1524 posts 5269 karma points MVP 2x
    Dec 17, 2015 @ 20:18
    Shannon Deminick
    1

    Hi, I will look at this more tomorrow but here are some tips

    • don't mix surface controllers with render controllers
    • Don't mix surface controllers with custom routed controllers
    • surface controllers are auto routed, so they already have a route, so now you have multiple routes for the same controller
    • if you are using custom routes, you are using mvc, plain old mvc, so you can just use mvc the normal way, you don't even need surface controllers to post to, just pass in the current node id to your custom, non-surface controller form. You can still use surface controllers of you really want but since you're creating your own routes there is no need to. Surface controllers are made for form post back when inside the Umbraco routing pipeline, you are operating outside this pipeline, so you are just doing mvc.
  • Shannon Deminick 1524 posts 5269 karma points MVP 2x
    Dec 17, 2015 @ 20:24
    Shannon Deminick
    0

    Another tip, is if you are creating custom routes, be as specific with the route as possible, for example, the route you mentioned

    routes.MapUmbracoRoute( "authForgottenPassword", firstUrlSegment + "/{action}", new { controller = "Authorization", action = "ForgottenPassword", }, new PortalAuthorizationRouteHandler());

    If there's actually only one endpoint which is

    firstUrlSegment + "ForgottenPassword"

    Then it should just be that exact route without the {action} token

  • Shannon Deminick 1524 posts 5269 karma points MVP 2x
    Dec 17, 2015 @ 20:29
    Shannon Deminick
    0

    I just saw your details on the tracker.... Before you submit a PR for that change, I suspect it might break normal surface controller post back behavior, it might work for this one instance but have a feeling it will break all other cases. I'm any case happy to review more tomorrow and if you could test your proposed change with all normal scenarios that'd be good as well as testing your implementation with the above recommendations.

  • Jason Prothero 422 posts 1243 karma points c-trib
    Dec 17, 2015 @ 21:21
    Jason Prothero
    0

    If there's another way around this, then I would love to do it. Are you saying having two controllers? One for the render and one for the post?

    Or if there are things I should test to QA the change, let me know.

    I'm available for a Skype if needed. I could do it fairly early here I think. Which would be late in the day for you.

  • Jason Prothero 422 posts 1243 karma points c-trib
    Dec 17, 2015 @ 21:46
    Jason Prothero
    0

    Also, thanks for the tip on the more specific url segment.

Please Sign in or register to post replies

Write your reply to:

Draft