web api 二
接着上一回说,上回说到,web api有几种访问方式,具体有几种,我还真没去研究过,但是这里打算从get、post、put、delete四种请求方式分别谈谈基础类型(包括int/string/datetime等)、实体、数组等类型的参数如何传递。
在介绍之前,有个概念必须先弄清楚:出于安全考虑,浏览器会限制脚本中发起的跨站请求,浏览器要求JavaScript或Cookie只能访问同域下的内容。
什么意思,就拿现在做的这个来说,在这里,我是把webapi项目作为一个单独的控制层来处理,而MVC项目就是表示层,model项目就是数据层了。现在我需要在表示层通过js来调用控制层的函数来获取数据层的数据,这里的调用方式就是webapi的调用方式。那么问题就来了,因为是不同的项目,表示层和控制层的端口不一样的,表示层的是7866,控制层的是6972,它们之间的请求就是跨站请求,偏偏浏览器是限制的(这里要说明一下,IE10,IE11在不作任何处理的情况下可以跨站请求,其他浏览器不行,这可能是微软开后门了),所以就有了webapi的跨域这个概念。
跨域问题的解决:
CORS全称Cross-Origin Resource Sharing,中文全称跨域资源共享。它解决跨域问题的原理是通过向http的请求报文和响应报文里面加入相应的标识告诉浏览器它能访问哪些域名的请求。比如我们向响应报文里面增加这个Access-Control-Allow-Origin:http://localhost:6972,就表示支持http://localhost:6972里面的所有请求访问系统资源。其他更多的应用我们就不一一列举,可以去网上找找。
首先是在api项目中建一个类:
public class CrossSiteAttribute : System.Web.Http.Filters.ActionFilterAttribute { private const string Origin = "Origin"; /// <summary> /// Access-Control-Allow-Origin是HTML5中定义的一种服务器端返回Response header,用来解决资源(比如字体)的跨域权限问题。 /// </summary> private const string AccessControlAllowOrigin = "Access-Control-Allow-Origin"; /// <summary> /// originHeaderdefault的值可以使 URL 或 *,如果是 URL 则只会允许来自该 URL 的请求,* 则允许任何域的请求 /// </summary> private const string originHeaderdefault = "http://localhost:7866"; /// <summary> /// 该方法允许api支持跨域调用 /// </summary> /// <param name="actionExecutedContext"> 初始化 System.Web.Http.Filters.HttpActionExecutedContext 类的新实例。</param> public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) { actionExecutedContext.Response.Headers.Add(AccessControlAllowOrigin, originHeaderdefault); } }
里面的http://localhost:7866就是mvc项目的地址。
还有一只方式允许所有网站都可跨域来访问当前项目里的资源,就是在web.config中添加如下代码。
<system.webServer> <validation validateIntegratedModeConfiguration="false"/> <handlers> <remove name="ExtensionlessUrlHandler-Integrated-4.0"/> <remove name="OPTIONSVerbHandler"/> <remove name="TRACEVerbHandler"/> <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0"/> </handlers> </system.webServer>
一、get请求
对于取数据,我们使用最多的应该就是get请求了吧,上回的例子中就有用到,在这里再来介绍一下
对于get请求,有前端和后端两种方式,前端就是用js来实现,后端就是上回说的那样。首先看前端请求。
1、调用无参函数,返回字符串
上面分别是视图项目和控制项目的相关代码,运行结果如下。在api项目中对两个函数添加了 [CrossSite]约束,当然也可以将这个约束添加到valuescontroller上,这样就对所有函数都约束了
2、调用有参函数,返回字符串
function btn2() { $.ajax({ type: 'get', url: "http://localhost:6972/api/values/getString", data: {txt:'123456'}, traditional: true, async: true, success: function (data, status) { if (status == "success") { $("#div_test").html(data); } else { $.ligerDialog.alert("", "操作失败", "error"); } } }); }
3、调用多参函数返回字符串
function btn3() { $.ajax({ type: 'get', url: "http://localhost:6972/api/values/GetAllChargingData", data: { id: 1234, name: "ffff", dt: "1988-09-11" }, traditional: true, async: true, success: function (data, status) { if (status == "success") { $("#div_test").html(data); } else { $.ligerDialog.alert("", "操作失败", "error"); } } }); } public string GetAllChargingData(int id, string name,DateTime dt) { return name+"-" + id+"-"+dt.ToString(); }
4、将实体传入后台调用,并返回一个字符串
function btn4() { $.ajax({ type: 'get', url: "http://localhost:6972/api/values/GetSiteName", contentType: "application/json", data: { query: JSON.stringify({ SiteId: 1, Title: "fwwe", Uri: "rwerwerwe" }) },//将实体序列化 traditional: true, async: true, success: function (data, status) { if (status == "success") { $("#div_test").html(data); } else { $.ligerDialog.alert("", "操作失败", "error"); } } }); } public string GetSiteName(string query) { Site obj = Newtonsoft.Json.JsonConvert.DeserializeObject<Site>(query);//反序列化 return "名称:" + obj.Title; }
5、返回实体
function btn5() { $.ajax({ type: 'get', url: "http://localhost:6972/api/values/GetSiteByID", data: { id: 2}, traditional: true, async: true, success: function (data, status) { if (status == "success") { $("#div_test").html(data.Title); } else { $.ligerDialog.alert("", "操作失败", "error"); } } }); } public Site GetSiteByID(int id) { SiteSource ss = new SiteSource(); return ss.DateSource().Where(o => o.SiteId == id).FirstOrDefault(); }
后端请求:
6、返回实体
public JsonResult getSiteById(int id) { HttpClient httpClient = new HttpClient(); //GET方式去调用webapi var responseJson = httpClient.GetAsync("http://localhost:6972/api/values/GetSiteByID?id="+id) .Result.Content.ReadAsStringAsync().Result; Site site = JsonConvert.DeserializeObject<Site>(responseJson); //允许使用GET方式获取,否则用GET获取是会报错 return new JsonResult() {Data=site,JsonRequestBehavior=JsonRequestBehavior.AllowGet }; } function btn6() { $.ajax({ url: "Web/getSiteById/", type:"get", data: { id:3 }, traditional: true, async: true, success: function (data, status) { if (status == "success") { $("#div_test").html(data.Title); } else { $.ligerDialog.alert("", "操作失败", "error"); } } }); }
二、post请求
post请求的基础类型的参数和get请求有点不一样,我们知道get请求的参数是通过url来传递的,而post请求则是通过http的请求体中传过来的,WebApi的post请求也需要从http的请求体里面去取参数。
如果按照通常的情况去传参就有错,比如:
解决方法之一是用[FromBody]在SaveString函数中修饰参数:public string SaveString([FromBody]string txt),然后再ajax中:data: {“”:"ff"},但是这个方法有一个缺点就是只能适用单一参数的情况,多参数就不行了
dynamic实现多参数传递,单一参数也可以
function ptn1() { $.ajax({ type: 'post', url: "http://localhost:6972/api/values/SaveString", data: JSON.stringify({ NAME: "Jon", DES: "备注" }), traditional: true, contentType: 'application/json', async: true, success: function (data, status) { if (status == "success") { $("#div_test").html(data); } else { $.ligerDialog.alert("", "操作失败", "error"); } } }); } [HttpPost] public string SaveString(dynamic txt) { return "保存:" + txt.NAME+"-"+txt.DES; }
function ptn2() { $.ajax({ type: 'post', url: "http://localhost:6972/api/values/SaveSite", data: JSON.stringify({ id: 3 }), traditional: true, contentType: 'application/json', async: true, success: function (data, status) { if (status == "success") { $("#div_test").html(data.Title); } else { $.ligerDialog.alert("", "操作失败", "error"); } } }); } [HttpPost] public Site SaveSite(dynamic obj) { SiteSource ss = new SiteSource(); string s = obj.id; //int ID = int.Parse(obj.id); 这种方式有错 int ID =int.Parse(s); return ss.DateSource().Where(o => o.SiteId == ID).FirstOrDefault(); }
将实体作为参数传递。这里的实体更上面post方式的第一个例子是不同的,那个例子是传递多个参数的情况
function ptn3() { $.ajax({ type: 'post', url: "http://localhost:6972/api/values/UpdateName", data: { SiteId: "1", Title: "test", Uri: "www.cnblogs.cc" }, traditional: true, //contentType: 'application/json', async: true, success: function (data, status) { if (status == "success") { $("#div_test").html(data); } else { $.ligerDialog.alert("", "操作失败", "error"); } } }); } [HttpPost] public string UpdateName(Site si) { SiteSource ss = new SiteSource(); Site s= ss.DateSource().Where(o => o.SiteId == si.SiteId).FirstOrDefault(); return s.Title + "123"; }
使用实体作为参数的时候,前端直接传递普通json,后台直接使用对应的类型去接收即可,不用FromBody。但是这里需要注意的一点就是,这里不能指定contentType为appplication/json,否则,参数无法传递到后台。
post请求默认是将表单里面的数据的key/value形式发送到服务,而我们的服务器只需要有对应的key/value属性值的对象就可以接收到。而如果使用application/json,则表示将前端的数据以序列化过的json传递到后端,后端要把它变成实体对象,还需要一个反序列化的过程。按照这个逻辑,那我们如果指定contentType为application/json,然后传递序列化过的对象应该也是可以的
2、实体与基础类型同时传递
function ptn4() { var postdata = { SiteId: "1", Title: "test", Uri: "www.cnblogs.cc" }; $.ajax({ type: 'post', url: "http://localhost:6972/api/values/SameName", data: JSON.stringify({ NAME: "test", o: postdata }), traditional: true, contentType: 'application/json', async: true, success: function (data, status) { if (status == "success") { alert(data); } else { $.ligerDialog.alert("", "操作失败", "error"); } } }); } [HttpPost] public bool SameName(dynamic obj) { string strName = Convert.ToString(obj.NAME); Site oCharging = Newtonsoft.Json.JsonConvert.DeserializeObject<Site>(Convert.ToString(obj.o)); return strName==oCharging.Title; }
3、基础类型数组作为参数
function ptn5() { var arr = ["1", "2", "3", "4"]; $.ajax({ type: "post", url: "http://localhost:6972/api/values/SaveData1", contentType: 'application/json', data: JSON.stringify(arr), success: function (data, status) { if (status == "success") { alert(data); } else { $.ligerDialog.alert("", "操作失败", "error"); } } }); [HttpPost] public bool SaveData1(string[] ids) { if (ids[0] == "1") return true; else return false; }
4、实体类型数组作为参数
function ptn6() { var arr = [ { SiteId: "1", Title : "test", Uri : "www.cnblogs.cc" }, { SiteId: "2",Title : "博客园首页", Uri :"www.cnblogs.com" }, { SiteId: "3", Title: "博问", Uri: "q.cnblogs.com" } ]; $.ajax({ type: "post", url: "http://localhost:6972/api/values/SaveData2", contentType: 'application/json', data: JSON.stringify(arr), success: function (data, status) { if (status == "success") { alert(data); } else { $.ligerDialog.alert("", "操作失败", "error"); } } }); } [HttpPost] public bool SaveData2(List<Site> lstSite) { if (lstSite[0].Title == "test") return true; else return false; }
5、post后台调用
function ptn7() { $.ajax({ url: "Web/PostReques/", type: "post", data: {}, traditional: true, async: true, success: function (data, status) { if (status == "success") { $("#div_test").html(data); } else { $.ligerDialog.alert("", "操作失败", "error"); } } }); }
mvc控制器中的相应函数
public JsonResult PostReques() { //请求路径 string url = "http://localhost:6972/api/values/UpdateName"; //定义request并设置request的路径 WebRequest request = WebRequest.Create(url); request.Method = "post"; //初始化request参数 string postData = "{SiteId: \"2\",Title : \"博客园首页\", Uri :\"www.cnblogs.com\" }"; //设置参数的编码格式,解决中文乱码 byte[] byteArray = Encoding.UTF8.GetBytes(postData); //设置request的MIME类型及内容长度 request.ContentType = "application/json"; request.ContentLength = byteArray.Length; //打开request字符流 Stream dataStream = request.GetRequestStream(); dataStream.Write(byteArray, 0, byteArray.Length); dataStream.Close(); //定义response为前面的request响应 WebResponse response = request.GetResponse(); //获取相应的状态代码 Console.WriteLine(((HttpWebResponse)response).StatusDescription); //定义response字符流 dataStream = response.GetResponseStream(); StreamReader reader = new StreamReader(dataStream); string responseFromServer = reader.ReadToEnd();//读取所有 return new JsonResult() { Data = responseFromServer};
三、put请求
WebApi里面put请求一般用于对象的更新。它和用法和post请求基本相同。同样支持[FromBody],同样可以使用dynamic。
function puttn() { $.ajax({ type: 'put', url: "http://localhost:6972/api/values/PutData", data: JSON.stringify({ txt: "Jon"}), traditional: true, contentType: 'application/json', async: true, success: function (data, status) { if (status == "success") { $("#div_test").html(data); } else { $.ligerDialog.alert("", "操作失败", "error"); } } }); } [HttpPut] public string PutData(dynamic txt) { return "123" + txt; }
四、delete请求
delete请求肯定是用于删除操作的。参数传递机制和post也是基本相同。下面简单给出一个例子,其他情况参考post请求。
function deletetn() { $.ajax({ type: 'delete', url: "http://localhost:6972/api/values/deleteData", data: JSON.stringify({ txt: "333333" }), traditional: true, contentType: 'application/json', async: true, success: function (data, status) { if (status == "success") { $("#div_test").html(data); } else { $.ligerDialog.alert("", "操作失败", "error"); } } }); } [HttpDelete] public string deleteData(dynamic txt) { return "----------" + txt; }