Ajax中使用json这个轻量级数据类型通信的好处相信大家已经很清楚,考虑到安全问题,ASP.NET Ajax的webService使用json,应该防止Json Hijacking。因此通常我们的做法是在使用Post请求式,并将请求的content-type设置成application/json; charset=utf-8。但客户端如果你使用的是JQuery,有三个细节问题是我们应该注意的:
1 :如果我们Post时没有任何数据传给服务端,请指定Data:{} 如:
Code
1 $.ajax({
2 type: "POST",
3 url: "PageMethod.aspx/PageMethodName",
4 data: "{}",//注意这里不可省。
5 contentType: "application/json; charset=utf-8",
6 dataType: "json"
7 });
这是因为在IIS中post请求时Content —Length是必须提供的,即使没用任何Post Data.Content-Length也应该设为0,但这样的话JQuery不会自动设置Header,除非请求中包含post Data。而ASP.NET Ajax的json传输,又要求post方式,因此我们不能改变他的请求方式。简便的解决法案就是在请求中给定一个空的json 对象。以符合IIS的要求,此时Content-Length为2。这时在服务端我可以完全忽略这个为空的参数,并处理相应的请求。
2:当post data 不为空时。我们应该避免在beforeSend事件里设置RequestHeader。
如一点所述的范例post data 为空时,既然JQuery不能自动设置Header,我们能否手工帮他设置呢?答案时是
肯定的。这时我们是在beforeSend事件中设置的。如代码所示(请注意:必须设置为application/json否则webservice
时不会返回json。这也是出于安全的考虑)。
Code
1 $.ajax({
2 type: "POST",
3 url: "WebService.asmx/WebMethodName",
4 beforeSend: function(xhr) {
5 xhr.setRequestHeader("Content-type",
6 "application/json; charset=utf-8");
7 },
8 dataType: "json"
9 });
但时此时问题又来了,在IE中XmlHttpRequst的setRequestHeader不时直接完全的设置RequstHeader。而是
在已有的基础上附加setRequestHeader参数所指定的以形成新的header。这样的话,在jQuery会在包含Post Data请求
的header中自动设置content-type为默认的application/x-www-form-urlencoded ,而在beforeSend又会被重新追
加一个application/json; charset=utf-8。此时Content-Type变成了 application/x-www-form-urlencoded,
application/json; charset=utf-8(在ff会重新设置)。很显然这个content-type不为ASP.NET Ajax所接受。webservice
不会返回任何json。因此包含post data时建议使用下列方式保证返回的为json
Code
$.ajax({
type: "POST",
url: "WebService.asmx/WebMethodName",
data: "{'fname':'dave', 'lname':'ward'}",
contentType: "application/json; charset=utf-8",
dataType: "json"
});
3:注意区分json对象和json形式的字符串。
看看下面代码:
Code
$.ajax({
type: "POST",
url: "WebService.asmx/WebMethodName",
data: {'fname':'dave', 'lname':'ward'},
contentType: "application/json; charset=utf-8",
dataType: "json"
});
咋一看没什么问题,想当然的认为data会乖乖的post给服务端。但实际上这是不正确的。请仔细看此时data时json
表示的对象。此时Json表示的对象会被jQuery序列化。如上述例子data将是fname=dave&lname=ward。再看如下:
Code
1 $.ajax({
2 type: "POST",
3 url: "WebService.asmx/WebMethodName",
4 data: "{'fname':'dave', 'lname':'ward'}",
5 contentType: "application/json; charset=utf-8",
6 dataType: "json"
7 });
这时data才是我们期望的。其形式:{'fname':'dave', 'lname':'ward'}。小小语义的改变就可以造成如此大的不同。
因此,我们在编程过程中应该特别注意。以免浪费时间。
注:有关细节可以参看3 mistakes to avoid when using jQuery with ASP.NET AJAX ,有更详细的说明。我只是
重新用中文简要的重述一下其中讲到的问题。
原文出处:http://www.cnblogs.com/mingxuan
As I was looking around for ways to enable Ajax in the current ASP.NET MVC release I found a couple of things that I found pretty useful and thought I should share.
The J in Ajax (JQuery)
The muscle behind the actual asynchronous calls comes from JavaScript. I looked around at a bunch of existing JavaScript libraries and settled on JQuery because of the way it leverages existing CSS knowledge. The three things that the library should do easily are:
- Help me easily display an "updater" to let user know something is happening (i.e. loading data),
- Assist in making the actual Ajax call without any hassle,
- and, most importantly, let me get 1 and 2 working without headaches.
Here is how JQuery helps in the three cases:
- The "updater":
$('#updater').show();
$('#updater').hide();
Notice the way jQuery uses the standard CSS id selector. Could it be any easier? - jQuery has the following Ajax calls available in its library:
object.load( )
$.get( )
$.post( )
$.getJSON( )
This takes away (hides) all of the XmlHttp object nonsense from the whole Ajax call. - See 1 and 2
ASP.NET MVC
There are tons of good posts/tutorials on exactly how the ASP.NET MVC model works so I will not attempt to get into it too much here. The most important thing to know is that there are three things working together:
- The Controller,
- The Model, and
- The View
The controller handles all of the requests, asks the model for data, and then instructs the view to present the data (if any) returned.
Routes
One of the neat things about the MVC framework is the notion of routes. The default route setup is as follows:
1: routes.MapRoute(
2: "Default", // Route name
3: "{controller}/{action}/{id}", // URL with parameters
4: new { controller = "Home", action = "Index", id = "" } // Parameter defaults
5: );
This simply means that, from the root of your web app, whenever a URL of the form http://root/foo/baz/bar is presented, the routing engine will call the foo controller with the baz action while supplying it with the bar id.
Some Code
Enough explanation, now to some code!
The Model
Since this is not an exercise in using the database, I created a simple Model class for students:
1: public class Student
2: {
3: public string FirstName { get; set; }
4: public string LastName { get; set; }
5: public int StudentId { get; set; }
6:
7: public static IQueryable<Student> GetStudentDataList()
8: {
9: return new List<Student>()
10: {
11: new Student { FirstName = "John", LastName = "Smith", StudentId = 1},
12: new Student { FirstName = "Susan", LastName = "Connor", StudentId = 2},
13: new Student { FirstName = "Bryan", LastName = "Jones", StudentId = 3},
14: new Student { FirstName = "Lucy", LastName = "Vargas", StudentId = 4},
15: new Student { FirstName = "Robert", LastName = "Jimenez", StudentId = 5},
16: new Student { FirstName = "Seth", LastName = "Juarez", StudentId = 6},
17: new Student { FirstName = "David", LastName = "Meeks", StudentId = 7},
18: new Student { FirstName = "Olivia", LastName = "Rassmusen", StudentId = 8},
19: new Student { FirstName = "Viola", LastName = "Masterson", StudentId = 9},
20: new Student { FirstName = "Russel", LastName = "Jones", StudentId = 10}
21: }
22: .AsQueryable<Student>();
23: }
24: }
This simply creates a new list of Students and returns them as an IQueryable.
The Controller
Since I want to only pass JSON serialized objects over the wire when making the Ajax calls, there is a handy return type for the action in the controller called JsonResult:
1: public JsonResult Find(string name)
2: {
3: // To simulate "wait"
4: System.Threading.Thread.Sleep(1500);
5:
6: return new JsonResult
7: {
8: Data = (from student in Student.GetStudentDataList()
9: where student.LastName.StartsWith(name)
10: select student).ToArray<Student>()
11: };
12: }
This will serialize the data out in the JSON format. Now for the actual interesting part:
The View
The html for this example is VERY simple:
1: <div id="query">
2: <%= Html.TextBox("textSearch") %>
3: <a href="#" id="linkFind">Find</a>
4: <span class="update" id="updater">
5: <img src="<%= AppHelper.ImageUrl("indicator.gif") %>" alt="Loading" />
6: Loading...
7: </span>
8: </div>
9: <div class="label">Students:</div>
10: <div id="studentList"></div>
There is a search box, a link, and update panel, and a div (studentList) that will be populated by the Ajax call. This is where the magic of the routing engine coupled with the jQuery Ajax call comes together:
1: $(document).ready(
2: function()
3: {
4: // Hide update box
5: $('#updater').hide();
6:
7: var retrieveData = function(path, query, funStart, funEnd, funHandleData)
8: {
9: // for displaying updater
10: funStart();
11:
12: // retrieve JSON result
13: $.getJSON(
14: path,
15: { name : query },
16: function(data)
17: {
18: // handle incoming data
19: funHandleData(data);
20: // for hiding updater
21: funEnd();
22: }
23: );
24: };
25:
26: // adding handling to find link
27: $('#linkFind').click(
28: function(event)
29: {
30: event.preventDefault();
31: retrieveData(
32: // mvc route to JSON request
33: '/Student/Find/',
34: // query from textbox
35: $('#textSearch')[0].value,
36: // function to show updater
37: function() { $('#updater').show(); },
38: // function to hide updater
39: function() { $('#updater').hide(); },
40: // retrieving the data
41: function(data)
42: {
43: // clear students (if any)
44: $('#studentList > div').remove();
45: // add students
46: for(s in data)
47: {
48: var student = data[s];
49: $('#studentList').append('<div>(' + student.StudentId ... you get the idea
50: }
51: }
52: );
53: }
54: );
55: }
56: );
The retrieveData function uses the JQuery $.getJSON call to retrieve the data. It takes as arguments the path to the controller (remembering routes), the argument (in this case the query) that the controller action expects, as well as a couple of functions. These functions will handle the cases where the request is started, the request is ended, and what to do with the data that is returned. Subsequently we add the event handler to the link that initiates the find request. Notice that we are passing in '/Student/Find/' as the path since we are using the Find action on the StudentController. The argument is passed in from the search text box. This sets into motion the asynchronous request to the controller. The controller then returns a JSON serialized array of Student objects. Once the data is received, the studentList div is then cleared and repopulated.
Screenshot
The request:
The response:
Conclusion
Hopefully this has been helpful! It was fun putting everything together. Feel free to send me an email or leave a comment on this post if there are any suggestions/comments.
Mikel



