代码改变世界

转载:Using JQuery with an ASP.NET webservice

2008-10-08 22:51  Koy  阅读(517)  评论(0编辑  收藏  举报
Using JQuery with an ASP.NET webservice
Using JQuery to call ASP.NET web services/loading with AJAX

转载自:http://www.sloppycode.net/articles/using-jquery-with-aspnet-web-services.aspx

An introduction
This article assumes you know how to make a web service, and are familiar with the scriptmanager/asp.net (atlas) approach to ajax, and know what JQuery is. Basically it's not a beginners' walkthrough for the 3. 

I've written this on the back of developing my own web-based RSS reader project I called Soot (because it sweeps RSS feeds, really bad I know, probably why I'm not in marketing). Another RSS reader you're thinking, not exactly ground-breaking I know. The point of it is to retrieve a set number of popular feeds, make their content easily readable with a simple layout, and allow AND style filtering on categories or domains. In the process of making it I moved to a dynamic loading method from which I discovered how to bypass ASP.NET's Atlas or Ajax framework, the Scriptmanager and updatepanels and dynamically load content in with a web service and JQuery's Ajax API. I thought I'd share my experience as some parts of it aren't well documented out there, they exist as scraps on blogs and forums. 

The articles includes: - Loading data dynamically through jquery and an asp.net web services
- Sending the data to the web service
- Parsing the XML data returned from the web service
- Adding a loading animation to indicate work is being done
- Loading data as you scroll down the page


I initially set about writing my own Ajax enabled ASP.NET control in order to dynamically load feeds in Soot, but quickly realised the learning curve of developing these was too steep for what I required. I'm sure they're as powerful or even more powerful than a plain web service and JQuery, but I was after a fast solution and being very familiar with JQuery I prefered this route. 

JQuery is intuitive, has a very active and chatty community, features a host of plugins and plenty of documentation and is also lightweight. Using it for async (ajax/xmlhttp) requests is also very simple as I found out. I'll admit upfront that I hate Javascript (I know I'm not alone in this). Even with Microsoft and Google's efforts to make it more predictable and faster to use, is still a painful developer experience. This isn't because I'm a newbie to Javascript, I've been using it since version 1.3 and remember the desk-pounding, keyboard-headbutting days of writing for Netscape prior to "web 2.0". Because debugging it so fiddly and almost always results in alerts() or a debug div, and it being a strange mix of a prototyped,OO and loosely typed language that writing anything that isn't basic is hard work. Fortunately we now have lots of well written libraries stuck on top of Javascript taking most of the misery away. But that's enough Javascript ranting.. 

Getting started
Here's what is needed to get JQuery working with ASP.NET and calling a webservice to load data dynamically. 

* Minor web.config tweak to get web services working with Jquery. * A web service
* The latest JQuery .js file.
* A webpage with JQuery included
* Call the webservice
* Parse the items returned by the webservice


Nothing overly complex there. I could probably have improved the integration further by making the JQuery .js file an embedded resource in your ASP.NET application if, but I've skipped that step for now. 


The web.config tweak
The first step before doing anything is to configure your web service calls so that JQuery can post to them. This is done with the following: 

  1. <system.web>                  
  2.   <protocols>  
  3.     <add name="HttpGet"/>  
  4.     <add name="HttpPost"/>  
  5.   </protocols>  
  6. </system.web>  
If you don't do this, you receive an error message when you call the webservice in JQuery: 

"Request format is unrecognized for URL unexpectedly ending in jquery" 


The webservice
The webservice doesn't require any special changes to it. Simply create your webservice, add a new method with [WebMethod] added and return whatever needs to be returned. Here's my webservice, with some code shortened to leave out the non essential bits: 

  1. [WebService(Namespace = "http://tempuri.org/")]  
  2. [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]  
  3. [System.ComponentModel.ToolboxItem(false)]  
  4. public class GetFeeds : System.Web.Services.WebService  
  5. {  
  6.     [WebMethod]  
  7.     public FeedItemSummary[] FilterFeeds(string arg1,string arg2)  
  8.     {  
  9.         // Load a list of feeds based on the arguments  
  10.         ...  
  11.   
  12.         // Turn into HTML friendly format  
  13.         List<FeedItemSummary> summaryList = new List<FeedItemSummary>();  
  14.         foreach (FeedItem item in list)  
  15.         {  
  16.             FeedItemSummary summary = new FeedItemSummary();  
  17.             summary.Title = item.Title;  
  18.             summary.PublishDate = item.PublishDate.ToString("dddd dd MMM yyyy");  
  19.             summary.Content = item.Html;  
  20.             summary.Hash = item.Hash;  
  21.             summary.FeedImage = item.ImageTag;  
  22.             summary.Url = item.Url;  
  23.             summary.TldName = "etc...";  
  24.             summaryList.Add(summary);  
  25.         }  
  26.   
  27.         return summaryList.ToArray();  
  28.     }  
  29. }  
