關於AJAX跨域呼叫ASP.NET MVC或者WebAPI服務的問題及解決方案
時間:2014-7-3
問題描述
當跨域(cross domain)呼叫ASP.NET MVC或者ASP.NET Web API編寫的服務時,會發生無法訪問的情況。
重現方式
-
使用模板建立一個最簡單的ASP.NET Web API專案,除錯起來確認能正常工作
-
建立另外一個專案,僅僅包含一個HTML頁面,發起AJAX的呼叫
-
在瀏覽器中開啟這個網頁,我們會發現如下的錯誤(405:Method Not Allowed)
【備註】同樣的情況,也發生在ASP.NET MVC中。某些時候,MVC也可以直接用來開發服務,與WebAPI相比各有優缺點。下面是一個利用MVC開發的服務的例子
原因分析
跨域問題僅僅發生在Javascript發起AJAX呼叫,或者Silverlight發起服務呼叫時,其根本原因是因為瀏覽器對於這兩種請求,所給予的許可權是較低的,通常只允許呼叫本域中的資源,除非目標伺服器明確地告知它允許跨域呼叫。
所以,跨域的問題雖然是由於瀏覽器的行為產生出來的,但解決的方法卻是在服務端。因為不可能要求所有客戶端降低安全性。
解決方案
針對ASP.NET MVC和ASP.NET Web API兩種專案型別,我做了一些研究,確定下面的方案是可行的。
針對ASP.NET MVC,只需要在web.config中新增如下的內容即可
<system.webServer>
</system.webServer>
針對ASP.NET Web API,除了上面這樣的設定,還需要新增一個特殊的設計,就是為每個APIController新增一個OPTIONS的方法,但無需返回任何東西。
public string Options()
{
return null; // HTTP 200 response with empty body
}
【備註】這個功能也可以進行一些研究,設計成Filter的形式可能就更好了。
如何讓你的 Asp.Net Web Api 介面,擁抱支援跨域訪問。
由於 web api 專案通常是被做成了一個獨立站點,來提供資料,在做web api 專案的時候,不免前端會遇到跨域訪問介面的問題。
剛開始沒做任何處理,用jsonp的方式呼叫 web api 介面,總是報一個錯誤,如下:
如果你想用JSONP來獲得跨域的資料,WebAPI本身是不支援javascript的callback的,它返回的JSON是這樣的:
{"YourSignature":"嫁人要嫁程式設計師,錢多話少死得早"}
複製程式碼
然而,JSONP請求期望得到這樣的JSON:
jQuery123456([{"YourSignature":"嫁人要嫁程式設計師,錢多話少死得早"}])
複製程式碼
所以我們需要對WebAPI做擴充,讓它支援這樣的callback
解決方案如下:
只需要給全域性註冊一個JsonCallbackAttribute,就可以判斷介面的訪問是屬於跨域,還是非跨域,正常的返回。
因為我們的介面,可能是用來給 移動端(Android 、IOS)做資料介面,也有可能是給網站用,所以,考慮到可能存在跨域的問題。
GlobalConfiguration.Configuration.Filters.Add(new JsonCallbackAttribute());
public class JsonCallbackAttribute : ActionFilterAttribute
{
private const string CallbackQueryParameter = "callback";
public override void OnActionExecuted(HttpActionExecutedContext context)
{
var callback = string.Empty;
if (IsJsonp(out callback))
{
var jsonBuilder = new StringBuilder(callback);
jsonBuilder.AppendFormat("({0})", context.Response.Content.ReadAsStringAsync().Result);
context.Response.Content = new StringContent(jsonBuilder.ToString());
//context.Response.Content = new StringContent("C(\"a\")");
}
base.OnActionExecuted(context);
}
private bool IsJsonp(out string callback)
{
callback = System.Web.HttpContext.Current.Request.QueryString[CallbackQueryParameter];
return !string.IsNullOrEmpty(callback);
}
複製程式碼
結合下面圖片不難開出,請求的地址帶回了,callback的引數標識。
測試程式碼如下:
<html>
<head>
<title>團隊資訊列表</title>
<script type="text/javascript" src="@Url.Content("~/scripts/jquery-1.7.1.js")"></script>
</head>
<body>
<ul id="contacts"></ul>
<script type="text/javascript">
$(function () {
$.ajax({
Type: "GET",
url: "http://app.uni2uni.com/api/CloudService/GetAllGroup",
dataType: "jsonp",
success: listContacts
});
});
function listContacts(contacts) {
alert(contacts);
$.each(contacts.data, function (index, contact) {
var html = "<li><ul>";
html += "<li>GroupName: " + contact.GroupName + "</li>";
html += "<li>GroupPicture:" + contact.GroupPicture + "</li>";
html += "</ul>";
$("#contacts").append($(html));
});
}
</script>
</body>
</html>
複製程式碼
返回介面如下:
相關文章推薦:diaosbook.com/Post/2013/1…
C# 後臺 呼叫 WebApi
2018年04月24日 20:30:49 AndyLee_ 閱讀數:757
Post:
private void button1_Click(object sender, EventArgs e)
{
string ss= HttpPost("http://localhost:41558/api/Demo/PostXXX", "{Code:\"test089\",Name:\"test1\"}");
}
public static string HttpPost(string url, string body)
{
//ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult);
Encoding encoding = Encoding.UTF8;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "POST";
request.Accept = "text/html, application/xhtml+xml, */*";
request.ContentType = "application/json";
byte[] buffer = encoding.GetBytes(body);
request.ContentLength = buffer.Length;
request.GetRequestStream().Write(buffer, 0, buffer.Length);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
using (StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8))
{
return reader.ReadToEnd();
}
}
複製程式碼
Get:
private void button1_Click(object sender, EventArgs e)
{
string ss = HttpGet("http://localhost:41558/api/Demo/GetXXX?Name=北京");
}
public static string HttpGet(string url)
{
//ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult);
Encoding encoding = Encoding.UTF8;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "GET";
request.Accept = "text/html, application/xhtml+xml, */*";
request.ContentType = "application/json";
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
using (StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8))
{
return reader.ReadToEnd();
}
}
複製程式碼
1、什麼是WebApi,它有什麼用途?
Web API是一個比較寬泛的概念。這裡我們提到Web API特指ASP.NET MVC Web API。在新出的MVC中,增加了WebAPI,用於提供REST風格的WebService,新生成的WebAPI專案和典型的MVC專案一樣,包含主要的Models、Views、Controllers等資料夾和Global.asax檔案。Views對於WebAPI來說沒有太大的用途,Models中的Model主要用於儲存Service和Client互動的物件,這些物件預設情況下會被轉換為Json格式的資料迚行傳輸,Controllers中的Controller對應於WebService來說是一個Resource,用於提供服務。和普通的MVC一樣,Global.asax用於配置路由規則,個人的理解MVC控制器中一般方法返回的是一個檢視而Api返回的是資料,可以是List、Json等格式。對於WebAPI來說它最初被設計為和WCF一樣的客戶埠、服務埠兩套結構我們到現在還沒有提到客戶埠是因為我們的請求別的方式來封裝成HTTP請求接收收HTTP相應的比如AJAX和Form表單提交。接下來我們用VS2017新建一個webApi專案。
複製程式碼
2、開啟VS2017 點檔案-----> 新建---->專案。快捷鍵(Ctrl+Shift+N)
Web裡面的Asp.Net Web 應用程式
上面一定要選Web API 。專案建好了 我先做一個從資料庫取出資料在呼叫的栗子。
3、在控制器資料夾右鍵---->新增---->控制器。選擇Web API2 Controller Empty點確定取名Customer 因為後面要運算元據庫這張表 ,點add完成
這時候App_Satrt資料夾下面多了一個檔案WebApiConfig.cs,這個檔案是它的路由
複製程式碼
由於以前都是用原生的sql訪問資料庫 所以現在也說一下 原生的sql 在asp.net MVC中的使用不用EF連線 後面也使用EF
4、在Models資料夾下面建立模型類Customer類與SqlDB類,這裡沒有分三層架構就簡單的寫在一起了理解就行(SqlDB類這裡有《Asp.Net中對操作Sql Server 簡單處理的SqlDB類》)
public class Customer
{
//ID
public int ID { get; set; }
//姓名
public string Name { get; set; }
//學號
public string Number { get; set; }
//性別1男2女
public int Sex { get; set; }
//院系
public string Department { get; set; }
//專業
public string Manjor { get; set; }
//年級
public int Class { get; set; }
//聯絡方式
public string Mobile { get; set; }
}
複製程式碼
在專案的web.config配置檔案裡面一定要加資料庫連線字串配置
<connectionStrings>
<!--連線字串-->
<add name="SqlConn" connectionString=" Data Source=.;Initial Catalog=OneCardSystem;Integrated Security=True" />
</connectionStrings>
複製程式碼
5、在API控制器裡面寫個方法返回List型別的資料
這個API控制器一定是繼承ApiController這個控制器的
public class CustomerController : ApiController
{
Customer customer = new Customer();
public List<Customer> GetCustomerAll()
{
//sql語句
string _sql = "Select * from Customer";
//例項SqlDB類
SqlDB sb = new SqlDB();
//定義List儲存
List<Customer> list = new List<Customer>();
//獲取資料
var data = sb.ExecuteDataSet(_sql).Tables[0].Rows;
//遍歷data
foreach(DataRow item in data)
{
var customer = new Customer()
{
//對應欄位
ID = Convert.ToInt32(item["ID"].ToString()),
Number= item["Number"].ToString(),
Name= item["Name"].ToString(),
Manjor= item["Manjor"].ToString(),
Mobile= item["Mobile"].ToString()
};
//逐條新增到list裡面
list.Add(customer);
}
//返回list
return list;
}
}
6、執行專案預設來到Home控制器所對應的檢視在位址列後面加上/api/Customer/GetCustomerAll 就可以看到資料了 這個是在谷歌瀏覽器上面看到的結果,不同的瀏覽器有不同的效果 有時間自己試試看,API控制器訪問主要的就是在埠後面加上/api/控制器/方法名, 後面有引數的接上引數跟mvc一般的控制器我的理解就是就多了api的字首
複製程式碼
7、現在在瀏覽器裡面可以直接呼叫這個api了 ,接下來在一個專案中Home所對應的Index檢視裡面我們用前臺技術(jQuery getJSON)訪問呼叫一下試試
把引用的佈局我倒是刪除了,Views資料夾下面的_ViewStart.cshtml 刪除了 Index.cshtml裡面的的內容也刪了
程式碼在下面 別忘了引用JS
<!DOCTYPE html>
<html lang="en">
<head>
<title>ASP.NET Web API</title>
<script type="text/javascript" src="~/Scripts/jquery-3.3.1.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$.getJSON("/api/Customer/GetCustomerAll", function (data) {
// console.log(data);
$.each(data, function (key, val) {
$("#batMain").append("<tr><td>" + data[key].ID + "</td><td>" + data[key].Name + "</td><td>" + data[key].Number + "</td><td>" + data[key].Manjor + "</td><td><a class='tabEdit'>編輯</a> <a class='tabDele'>刪除</a></td></tr>");
Op();
});
});
$("#btnSave").click(function () {
var id = $("#txtID").val();
var Name = $("#txtName").val();
var Number = $("#txtNumber").val();
var Manjor = $("#txtManjor").val();
var customer = {
ID:id,
Name:Name,
Number:Number,
Manjor:Manjor
}
$.ajax({
type: "post",
url: "/api/Customer/Edit",
// data: { "id": id, "Name": Name, "Number": Number, "Manjor": Manjor },
data: JSON.stringify(customer),
contentType:"application/json",
success: function (data) {
console.log(data);
},
error: function () {
}
});
});
});
function Op() {
var id = "";
var row
var tab = document.getElementById("batMain");
var edit = $(".tabEdit");
var dele = $(".tabDele");
for (var i = 0; i < edit.length; i++) {
edit[i].onclick = function () {
row = this.parentNode.parentNode.rowIndex;
id = $("#batMain tr:eq(" + row + ") td:eq(0)").html();
$("#txtID").val($("#batMain tr:eq(" + row + ") td:eq(0)").html());
$("#txtName").val($("#batMain tr:eq(" + row + ") td:eq(1)").html());
$("#txtNumber").val($("#batMain tr:eq(" + row + ") td:eq(2)").html());
$("#txtManjor").val($("#batMain tr:eq(" + row + ") td:eq(3)").html());
}
}
}
</script>
<style type="text/css">
table{width:100%;}
table tr td{text-align:center; line-height:20px; height:25px; min-width:80px; border:1px solid #d0cfcf;word-wrap:break-word;word-break:break-all;}
a{list-style:none;font-size:12px;font-weight:600; color:#072af7;}
a:hover{cursor:pointer}
.trOne{background:rgb(202, 200, 212); line-height:40px; font-size:14px; font-weight:600;}
</style>
</head>
<body id="body">
<div style="width:90%; margin: 0 auto;">
<h1>學生資訊</h1>
<table cellpadding="0" cellspacing="0">
<tr>
<td width="20%"><input type="text" id="txtID" readonly="readonly" /></td>
<td width="20%"><input type="text" id="txtName" /></td>
<td width="20%"><input type="text" id="txtNumber" /></td>
<td width="20%"><input type="text" id="txtManjor" /></td>
<td width="20%"><input type="button" id="btnSave" value="確定" /></td>
</tr>
</table>
<table cellpadding="0" cellspacing="0" id="batMain">
<tr class="trOne">
<td width="20%">ID</td>
<td width="20%">姓名</td>
<td width="20%">學號</td>
<td width="20%">專業</td>
<td width="20%">操作</td>
</tr>
</table>
</div>
</body>
</html>
複製程式碼
看看效果
這裡的編輯刪除操作我還沒有做完,不過一般的asp.net mvc我裡面用的是ajax請求後臺 這個不是請求API只是處理一般的增刪查改,這裡記錄一下傳值的問題
dada裡面可以是// data: { "id": id, "Name": Name, "Number": Number, "Manjor": Manjor },這樣的格式 後臺獲取直接用Request["ID"] 這樣子就可以獲取了。也可以在方法裡面定義對應的引數 ,引數名要跟data裡面的一樣
也可以傳值成一個物件過去 data: JSON.stringify(customer), 這樣 後臺接收 var stream = HttpContext.Request.InputStream; string JsonStr = new StreamReader(stream).ReadToEnd(); 然後自己解析一下
如果要在請求的API裡面傳引數我只能傳一個ID 其他的還沒有研究 要傳物件過去的話還要在方法裡面加上 ([FromBody] Customer ct)這樣
8、在後臺通過HttpClient呼叫API 我做練習的時候是用兩個專案 一個執行著 另一個呼叫這個 現在我弄在一個專案裡面 用控制器來請求。新建一個控制器Show右鍵Index新增檢視Index.cshyml
public class ShowController : Controller
{
// GET: Show
public ActionResult Index()
{
//獲取埠
string url= Request.Url.ToString();
var httpClient = new HttpClient();
var data= httpClient.GetAsync(url+ "api/Customer/GetCustomerAll").Result.Content.ReadAsStringAsync().Result;//呼叫API
System.Web.Script.Serialization. JavaScriptSerializer Serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
List<Customer> objs = Serializer.Deserialize<List<Customer>>(data);//Json反序列化成List
// Customer cs = JsonConvert.DeserializeObject<Customer>(cg.ReturnData);
//, JsonRequestBehavior.AllowGet
return View(objs);
}
}
複製程式碼
檢視
對應的程式碼
@{
ViewBag.Title = "Index";
}
@using WebApiTest.Models;
@model List<Customer>
<html lang="en">
<head>
<title>呼叫API</title>
<style type="text/css">
table{width:100%;}
table tr td{text-align:center; line-height:20px; height:25px; min-width:80px; border:1px solid #d0cfcf;word-wrap:break-word;word-break:break-all;}
a{list-style:none;font-size:12px;font-weight:600; color:#072af7;}
a:hover{cursor:pointer}
.trOne{background:rgb(202, 200, 212); line-height:40px; font-size:14px; font-weight:600;}
</style>
</head>
<body id="body">
<br />
<div style="width:90%; margin: 0 auto;">
<table>
<tr class="trOne">
<td width="20%">ID</td>
<td width="20%">姓名</td>
<td width="30%">學號</td>
<td width="30%">專業</td>
</tr>
@{
if(Model.Count>0)
{
foreach(var item in Model)
{
<tr>
<td>@item.ID</td>
<td>@item.Name</td>
<td>@item.Number</td>
<td>@item.Manjor</td>
</tr>
}
}
}
</table>
</div>
</body>
</html>
複製程式碼
注意應用using System.Web.Script.Serialization; 沒有的話來這裡找
修改路由配置指向Show 控制器執行專案 你會看到
9、遇到的問題:
如果這裡你要返回一個Json資料 記得新增 return(objs, JsonRequestBehavior.AllowGet) 寫成這樣 具體什麼錯我也忘記了 JsonResult也是一樣的new 的時候一樣的新增後面的
如果在不同的專案中呼叫還要在配置檔案裡面加下面的程式碼 加在<system.webServer>裡面 實在寫API的專案的配置檔案裡面
<!--跨域配置-->
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Max-Age" value="30" />
<add name="Access-Control-Allow-Methods" value="GET,POST,OPTIONS" />
<add name="Access-Control-Allow-Headers" value="Content-Type, Accept" />
</customHeaders>
<tpProtocol>
<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>
複製程式碼
要是除錯的時候出錯發現請求的路徑會自動的加上一個Home 在api前面記得在api裡面類名前面加 [RoutePrefix("Api/Customer")]
10、總結:本來的需求是要呼叫人家的API資料庫記錄呼叫的地址、時間、返回的資料這些,就先弄弄,EF、Linq拉姆達這些後面有時間在弄吧 我弄了一點不是很理解在這裡還是個小白,以前都是用原生的Sql語句寫的現在都不用了,只能慢慢學了。歡迎有緣看到的朋友指出不足,共同學習!好久沒有寫部落格了,月到十五光明少,人到中年萬事休。
NET Web Service介面生成及呼叫