JQuery AJAX call not finding controller method

Topics: Customizing Orchard, Writing modules
Mar 26, 2012 at 2:43 PM
Edited Mar 26, 2012 at 2:45 PM

I'm trying to create a JQuery call that pulls down information from a Content Item's Part when selected in a list of the Content Items. When I have the JQuery AJAX call run, it returns an error "Not Found" and the Chrome console shows this error "POST http://localhost:30320/Item/DetailedExpert 404 (Not Found)"

Here's the Method I'm trying to call (the location of the Controller is ~/Modules/ClientName.MeetExperts/Controllers/ItemController.cs):

 

[HttpPost]
public ActionResult DetailedExpert(int id)
{
    var expert = _contentManager.Get(id).As<ExpertPart>();
    if (expert == null)
    {
          return HttpNotFound(T("Expert not found").Text);
    }
    var model = _contentManager.BuildDisplay(expert, "Detail");

    return new ShapeResult(this, model);
}

 

Here's the Route I've defined for the Method:

 

public IEnumerable<RouteDescriptor> GetRoutes()
{
     return new[] {
            new RouteDescriptor {
                    Priority = 15,
                    Route = new Route(
                        "Item/DetailedExpert",
                        new RouteValueDictionary {
                            {"area", "ExpertList"},
                            {"controller", "Item"},
                            {"action", "DetailedExpert"}
                        },
                        new RouteValueDictionary{
                            {"path", _ExpertPathConstraint}
                        },
                        new RouteValueDictionary {
                            {"area", "ExpertList"}
                        },
                        new MvcRouteHandler())
            }

     };
}

 

And here's the JQuery AJAX call:

 

var expertId = $(this).find('.content-id').attr('id');
$.ajax({
    type: "POST",
    dataType: "",
    url: "Item/DetailedExpert",
    data: {
        id: expertId, 
        __RequestVerificationToken: $("#__requesttoken").val()
    },
    success: function (response) {
        alert('success');
        $('.expert-content').append(data);
    },
    error: function (req, status, error) {
        alert(error);
    }
});

 

I'm thinking it has to do with the url that I'm sending. Any information would help.

Mar 26, 2012 at 3:56 PM

Try add a / before Item/DetailedExpert @ your javascript

Mar 26, 2012 at 4:49 PM

Unfortunately, that didn't fix it. Do you have any other ideas?

Developer
Mar 26, 2012 at 6:34 PM
Edited Mar 26, 2012 at 7:20 PM
  1. Is the module called "ExpertList"? The "area" name has to be the full module name.
  2. Have you tried removing the [HttpPost] attribute and checking in the browser whether the URL gets resolved?
  3. I assume your javascript is somewhere inside a view file, right (or is it a .js file)? If it's in the view - try using @Url.Action(...) instead of hardcoding "Item/DetailedExpert" url in ajax call.
Developer
Mar 26, 2012 at 6:37 PM

One more thing - could you paste the ExpertPathConstraint code? There might also be something wrong there that prevents the route from being resolved.

Mar 26, 2012 at 6:54 PM

pszmyd, I've tried 1 and 2 and it still isn't working. I have my JavaScript in a js file, but it could be moved into the view if you think it would make this easier.

Here's the updated Route: 

 

public IEnumerable<RouteDescriptor> GetRoutes()
{
     return new[] {
           new RouteDescriptor {
                 Priority = 15,
                 Route = new Route(
                      "Item/DetailedExpert",
                      new RouteValueDictionary {
                          {"area", "ClientName.MeetExperts"},
                          {"controller", "Item"},
                          {"action", "DetailedExpert"}
                      },
                      new RouteValueDictionary{
                           {"path", _ExpertPathConstraint}
                      },
                      new RouteValueDictionary {
                           {"area", "ClientName.MeetExperts"}
                      },
                      new MvcRouteHandler())
           }

     };
}

 

Here's the ExpertPathConstraint code, it's based off the ContainersPathConstraint from v1.3 so it might be outdated:

 