Calling the webservice with JQuery
Now the web service is complete, the actual jquery work can be started. There's a few ways to make Ajax calls in JQuery, the one we're interested in the $.ajax() method or function. This function takes a set of config options as with a lot of JQuery calls. The settings that are important for ASP.NET web services are: 

- It needs to be a POST, for this example anyway
- processData is false
- dataType is xml
- Make sure you give it an error function to call, this is extremely useful for finding out where things are going wrong - on the client or server.
- The URL format is "webservice.asmx/MethodName"


Here's one I'm using for the Soot application: 

  1. $.ajax({ type: "POST",  
  2.     url: "WebService.asmx/MyMethod",  
  3.     dataType: "xml",  
  4.     data: "arg1=" +arg1+ "&arg2=" +arg2,  
  5.     processData: false,  
  6.     error: function(XMLHttpRequest, textStatus, errorThrown) { ajaxError(XMLHttpRequest,textStatus, errorThrown); },  
  7.     success: function(xml) { ajaxFinish(xml); }  
  8. });  


So you can see a couple of things here. First is the data (the parameters of the webservice method) are passed in, in querystring format. Secondly the error function delegate passes the XML object, status and the error to its error handling function. Lastly the success function, the one called if it all works, gets passed a XMLHttpRequest object, which I've called 'xml'. So as well as the call above, you would have two functions: 

  1. function ajaxFinish(xml)  
  2. {  
  3.     // parse the object  
  4. }  
  5.   
  6. function ajaxError(xmlObj,textStatus,errorThrown)  
  7. {  
  8.     // Comment this out for live environments, and put a friendly error message  
  9.     alert("(Ajax error: "+txt+")");  
  10.     alert(request.responseText);  
  11. }  
Parsing the XML
This was the part of the whole process I struggled with the most. Fortunately some blogs were at hand to help, this one was particularly helpful, as was this one

The fastest way to figure out what you're getting back in your ajaxFinish(xml) function is to simply do alert(xml). This will display the SOAP XML returned from your webservice. There is infact a SOAP parser plugin for JQuery which I haven't used, but could be incorporated with this to parse the returned XML. For my needs, I've used JQuery's inbuilt XML DOM parser to get at the tags I need. The 'xml' parameter is an XMLHTTPRequest object but you don't have to worry about using this, as JQuery can take this type and do its magic allowing you to find tags within it. 

Once you have worked out the format of the document returned, you can use alert($(xml).find("YourTag").children().length) to see if it's retrieving the XML elements ok. 

Here's an example I've stripped from Soot that parses the following XML format: 

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">  
  3.   <soap:Body>  
  4.     <FilterFeedsResponse xmlns="http://tempuri.org/">  
  5.       <FilterFeedsResult>  
  6.         <FeedItemSummary>  
  7.           <Title>string</Title>  
  8.           <TldName>string</TldName>  
  9.           <Content>string</Content>  
  10.           <PublishDate>string</PublishDate>  
  11.           <Hash>int</Hash>  
  12.           <FeedImage>string</FeedImage>  
  13.           <Url>string</Url>  
  14.         </FeedItemSummary>  
  15.         <FeedItemSummary>  
  16.           <Title>string</Title>  
  17.           <TldName>string</TldName>  
  18.           <Content>string</Content>  
  19.           <PublishDate>string</PublishDate>  
  20.           <Hash>int</Hash>  
  21.           <FeedImage>string</FeedImage>  
  22.           <Url>string</Url>  
  23.         </FeedItemSummary>  
  24.       </FilterFeedsResult>  
  25.     </FilterFeedsResponse>  
  26.   </soap:Body>  
  27. </soap:Envelope>  


This is formed from returning a FeedItemSummary[] array. FeedItemSummary is no more complex than the XML above shows (FeedItemSummary is actually a projected object from NHibernate): 

  1. public class FeedItemSummary  
  2. {  
  3.     public string Title {get;set;}  
  4.     public string TldName {get;set;}  
  5.     public string Content {get;set;}  
  6.     public string PublishDate {get;set;}  
  7.     public int Hash {get;set;}  
  8.     public string FeedImage {get;set;}  
  9.     public string Url {get;set;}  
  10. }  


