views:

540

answers:

4

This is a bit of a continuation of a previous question.

Now I'm trying to make a call to an AJAX enabled web service which I have defined within the ASP.NET MVC application (i.e. the MovieService.svc). But the service is never being called in my getMovies javascript function.

This same technique of calling the AJAX web service works ok if I try it in a non ASP.NET MVC application, so it makes me wonder if maybe the ASP MVC routes are interfering with things somehow when it tries to make the AJAX web service call.

Do you have any idea why my web service isn't getting called? Code below.

    <script src="<%= ResolveClientUrl("~/scripts/jquery-1.4.2.min.js") %>" type="text/javascript"></script>

    <script src="<%= ResolveClientUrl("~/scripts/grid.locale-en.js") %>" type="text/javascript"></script>

    <script src="<%= ResolveClientUrl("~/scripts/jquery-ui-1.8.1.custom.min.js") %>"
        type="text/javascript"></script>

    <script src="<%= ResolveClientUrl("~/scripts/jquery.jqGrid.min.js") %>" type="text/javascript"></script>

    <script type="text/javascript">
        var lastsel2;

        function successFunction(jsondata) {
            debugger
            var thegrid = jQuery("#editgrid");
            for (var i = 0; i < jsondata.d.length; i++) {
                thegrid.addRowData(i + 1, jsondata.d[i]);
            }
        }

        function getMovies() {
            debugger
            // ***** the MovieService#GetMovies method never gets called
            $.ajax({
                url: 'MovieService.svc/GetMovies',
                data: "{}",  // For empty input data use "{}",
                dataType: "json",
                type: "GET",
                contentType: "application/json; charset=utf-8",
                success: successFunction
            });
        }

        jQuery(document).ready(function() {
            jQuery("#editgrid").jqGrid({
                datatype: getMovies,
                colNames: ['id', 'Movie Name', 'Directed By', 'Release Date', 'IMDB Rating', 'Plot', 'ImageURL'],
                colModel: [
                  { name: 'id', index: 'Id', width: 55, sortable: false, hidden: true, editable: false, editoptions: { readonly: true, size: 10} },
                  { name: 'Movie Name', index: 'Name', width: 250, editable: true, editoptions: { size: 10} },
                  { name: 'Directed By', index: 'Director', width: 250, align: 'right', editable: true, editoptions: { size: 10} },
                  { name: 'Release Date', index: 'ReleaseDate', width: 100, align: 'right', editable: true, editoptions: { size: 10} },
                  { name: 'IMDB Rating', index: 'IMDBUserRating', width: 100, align: 'right', editable: true, editoptions: { size: 10} },
                  { name: 'Plot', index: 'Plot', width: 150, hidden: false, editable: true, editoptions: { size: 30} },
                  { name: 'ImageURL', index: 'ImageURL', width: 55, hidden: true, editable: false, editoptions: { readonly: true, size: 10} }
                ],
                pager: jQuery('#pager'),
                rowNum: 5,
                rowList: [5, 10, 20],
                sortname: 'id',
                sortorder: "desc",
                height: '100%',
                width: '100%',
                viewrecords: true,
                imgpath: '/Content/jqGridCss/redmond/images',
                caption: 'Movies from 2008',
                editurl: '/Home/EditMovieData/',
                caption: 'Movie List'
            });

            $("#bedata").click(function() {
                var gr = jQuery("#editgrid").jqGrid('getGridParam', 'selrow');
                if (gr != null)
                    jQuery("#editgrid").jqGrid('editGridRow', gr, { height: 280, reloadAfterSubmit: false });
                else
                    alert("Hey dork, please select a row");
            });            

        });

    </script>

    <h2>
        <%= Html.Encode(ViewData["Message"]) %></h2>
    <p>
        To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">
            http://asp.net/mvc&lt;/a&gt;.
    </p>
    <table id="editgrid">
    </table>
    <div id="pager" style="text-align: center;">
    </div>
    <input type="button" id="bedata" value="Edit Selected" />

