GetInferredPagingMapper has default actions (Index, Details), causes MVC widgets with matching actions to fail. - Front- & Back-End Development - Front- & Back-End Development - Progress Community

GetInferredPagingMapper has default actions (Index, Details), causes MVC widgets with matching actions to fail.

 Front- & Back-End Development

GetInferredPagingMapper has default actions (Index, Details), causes MVC widgets with matching actions to fail.

  • GetInferredPagingMapper has default actions (Index, Details), causes MVC widgets with matching actions to fail.
  • I recently started playing with Feather and found an issue that has me baffled.

     I had an existing MVC widget that was working fine until I enabled Feather to make use of the MVC Video Gallery widget.

    I didn't expect it would break anything by just being enabled but perhaps you can tell me if I did something wrong.

    The original widget has an action "CreateItem" that returns the associated view, and another CreateItem(itemModel) with [HttpPost] to accept the post.

    Despite this separation, posting the form for that action resulted in the error:

    [AmbiguousMatchException: Ambiguous match found.]
       System.RuntimeType.GetMethodImpl(String name, BindingFlags bindingAttr, Binder binder, CallingConventions callConv, Type[] types, ParameterModifier[] modifiers) +12226787
       System.Type.GetMethod(String name, BindingFlags bindingAttr) +31
       Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Routing.CustomActionParamsMapper..ctor(ControllerBase controller, Func`1 routeTemplateResolver, String actionName) +55
       Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Routing.DynamicUrlParamActionInvoker.GetInferredPagingMapper(ControllerBase controller, String actionName) +208
       Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Routing.DynamicUrlParamActionInvoker.GetDefaultParamsMapper(ControllerBase controller) +103
       Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Routing.DynamicUrlParamActionInvoker.InitializeRouteParameters(MvcProxyBase proxyControl) +129
       Telerik.Sitefinity.Mvc.ControllerActionInvoker.TryInvokeAction(MvcProxyBase proxyControl, String& output) +136
       Telerik.Sitefinity.Mvc.Proxy.MvcControllerProxy.ExecuteController() +38
       Telerik.Sitefinity.Mvc.Proxy.MvcControllerProxy.OnPreRender(EventArgs e) +20
       System.Web.UI.Control.PreRenderRecursiveInternal() +88
       System.Web.UI.Control.PreRenderRecursiveInternal() +160
       System.Web.UI.Control.PreRenderRecursiveInternal() +160
       System.Web.UI.Control.PreRenderRecursiveInternal() +160
       System.Web.UI.Control.PreRenderRecursiveInternal() +160
       System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +883

    Hours of decompiling and a million breakpoints later, I discovered this method:

    private IUrlParamsMapper GetInferredPagingMapper(ControllerBase controller, string actionName)
        ActionDescriptor actionDescriptor = (new ReflectedControllerDescriptor(controller.GetType())).FindAction(controller.ControllerContext, actionName);
        if (actionDescriptor == null || (int)actionDescriptor.GetParameters().Length == 0 || actionDescriptor.GetParameters()[0].ParameterType != typeof(int?))
            return null;
        return new CustomActionParamsMapper(controller, () => string.Concat("/", actionDescriptor.GetParameters()[0].ParameterName, ":int"), actionName);

    which at runtime contained the action name "Index". Since this is clearly not the action I expected or defined for the form I was confused. 

    Tracing back revealed this method:

    protected virtual IUrlParamsMapper GetDefaultParamsMapper(ControllerBase controller)
        IUrlParamsMapper urlParamsMapper = null;
        urlParamsMapper = urlParamsMapper.SetLast(this.GetInferredDetailActionParamsMapper(controller)).SetLast(this.GetInferredTaxonFilterMapper(controller, "ListByTaxon")).SetLast(this.GetInferredClassificationFilterMapper(controller, "ListByTaxon")).SetLast(this.GetInferredPagingMapper(controller, "Index"));
        if (urlParamsMapper != null)
            urlParamsMapper.SetLast(new DefaultUrlParamsMapper(controller));

    which as you can see hardcodes the value of "Index".

    If I'm not mistaken, it appears that Feather is first searching for this exact action name "Index", and if it is not found, then will search for the custom action name.

    This sounds very unusual to me, and the error was unexpected, especially since the error message had no reference to the "Index" method it was using instead of the method I specified, so it was only by accident  (and a lot of source hacking) that I stumbled on this.

    It turns out that indeed the original MVC widget has two index methods, one for GET and one for POST, and it is my suspicion that this is what was tripping it up.

    I confirmed this by changing the names of these methods on my controller from Index to Default, which immediately resolved the error!

    SO... my question is, what did I do wrong? Am I not supposed to use Index as an action name? why is the "Index" action passed as a default value for the controller action?

    I also found another that uses "Details" instead of "index" as the default method.. is this a similar situation? why is this done?

    Finally, am I the only one who has experienced this? If so surely I did do something wrong, because I can't imagine I'm the only one who uses "Index" as the default name for their controller actions...

    I'd appreciate any insight as I do like Feather so far, but if I'm using it wrong I definitely need to know! thanks

  • aha, so after reading further into Feather docs (this is still very new to me) it appears indeed this is a naming convention of the Feather platform:

    If you add specific methods to an MVC widget while feather in installed (such as Index(int? id) and Details(ContentItem item)) it will internally call these based on the routing to help you acheive those views. the same thing goes for filtering by category or tag:

    this is a bit of a "duh" moment for me as indeed it was just not me realizing this happened. Personally I don't like it, especially since it came as a complete surprise and I only stumbled on this by accident because the developer I was working with just by coincidence had the Index(int? id) action defined for an entirely different purpose.

    This is probably an edge case, but I'd still prefer to see these kinds of methods be "opt in" with perhaps an attribute like [Master] and [Details] (assuming such a thing is possible) so that you can still leverage these neat features, just not by accident :)

    sorry for the confusion on my part!