1、使用场景
我在练习ArcGIS API 4.12 for JavaScript 开发的时候,需要把数据库(我用的MySQL5.7)中的数据,通过查询调取出来,然后在客户端也就是网页端用JavaScript来呈现出来,比如我在文章:基于ArcGIS API 4.12 for JS 的开发实践(四)——html5tooltips气泡标注窗 里面用到的,把智能设备的经纬度及设备状态信息从数据库调取出来,然后通过定制html5tooltips这个开源插件实现比较舒服的呈现方式。下图就是智能设备在数据库中存放的方式:
那有同学就问了,JavaScript也可以直接有API来操作数据库啊,干嘛要多此一举呢?首先,JavaScript确实有这个能力直接操作数据库,但是JavaScript是直接暴露在客户端,任何人都是通过浏览器的开发功能查看你的JavaScript源码,这样你的数据库就有暴露的风险,虽然可以通过加密JavaScript源码来避免这种情况,可是破解和加密这对双胞胎使得这种风险依然存在。
所以我们可以通过WebService这种方式,在服务器端发布WebService,客户端用JavaScript通过URL来调用WebService,这样客户端最多也就暴露了一个服务器端的WebService服务URL,这对服务器端的数据库威胁大大降低。另外对于刚上手JavaScript的开发者,使用自己熟悉的语言比如C#或者Java来开发一个服务器端的WebService服务,难度将大大降低。我比较熟悉C#,所以我在Visual Studio上面开发了一个asp.net WebService服务。
2、基于asp.net的 WebService服务
(1)创建一个WebService服务
在Visual Studio中创建WebService服务的操作流程就不多说了,傻瓜式的步骤,如果不清楚百度一下吧。我这里着重讲一下JavaScript调用的WebService服务创建时候要注意的内容。创建好之后,一个自定义的服务大概是下面代码的样子:
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
[System.Web.Script.Services.ScriptService]
public class smcJsonService : System.Web.Services.WebService
{
//webservice Methord
……………………………………………………
}
这就是我们创建好的自定义的WebService服务,我用的服务名是“smcJsonService”。注意:
[System.Web.Script.Services.ScriptService]
这一行代码,系统默认是注释掉的,并且有一句提醒就是:若要允许ASP.NET AJAX调用此Web服务,请去掉注释。因为我们要在JavaScript调用此Web服务,那么我们就需要把这行代码的注释去掉。
(2)添加WebMethod(不带参数),Dojo的XMLHttpRequest进行Post
在class smcJsonService的大括号里面添加我们的第一个WebMethod,代码如下:
[WebMethod]
public string GetAllRows()
{
DataTable dt = MySqlHelper.getRows("SELECT * FROM smartcover.smc ");
string jsonResult = JsonHelper.DataTableToJson(dt);
return jsonResult;
}
这个就是一个WebMethod,我们后面发布之后可以通过URL来调用这个方法。这里有几个说明:
第一:WebMethod方法GetAllRows()上面有一个[WebMethod]修饰,这个就是告诉编译器,这是一个WebMethod。这个可以是有参数的,可以是没有参数的,返回值也可以自定义,这里我返回一个字符串。
第二:代码第一句“DataTable dt = MySqlHelper.getRows("SELECT * FROM smartcover.smc ");”这里是通过C#查询数据库,并把返回值放到一个DataTable里面。
第三:代码第二句“string jsonResult = JsonHelper.DataTableToJson(dt);”,这里是我把存放数据库查询结果的DataTable转换成JSON格式的数据。并返回。(文章顶部会提供MySqlHelper、JsonHelper的完整代码,需要自取)
最后需要在JavaScript端来调用,我这里先使用Dojo的xhr来发送请求:
require([
"dojo/_base/xhr",
"dojo/domReady!"],
function(xhr,
dom,
on)
{
xhr.post({
//请求自建的WebService服务,返回的是JSON字符串
url:"http://localhost/SMCJsonService/smcJsonService.asmx/GetAllRows",
headers: { "Content-Type": "application/json" },
handleAs : "json",
load : function(data)
{
//dosomting
var jb=JSON.parse(data.d);
},
error:function (error)
{
console.log("error"+error);
}
});
}
);
这里是使用Dojo的require以及xhr来向WebService发送请求,首先要在require的参数中加入"dojo/_base/xhr",然后在回调函数function中定义变量名xhr,然后在回调函数中调用xhr.post,正式向WebService发送请求。请注意,因为我们返回的是JSON格式的字符串,所以这里的“headers”以及“handleAs”都采用JSON的方式,然后在“load”加载成功事件中,它的参数“data”就是调用的返回值,但是“data”本身并不是GetAllRows()返回的JSON字符串,而是在data.d中存放,所以我们把JSON字符串要重新解析为JSON格式的数据:var jb=JSON.parse(data.d);,或者返回数据后就可以进行你的操作,比如把数据库中的数据呈现出来,这里的代码就不详细说了。这里返回的是我的设备点集,是一个JSON数组,我可以遍历它。
注意上述内容,是不带参数的WebMethod,一般不会出现问题,如果遇到“Failed to load resource: the server responded with a status of 500 (Internal Server Error)”这个错误,在只要你在浏览器地址栏里面能打开你的WebService的前提下,多半是因为参数问题或者返回格式的问题,要好好查一下。下面我说说带参数的WebMethod。
(3)添加WebMethod(带参数),Dojo的XMLHttpRequest进行Post
在class smcJsonService的大括号里面添加我们的第二个WebMethod,代码如下:
[WebMethod]
public string UserCheck(string jsonObject)
{
ResultJson rj = new ResultJson();
if (jsonObject == null || jsonObject == "")
{
User myuser = JsonHelper.JsonDeserialize<User>(jsonObject);
string username = myuser.UserName;
string userpassword = myuser.UserPassword;
//dosomthing(用SQL语句验证用户密码是否正确)
return JsonHelper.ObjectToJson(rj);//通过对ResultJson赋不同的值,来指示不同的验证结果
}
}
大家看上述代码,这次UserCheck这个WebMethod带了一个参数,字符串string类型,但其实这是一个JSON字符串;返回值也是一个字符串string类型,这也是一个JSON字符串。里面有一个我自定义的类ResultJson,而JsonHelper可以帮助我把自定义类ResultJson转换成一个JSON字符串返回。
大家注意,参数是一个JSON字符串,可以是前端的一个JSON对象,然后由JavaScript转换成JSON字符串。比如我在JavaScript端有这样的代码:
var userJson={
UserName:myusername,
UserPassword:myuserpassword
};
上面的JavaScript是我获取了前端的用户名和密码,然后希望在后端进行数据库验证是否密码符合。两个字段“UserName”和“UserPassword”并不是随意起的名字,而是在后端我有一个“User”类,而这个“User”类是有“UserName”和“UserPassword”这两个公共属性的,JavaScript和ASP.NET这两边名字必须完全一致。以下是ASP.NET端“User”类的代码:
public class User
{
private string _userName;
/// <summary>
/// 用户名
/// </summary>
public string UserName
{
get { return _userName; }
set { _userName = value; }
}
private string _userPassword;
/// <summary>
/// 要用户密码
/// </summary>
public string UserPassword
{
get { return _userPassword; }
set { _userPassword = value; }
}
private string _telephone;
/// <summary>
/// 电话
/// </summary>
public string Telephone
{
get { return _telephone; }
set { _telephone = value; }
}
private string _mail;
/// <summary>
/// 用户邮箱
/// </summary>
public string Mail
{
get { return _mail; }
set { _mail = value; }
}
private string _registerTime;
/// <summary>
/// 注册时间
/// </summary>
public string RegisterTime
{
get { return _registerTime; }
set { _registerTime = value; }
}
public User()
{
_userName = string.Empty;
_userName = string.Empty;
_userName = string.Empty;
_userName = string.Empty;
_registerTime = "1990/1/1 00:00:00";
}
}
avaScript和ASP.NET这两边名字完全一致后,我就可以在ASP.NET端解析传过来的参数“jsonObject”,就是下面这句代码:
User myuser = JsonHelper.JsonDeserialize<User>(jsonObject);
ASP.NET端的代码其实没有什么可说的,就是解析传过来的参数,再执行一些验证操作,最后返回验证结果,对于ASP.NET端来说,传入参数和返回值都是字符串string类型的。
下面我着重说一下JavaScript端的代码,以及常碰到的问题,先贴代码:
var userJson={
UserName:myusername,
UserPassword:myuserpassword
};
var data1=JSON.stringify(userJson);
var data2={jsonObject:data1};
var data3=dojo.toJson(data2);
var data4=JSON.stringify(data2);
require([
"dojo/_base/xhr",
"dojo/domReady!"],
function(xhr,
dom,
on)
{
xhr.post({
//请求自建的WebService服务,返回的是JSON字符串
url:"http://localhost/SMCJsonService/smcJsonService.asmx/UserCheck",
headers: { "Content-Type": "application/json" },
postData: data3,
handleAs : "json",
load : function(data)
{
//dosomting
var jb=JSON.parse(data.d);
console.log(jb.Result);
},
error:function (error)
{
console.log("error"+error);
}
});
}
);
因为我们请求的WebService服务UserCheck是需要输入参数的,因此我们在xhr.post事件中加了一行代码,就是让xhr提交请求的时候把参数带上,就是下面这行代码:
postData: data3,
上面的“data3”,是需要传到WebService服务的参数,“postData”是xhr.post的参数名。而“data3”的来历,大家可以看下require语句的上面部分,也就是下面这段代码:
var userJson={
UserName:myusername,
UserPassword:myuserpassword
};
var data1=JSON.stringify(userJson);
var data2={jsonObject:data1};
var data3=dojo.toJson(data2);
var data4=JSON.stringify(data2);
上面代码中,首先我根据从客户端获取到的用户名和密码两个值(myusername和myuserpassword),创建了一个JSON对象“userJson”,然后我把这个JSON对象转换成JSON字符串,即“data1”。然后由“data1”这个变量,再创建了一个JSON对象“data2”。然后我又把“data2”这个JSON对象转换成JSON字符串,即“data3”和“data4”,这里我用了两种转换方式,结果都是一样的。读者可以选用其中一种。
细心的读者,可能看到了,这个“data2”JSON对象里面,“data1”是JSON字符串,为什么里面有一个“jsonObject”这个名称呢?从何而来呢?这个“jsonObject”其实就是我在WebMethod函数“UserCheck”中自定义的一个参数名称,这个名称读者可以换成其他方便自己辨识的名称,并非固定的。也就是说如果,你的WebMethod函数的参数名是“User”,这么这里这个“data2”JSON对象里面就应该用var data2={User:data1};,只要跟你WebMethod函数的参数名一样就行了。最后,我们需要把这个“data2”JSON对象转换成JSON字符串,如果不转换直接用“postData: data2,”就会报“Failed to load resource: the server responded with a status of 500 (Internal Server Error)”这个错误。经过我的测试,“data3”和“data4”都可以正常调用WebMethod,而“data1”和“data2”都会报上面这个Internal Server Error错误,读者可以自行测试。
说实话,这个问题困扰了我很久,我也是不断的找文章,不断地测试才发现问题所在,希望对大家有所帮助,节省时间。
(4)使用AJAX进行Post请求webservice
除了用Dojo的XMLHttpRequest进行Post,还可以用jQuery的$.ajax进行Post请求,其实$.ajax的Post请求跟XMLHttpRequest的Post请求差不多,只有些许的差别,下面看代码:
var userJson={
UserName:myusername,
UserPassword:myuserpassword
};
var data1=JSON.stringify(userJson);
var data2={jsonObject:data1};
var data3=dojo.toJson(data2);
var data4=JSON.stringify(data2);
$.ajax({
type: "POST",
url: "http://localhost/SMCJsonService/smcJsonService.asmx/UserCheck",
data: data3,
contentType: "application/json; charset=utf-8",
dataType: "json"
}).done(function(data)
{
var jb=JSON.parse(data.d);
console.log(jb.Result);
}).fail(function(a, b, c, d)
{
alert("Failure: "
+ JSON.stringify(a) + " "
+ JSON.stringify(b) + " "
+ JSON.stringify(c) + " "
+ JSON.stringify(d) );
});
上面这段代码就不详说了,跟之前的XMLHttpRequest的Post请求差不多,只是处理函数名称有不一样,另外一定要记着加上“contentType: "application/json; charset=utf-8",”,否则会报“Failed to load resource: the server responded with a status of 500 (Internal Server Error)”这个错误。
我在开发过程中,上述两种方式都试过,有时候还是会出现“Failed to load resource: the server responded with a status of 500 (Internal Server Error)”这个错误,而在浏览器地址栏中输入WebService的服务URL,可以正常打开。如果你还是出现这个Internal Server Error错误,或者是CORS policy跨域访问的错误,我建议你用下一jsonp这种数据传输方式,jsonp是专门为解决跨域问题而生的。下面是使用jsonp这种数据传输方式的代码:
var userJson={
UserName:myusername,
UserPassword:myuserpassword
};
var data1=JSON.stringify(userJson);
var data2={jsonObject:data1};
var data3=dojo.toJson(data2);
var data4=JSON.stringify(data2);
$.ajax({
type: "POST",
url: "http://localhost/SMCJsonService/smcJsonService.asmx/UserCheck",
data: data2,
jsonp: "callback",
contentType: "application/json; charset=utf-8",
dataType: "jsonp"
}).done(function(data)
{
var v=data.Result;
console.log(v);
}).fail(function(a, b, c, d)
{
alert("Failure: "
+ JSON.stringify(a) + " "
+ JSON.stringify(b) + " "
+ JSON.stringify(c) + " "
+ JSON.stringify(d) );
});
大家看上面的代码,跟XMLHttpRequest的Post不一样,并且XMLHttpRequest的Post中没有支持jsonp的内容;并且也跟前面的$.ajax的Post代码不一样,主要有以下不同:
第一:$.ajax的Post请求里面多了一段代码“jsonp: "callback",”,这段代码必须加上,并且对应ASP.NET端的WebMethod也需要相应修改,先按下不说。
第二:$.ajax的Post请求里面的“dataType”由原来的“json”换成了“jsonp”。
第三:$.ajax的Post请求里面的“data”由原来的“data3”换成了“data2”。如果你继续用“data3”这样的值,就会报“Failed to load resource: the server responded with a status of 500 (Internal Server Error)”这个错误。
第四:$.ajax的Post请求的返回结果不是原来的“data.d”进行取值了,“var v=data.Result;”这行代码里而是返回的直接是一个JSON对象。
第五:需要修改ASP.NET端WebMethod的代码,修改后的代码如下:
[WebMethod]
public string UserCheck(string jsonObject)
{
ResultJson rj = new ResultJson();
Context.Response.Clear();
Context.Response.ContentType = "aplication/json";
string callback = Context.Request.QueryString["callback"] != null ? Context.Request.QueryString["callback"] : "?";
if (jsonObject == null || jsonObject == "")
{
User myuser = JsonHelper.JsonDeserialize<User>(jsonObject);
string username = myuser.UserName;
string userpassword = myuser.UserPassword;
//dosomthing(用SQL语句验证用户密码是否正确)
Context.Response.Write(callback + "(" + JsonHelper.ObjectToJson(rj) + ");");
Context.Response.End();
return callback + "(" + JsonHelper.ObjectToJson(rj) + ");";//通过对ResultJson赋不同的值,来指示不同的验证结果
}
}
这里面添加了几行代码,读者应该能看出来首先是这三句:
Context.Response.Clear();
Context.Response.ContentType = "aplication/json";
string callback = Context.Request.QueryString["callback"] != null ? Context.Request.QueryString["callback"] : "?";
然后就是最后返回值的时候记上下面三句:
Context.Response.Write(callback + "(" + JsonHelper.ObjectToJson(rj) + ");");
Context.Response.End();
return callback + "(" + JsonHelper.ObjectToJson(rj) + ");";
在下愚钝,暂时还不能理解以上六句代码的内在含义,不过确实解决了JavaScript访问WebService时遇到的跨域问题,比如“has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.”这个问题真是困扰很久,只有不断的查,不断的试才能解决问题。其实后来我发现使用WebAPI也可以,好像更方便,因为我暂时还没有涉猎,等我研究好了再分享给大家。