Here's my RegisterRoutes code:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.IgnoreRoute("*MovieService.svc*");

    routes.MapRoute(
        "Default",                                              // Route name
        "{controller}/{action}/{id}",                           // URL with parameters
        new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
    );
}

Here's what my MovieService class looks like:

namespace jQueryMVC
{
    [ServiceContract(Namespace = "")]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class MovieService
    {
        // Add [WebGet] attribute to use HTTP GET
        [OperationContract]
        [WebGet(ResponseFormat = WebMessageFormat.Json)]
        public IList<Movie> GetMovies()
        {
            return Persistence.GetMovies();
        }

    }
}
A: 

That is because the registered route in the global.asax, will not be recognizing this .svc file. it will try to search for that controller with the action getmovies and will fail. try out debugging using firebug. you can fix this by ignoring this route in the global.asax

Sundararajan S
It made no difference. I added routes.IgnoreRoute("*MovieService.svc*"); to the RegisterRoutes, and also tried it with routes.IgnoreRoute("MovieService.svc/GetMovies"). Neither worked.
dcp
did u try accessing the service from browser?
Sundararajan S
From what I can tell it works ok from the browser. Of course, we can't invoke WCF web methods from browser (unlike asmx web methods).
dcp
+5  A: 

Your main problem is that you use not absolute URLs in the ajax call. Wrong entries in web.config can make also problems. Moreover you use datatype: getMovies instead of datatype: 'json' and postData: yourData. The way with datatype as functions exist (see http://www.trirand.com/jqgridwiki/doku.php?id=wiki:retrieving_data#function), but since jqGrid 3.6.5 you have more direct way inside of jsonReader to read the data returned from web server.

UPDATED: It seems to me that describing of editing features I'll make later and explain here just how to get JSON data and fill there inside of jqGrid.

First of all jqGrid can request itself the JSON data from the server. So we don’t need to make a separate jQuery.ajax call. You need only define a URL which point to the server and define some additional jQuery.ajax parameters which you prefer. You don’t post in your question the definition of the Movie class. So I define it myself like following

public class Movie {
    public int Id { get; set; }
    public string Name { get; set; }
    public string Director { get; set; }
    public string ReleaseDate { get; set; }
    public string IMDBUserRating { get; set; }
    public string Plot { get; set; }
    public string ImageURL { get; set; }
}

You should remark, that Microsoft serialize DataTime type not as a readable date string but as a string /Date(utcDate)/, where utcDate is this number (see http://stackoverflow.com/questions/2794633/jquery-param-doesnt-serialize-javascript-date-objects/2794836#2794836). To make fewer problems at the beginning I define ReleaseDate as string.

Method IList<Movie> GetMovies() returns JSON data like an array of objects Movie. So jqGrid as a response to HTTP GET request receive from the MovieService.svc/GetMovies URL the data like following:

 [{"Id":1, "Name": "E.T.", "Director": "Steven Spielberg",...},{...},...]

I can say that it is not typical format of data, which are waiting jqGrid (compare with http://www.trirand.com/jqgridwiki/doku.php?id=wiki:retrieving_data#json_data). To be able to place the data inside of jqGrid we must define a jsonReader. So we do following

jQuery("#editgrid").jqGrid({
    url: '<%= Url.Content("~/MovieService.svc/GetMovies")%>',
    datatype: 'json',
    ajaxGridOptions: { contentType: "application/json" },
    jsonReader: { repeatitems: false, id: "Id", root: function(obj) { return obj; }},
    headertitles: true,
    sortable: true,
    colNames: ['Movie Name', 'Directed By', 'Release Date',
               'IMDB Rating', 'Plot', 'ImageURL'],
    colModel: [
        { name: 'Name', width: 250},
        { name: 'Director', width: 250, align: 'right' },
        { name: 'ReleaseDate', width: 100, align: 'right' },
        { name: 'IMDBUserRating', width: 100, align: 'right' },
        { name: 'Plot', width: 150 },
        { name: 'ImageURL', width: 55, hidden: true }
    ],
    pager: jQuery('#pager'),
    pginput: false,
    rowNum: 0,
    height: '100%',
    viewrecords: true,
    rownumbers: true,
    caption: 'Movies from 2008'
}).jqGrid('navGrid', '#pager', { add: false, edit: false, del: false, search: false });

REMARK: I remove from the example any sorting parameters, because in case of request of JSON data, the sorting parameter will be only send to server (some additional parameters append the server URL) and server must give back sorted data. For more information see description of prmNames parameter on http://www.trirand.com/jqgridwiki/doku.php?id=wiki:options and description of sopt parameter on http://www.trirand.com/jqgridwiki/doku.php?id=wiki:singe_searching.

With respect of datatype: 'json' we define dataType: 'json' parameter of jQuery.ajax (don’t confuse the case inside of datatype parameter). The names of all fields inside of colModel we define exact the same as the field names inside our JSON objects. Some additional parameters viewrecords, rownumbers, sortable and headertitles are not very important in this example, I choosed there because 1) I like there and 2) I set rowNum: 0 to make possible the options rownumbers: true works correct and not show us negative row numbers started with -5 if rowNum: 5 like in your original example.

With ajaxGridOptions: { contentType: "application/json" } we define additional parameters which will be direct forwarded to the jQuery.ajax.

The most complex part of this example is

jsonReader: { repeatitems: false, id: "Id", root: function(obj) { return obj; }}

It defines that id of all rows have the name "Id" (see definition of the class Movie). "repeatitems: false" say that every data field we want identify by the field name (defined in colModel) instead of default definition per position. The definition of root is a little strange, but it defines how to find the root of rows inside of JSON data. Default format of JSON data is following

{
  total: "xxx", 
  page: "yyy", 
  records: "zzz",
  rows : [
    {id:"1", cell:["cell11", "cell12", "cell13"]},
    {id:"2", cell:["cell21", "cell22", "cell23"]},
      ...
  ]
}

and the root of rows are defined as root: "rows". So if the JSON data assigned to the variable res, the root can be returned as res.rows. To allow jqGrid to read our data we define jsonReader.root as a function (this feature exist since jqGrid 3.6.5 see http://www.trirand.com/jqgridwiki/doku.php?id=wiki:change#additions_and_changes). You can verify that this strange method work. The typical additional parameters page, total (lastpage) and records are not exist inside of our JSON data and they will be initialized as following page:0, total:1, records:0. So we are not able to make data paging. You can expand jsonReader with functions defining page, total and records (also as functions) like

jsonReader: {
    repeatitems: false,
    id: "Id",
    root: function (obj) { return obj; },
    page: function (obj) { return 1; },
    total: function (obj) { return 1; },
    records: function (obj) { return obj.length; }
}

which will complete our jsonReader. Then setting of rowNum: 0 will not more needed.

I showed this way only to show the flexibility of jqGrid. You should use described way only if you access a web server which you cannot change. jqGrid has features like paging, sorting and two kind of searching (more as filtering with WHERE in the corresponding SELECT) of data: simple and advanced. If we want have these nice features inside of jqGrid on our web pages we should define in Web Service an additional method like

[OperationContract]
[WebGet(ResponseFormat = WebMessageFormat.Json,
        UriTemplate = "jqGridGetTestbereiche?_search={_search}&page={page}&"+
                      "rows={rows}&sidx={sortIndex}&sord={sortDirection}&"+
                      "searchField={searchField}&searchString={searchString}&"+
                      "searchOper={searchOper}&filters={filters}")]
public jqGridTable jqGridGetMovies(
  int page, int rows, string sortIndex, string sortDirection,
  string _search, string searchField, string searchString,
  string searchOper, string filters)

where jqGridTable

public class jqGridTable
{
    public int total { get; set; }      // total number of pages
    public int page { get; set; }       // current zero based page number
    public int records { get; set; }    // total number of records
    public List<jqGridRow> rows { get; set; }
}
public class jqGridRow
{
    public string id { get; set; }
    public List<string> cell { get; set; }
}

Or if we want use the most compact form of data transferred from server to client then

// jsonReader: { repeatitems : true, cell:"", id: "0" }
public class jqGridTable {
    public int total { get; set; }          // total number of pages
    public int page { get; set; }           // current zero based page number
    public int records { get; set; }        // total number of records
    public List<List<string>> rows { get; set; }// first element in every row must be id of row.
}

(you can read more about this kind of data transfer on http://www.trirand.com/blog/jqgrid/jqgrid.html if you choose on the left tree part "Data Mapping" and then "Data Optimization")

P.S.: About jsonReader you can read more on http://www.trirand.com/jqgridwiki/doku.php?id=wiki:retrieving_data#json_data. One my old answer http://stackoverflow.com/questions/2690657/mapping-json-data-in-jqgrid/2706620#2706620 can be also interesting for you.

UPDATED 2: Because you don't mark the answer as accepted, you stay have some problems. So I created a new Project in Visual Studio 2010 which demonstrate what I written. You can download the source from http://www.ok-soft-gmbh.com/jqGrid/jQueryMVC.zip. Compare with your project, especially the part with full url as a parameter of jqGrid and a part of web.config which describes WCF service interface.

UPDATED 3: I use VS2010 not so long time. So I could very quickly downgrade this to VS2008. So almost the same code working working in Visual Studio 2008, but with ASP.NET MVC 2.0 you can download from http://www.ok-soft-gmbh.com/jqGrid/VS2008jQueryMVC.zip. The code in ASP.NET MVC 1.0 should be the same, but a GUID from the project file and some strings from Web.config should be patched (see http://www.asp.net/learn/whitepapers/aspnet-mvc2-upgrade-notes).

Oleg
@Oleg - Yes, I am new at jqGrid but am trying hard to learn it, but it's very difficult to use and the examples are poor. I don't really understand your answer though. I already had this working with using datatype: 'json' and letting control flow to the controller. I was trying to get it to work with a call to the web service. That works in my non MVC project, but it doesn't work in MVC, which seems to indicate that MVC is messing things up somwhere.
dcp
@Oleg - I should qualify what I meant when I said the examples are poor. The JQGrid examples themselves are very good, but they are all in php, they don't have any in asp.net. I guess this is due to the fact that they want you to buy the commercial controls :).
dcp
I use jqGrid in a project written in ASP.NET MVC with a RESTfull WCF service which is a part of the same ASP.NET site. So we use the same technique. I play a little with my daughter and post you an example
Oleg
@Oleg - Thanks for the great help. I'm going to be trying out your solution very soon, I just have not had a chance yet because I've been busy with some other projects. I will let you know what I find. Again, I appreciate the detailed explanation.
dcp
@Oleg - Is this a VS 2010 solution? I only have VS 2008 so I cannot open the solution file :(. Anyway, you've spent enough time on this, I'll go ahead an accept the answer so you get your rep points.
dcp
@Oleg - Regarding Updated 3, thanks, I was able to run it just fine after I installed MVC 2.0. I think I get the basic idea of it. The other examples I looked at actually handled the paging by getting the parameters inside the controller method, like this: string sortColumn = (Request.Params["sidx"]);. One other question, you ever used jqGrid in non-ASP.net application with just regular web forms? I didn't find easy way to do such a thing, I guess it's geared more towards MVC pattern. Thanks again!
dcp
A: 

I encountered the same issue. I came to the conclusion that the routes were interfering with the service call. Have you tried Phil Haack's Route Debugger? It's saved my bacon a couple times.

In the ended, I created an endpoint off one of the controllers.

Chuck Conway
Yes, I just got through trying the route debugger and the route matched. As I said in my response to Oleg, I think MVC just hoses things up somewhere when trying to call the web service. Nice to know someone else had the same problem and that I'm not alone :).
dcp
A: 

Oleg,

Do you have the example you were talking about as i am working with jqgrid/asp.net mvc and a restful service and having a bugger of a time. It would help out seeing an example as i am at a wall. Thx

SEM