My advice is to keep your returned objects in a format that is friendly to Javascript, so stick to primitive types and don't use DateTimes or other .NET specialities, keep to strings,ints,bools, doubles. Unless you enjoy writing 5x more lines of JQuery to get at the tags. 

In parsing the XML I'm only interested in each FeedItemSummary. My parsing code builds up some dynamic HTML from each item, adding it to the DOM of page. This makes the initial loading of the page very fast, adding chunks of 10 items each time you scroll. Here's the JQuery: 

  1. function ajaxFinish(xml)  
  2. {  
  3.     $("FeedItemSummary", xml).each(function()  
  4.     {  
  5.         url = $("Url"this).text();  
  6.         title = $("Title"this).text();  
  7.         tldName = $("TldName"this).text();  
  8.         content = $("Content"this).text();  
  9.         publishDate = $("PublishDate"this).text();  
  10.         hash = $("Hash"this).text();  
  11.         feedImage = $("FeedImage"this).text();  
  12.       
  13.                 // Add the title with a named anchor  
  14.         $("#feedcontainer").append('<div class="feedItem"><a name="' + hash + '"></a>');  
  15.                 $("#feedcontainer").append('<a href="' + url + '" target="_blank" class="title">' + title + '</a><br/>');  
  16.         // etc.  
  17.     });  
  18. }  


Ignoring the structure of the page, it simply gets the text of each property of a FeedItemSummary and uses it to append to the #feedcontainer div. 

Adding some polish
The loading animation One UI aspect you will be keen to include is a loading animation. A kind hourglass cursor except it's obviously now a hypnotic circle that rotates. There's lots of free animations you can use for this at http://www.ajaxload.info/ which allows you to configure the background colour and foreground colour of it. 

It's fairly trivial to add this loading animation in JQuery, you simple add a hidden div to your pay with this animation in, and toggle its display. So before your $.ajax call you toggle the div (I've called #loading): 

$("#loading").toggle();
$.ajax(...);


then inside either the ajaxFinish() or ajaxError() functions you once again call it: 

  1. function ajaxFinish(xml)  
  2. {  
  3.     $("#loading").toggle();  
  4.       
  5.     ...  
  6. }  
  7.   
  8. function ajaxError(xmlObj,textStatus,errorThrown)  
  9. {  
  10.     $("#loading").toggle();  
  11.   
  12.     ....  
  13. }  
  14. </textare>  
  15.   
  16. <b>Loading data while scrolling the page.</b>  
  17. The items for each category/website in Soot don't all load when the page loads but instead get paged 10 items at a time. This happens when you scroll the page down and is something you find fairly frequently on websites now, one obvious example is Google Reader.  
  18. <br/><br/>  
  19.   
  20. I won't take any credit for this, it was taken entirely from <a href="http://www.webresourcesdepot.com/load-content-while-scrolling-with-jquery/" target="_blank">this post</a>.  
  21. <br/><br/>  
  22.   
  23. I've simply tied in this example to increment the page by 1 each time the scrollbar hits the bottom of the page, like so:  
  24. <br/><br/>  
  25.   
  26. <textarea name="code" class="jscript">  
  27. function onScroll()  
  28. {  
  29.     if (_enableDynamicLoad && !_itemsComplete)  
  30.     {  
  31.         if ($(window).scrollTop() == $(document).height() - $(window).height())  
  32.         {  
  33.             _currentPage++;  
  34.             filterFeeds(); // calls the webservice to get the next page of 10 items  
  35.         }  
  36.     }  
  37. }  


This dynamic loading can be turned on and off so that mobile-based browsers such as Safari on the IPod/IPhone or IE on Windows Mobile can load the entire feed list, then disconnect from the web to read them offline. 

Finally
I'm still testing Soot so it will have some bugs to iron out over the next month. The UI for the filtering is getting an overhaul soon and the feed HTML cleaning code is gradually getting worked on. You will notice it has no ability to add your own feeds. This is deliberate, as the point of it is to take feeds from popular sources that have good content, and provide combined filtering on them. I'm also planning to have a new feature to email the latest 5-10 feeds from each category or website to you twice a day, a bit like a mailing list summary. 

That's about it, hopefully this article has helped with you getting JQuery and ASP.NET/Ajax working together.

Kant
Wed Oct 08 2008
thx for your article, but the first step your code is wrong, the right is below: 
 
<system.web> 
<webServices> 
<protocols> 
<add name="HttpGet"/> 
<add name="HttpPost"/> 
</protocols> 
</webServices> 
</system.web>]