public interface IExpertPathConstraints : IRouteConstraint, ISingletonDependency
    {
        void SetPaths(IEnumerable<string> paths);
        string FindPath(string path);
        void AddPath(string path);
    }

    public class ExpertPathConstraints : IExpertPathConstraints
    {
        private IDictionary<string, string> _paths = new Dictionary<string, string>();

        public void SetPaths(IEnumerable<string> paths)
        {
            // Note: this does not need to be synchronized as long as the dictionary itself is treated as immutable.
            // do not add or remove to the dictionary instance once created. recreate and reassign instead.
            _paths = paths.Distinct().ToDictionary(path => path, StringComparer.OrdinalIgnoreCase);
        }

        public string FindPath(string path)
        {
            string actual;
            return _paths.TryGetValue(path, out actual) ? actual : path;
        }

        public void AddPath(string path)
        {
            SetPaths(_paths.Keys.Concat(new[] { path }));
        }

        public bool Match(System.Web.HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
        {
            if (routeDirection == RouteDirection.UrlGeneration)
                return true;

            object value;
            if (values.TryGetValue(parameterName, out value))
            {
                var parameterValue = Convert.ToString(value);
                return _paths.ContainsKey(parameterValue);
            }

            return false;
        }
    }

 

 

Coordinator
Mar 26, 2012 at 7:00 PM

Hard-coding the url into the script is extremely brittle and probably the origin of your problems. It's better to inject the right URL as generated by the server, using Url.Action, into a short bit of initialization client code on the page.

Developer
Mar 26, 2012 at 7:09 PM

Ah yes, I thought about Url.Action, but mistakenly suggested using Html.ActionLink:)

Mar 26, 2012 at 7:14 PM

bertrandleroy, taking your advice, I added this snippet to the view: 

<input id="__Action" type="hidden" value='@Url.Action("DetailedExpert","Item")' />
and changed my url in the JQuery call to:
url: $("#__Action").val(),
, but it still isn't working. However, the POSTing url is now http://localhost:30320/Contents/Item/DetailedExpert . 
Here's the view code:
@{
    IEnumerable<object> items = Model.ContentItems;
    var part = Model.ContentPart;
    Script.Include("jquery.simplemodal.js").AtFoot();
    Script.Include("basic.js").AtFoot(); // <- the JS file with the AJAX call
    Script.Include("pixastic.custom.js").AtFoot();
}
<div id="experts">
    <div id="expert-info">
        <h1>@Model.ContentPart.Record.Title</h1>
        @Html.Raw(@Model.ContentPart.Record.Body)
        <ul id="expert-nav">
            <li class="doctor-nav">
                <img src="Themes/ClientName/Content/img/expert-nav/doctor1.png" width="234" height="28"></li>
            <li class="wellness-nav">
                <img src="Themes/ClientName/Content/img/expert-nav/coach1.png" width="234" height="28"></li>
            <li class="trainer-nav">
                <img src="Themes/ClientName/Content/img/expert-nav/trainer1.png" width="234"
                    height="28"></li>
            <li class="nutrition-nav">
                <img src="Themes/ClientName/Content/img/expert-nav/nutrition1.png" width="234"
                    height="28"></li>
        </ul>
    </div>
    <div class="expert-content clearfix">
        <input id="__requesttoken" type="hidden" value="@Html.AntiForgeryTokenValueOrchard()" />
        <input id="__Action" type="hidden" value='@Url.Action("DetailedExpert","Item")' />
        @foreach (dynamic item in items)
        { 
            <a href="#" class="basic">
                <div class="content-id" id="@item.ContentItem.ExpertPart.Record.Id"></div>
                <div class="expert-container clearfix">
                    <div class="@item.ContentItem.ExpertPart.Record.AreaOfExpertise section-copy">
                        @if (item.ContentItem.ExpertPart.SmallImage.Url != null)
                        {
                            <img src="@Url.Content(item.ContentItem.ExpertPart.SmallImage.Url)" />   
                        }
                        else
                        {
                            <img src="Themes/ClientName/Content/img/experts/drone_color.jpg" />
                        }
                        <div class="@item.ContentItem.ExpertPart.Record.AreaOfExpertise ">
                        </div>
                        <div class="expert-name">@item.ContentItem.ExpertPart.Record.FirstName @item.ContentItem.ExpertPart.Record.LastName</div>
                    </div>
                </div>
            </a>
        }
    </div>
</div>

Developer
Mar 26, 2012 at 7:14 PM

Also, this part can be troublesome:

        public bool Match(System.Web.HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
        {
            if (routeDirection == RouteDirection.UrlGeneration)
                return true;

            object value;
            if (values.TryGetValue(parameterName, out value))
            {
                var parameterValue = Convert.ToString(value);
                return _paths.ContainsKey(parameterValue);
            }

            return false;
        }
Are you sure that the bolded part is returning correct values? I mean that the _paths variable contains correct entries? Look out for case-sensitivity. In your case it makes a difference.

Developer
Mar 26, 2012 at 7:19 PM
Edited Mar 26, 2012 at 7:20 PM

Always add "area" parameter in Url.Action. It now gets you incorrect URL because it falls back to the default routes defined for controller Core/Contents/ItemController.cs.

@Url.Action("DetailedExpert","Item", new { area = "ClientName.MeetExperts"})
Mar 26, 2012 at 7:34 PM

pszmyd, adding the area in Url.Action made the AJAX call work! Now I just need to figure out how to get the right content shape. Thanks to you, bertrandleroy, and AimOrchard for helping me out.