Asp.net WebAPi Restful 的实现和跨域
现在实际开发中用webapi来实现Restful接口开发很多,我们项目组前一段时间也在用这东西,发现大家用的还是不那么顺畅,所以这里写一个Demo给大家讲解一下,我的出发点不是如何实现,而是为什么?
首先我们来看看我么的code吧:
control:
public class Users
{
public int UserID { set; get; }
public string UserName { set; get; }
public string UserEmail { set; get; }
}
public class ValuesController : ApiController
{
private static List<Users> _userList;
static ValuesController()
{
_userList = new List<Users>
{
new Users {UserID = , UserName = "zzl", UserEmail = "bfyxzls@sina.com"},
new Users {UserID = , UserName = "Spiderman", UserEmail = "Spiderman@cnblogs.com"},
new Users {UserID = , UserName = "Batman", UserEmail = "Batman@cnblogs.com"}
};
}
/// <summary>
/// User Data List
/// </summary> /// <summary>
/// 得到列表对象
/// </summary>
/// <returns></returns>
public IEnumerable<Users> Get()
{
return _userList;
} /// <summary>
/// 得到一个实体,根据主键
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public Users Get(int id)
{
return _userList.FirstOrDefault();// (i => i.UserID == id);
} /// <summary>
/// 添加
/// </summary>
/// <param name="form">表单对象,它是唯一的</param>
/// <returns></returns>
public Users Post([FromBody] Users entity)
{
entity.UserID = _userList.Max(x => x.UserID) + ;
_userList.Add(entity);
return entity;
} /// <summary>
/// 更新
/// </summary>
/// <param name="id">主键</param>
/// <param name="form">表单对象,它是唯一的</param>
/// <returns></returns>
public Users Put(int id, [FromBody]Users entity)
{
var user = _userList.FirstOrDefault(i => i.UserID == id);
if (user != null)
{
user.UserName = entity.UserName;
user.UserEmail = entity.UserEmail;
}
else
{ _userList.Add(entity);
}
return user;
}
/// <summary>
/// 删除
/// </summary>
/// <param name="id">主键</param>
/// <returns></returns>
public void Delete(int id)
{
//_userList.Remove(_userList.FirstOrDefault(i => i.UserID == id));
_userList.Remove(_userList.FirstOrDefault());
}
public string Options()
{
return null; // HTTP 200 response with empty body
} }
HTML:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>web api test</title>
</head>
<body>
<script type="text/javascript" src="js/jquery-1.7.1.js"></script>
<script type="text/javascript"> function add() {
$.ajax({
url: "http://localhost:6221/api/values/",
type: "POST",
data: { "UserID": , "UserName": "test", "UserEmail": "Parry@cnblogs.com" },
success: function (data) { alert(JSON.stringify(data)); } });
} //更新
function update(id) {
$.ajax({
url: "http://localhost:6221/api/values?id=" + id,
type: "Put",
data: { "UserID": , "UserName": "moditest", "UserEmail": "Parry@cnblogs.com" },
success: function (data) { alert(JSON.stringify(data)); }
}); }
function deletes(id) {
$.ajax({
url: "http://localhost:6221/api/values/1",
type: "DELETE",
success: function (data) { alert(data); }
});
}
function users() {
$.getJSON("http://localhost:6221/api/values", function (data) {
alert(JSON.stringify(data));
});
}
function user() {
$.getJSON("http://localhost:6221/api/values/1", function (data) {
alert(JSON.stringify(data));
});
}
</script>
<fieldset>
<legend>测试Web Api
</legend>
<a href="javascript:add()">添加(post)</a>
<a href="javascript:update(1)">更新(put)</a>
<a href="javascript:deletes(1)">删除(delete)</a>
<a href="javascript:users()">列表(Gets)</a>
<a href="javascript:user()">实体(Get)</a>
</fieldset> </body>
</html>
WebAPI的配置:
<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Headers" value="Content-Type" />
<add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
</customHeaders>
</httpProtocol>
首先说明一下,配置中的httpProtocol和control中的Options都是在跨域的时候才需要的。
问题1.
Get,Post,Put,Delete,Options 这几个方法 ,服务器端是怎么来定位的, 或者说服务器是如何确定是调用哪个Action?
其实我以前的文章 Asp.net web Api源码分析-HttpActionDescriptor的创建 中有提到,这里简单回忆一下:
首先我们客户端的请求Url中都是 http://localhost:6221/api/values/ 打头,这里的values就是我们的Control,这样我们就可以很容易找到这个control下面的方法。主要的类是ApiControllerActionSelector,在它里面有一个子类ActionSelectorCacheItem, 其构造函数就负责初始化control里面的ReflectedHttpActionDescriptor,
MethodInfo[] allMethods = _controllerDescriptor.ControllerType.GetMethods(BindingFlags.Instance | BindingFlags.Public);
MethodInfo[] validMethods = Array.FindAll(allMethods, IsValidActionMethod);
_actionDescriptors = new ReflectedHttpActionDescriptor[validMethods.Length];
for (int i = 0; i < validMethods.Length; i++)
{
MethodInfo method = validMethods[i];
ReflectedHttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor(_controllerDescriptor, method);
_actionDescriptors[i] = actionDescriptor;
HttpActionBinding actionBinding = actionDescriptor.ActionBinding;
// Building an action parameter name mapping to compare against the URI parameters coming from the request. Here we only take into account required parameters that are simple types and come from URI.
_actionParameterNames.Add(
actionDescriptor,
actionBinding.ParameterBindings
.Where(binding => !binding.Descriptor.IsOptional && TypeHelper.IsSimpleUnderlyingType(binding.Descriptor.ParameterType) && binding.WillReadUri())
.Select(binding => binding.Descriptor.Prefix ?? binding.Descriptor.ParameterName).ToArray());
}
_actionNameMapping = _actionDescriptors.ToLookup(actionDesc => actionDesc.ActionName, StringComparer.OrdinalIgnoreCase);
int len = _cacheListVerbKinds.Length;
_cacheListVerbs = new ReflectedHttpActionDescriptor[len][];
for (int i = 0; i < len; i++)
{
_cacheListVerbs[i] = FindActionsForVerbWorker(_cacheListVerbKinds[i]);
}
这里的validMethods 就是我们定义6个方法(2个Get,Post,Put,Delete,Options),在ReflectedHttpActionDescriptor里面的InitializeProperties 的实现如下:
private void InitializeProperties(MethodInfo methodInfo)
{
_methodInfo = methodInfo;
_returnType = GetReturnType(methodInfo);
_actionExecutor = new Lazy<ActionExecutor>(() => InitializeActionExecutor(_methodInfo));
_attrCached = _methodInfo.GetCustomAttributes(inherit: true);
CacheAttrsIActionMethodSelector = _attrCached.OfType<IActionMethodSelector>().ToArray();
_actionName = GetActionName(_methodInfo, _attrCached);
_supportedHttpMethods = GetSupportedHttpMethods(_methodInfo, _attrCached);
}
private static Collection<HttpMethod> GetSupportedHttpMethods(MethodInfo methodInfo, object[] actionAttributes)
{
Collection<HttpMethod> supportedHttpMethods = new Collection<HttpMethod>();
ICollection<IActionHttpMethodProvider> httpMethodProviders = TypeHelper.OfType<IActionHttpMethodProvider>(actionAttributes);
if (httpMethodProviders.Count > )
{
// Get HttpMethod from attributes
foreach (IActionHttpMethodProvider httpMethodSelector in httpMethodProviders)
{
foreach (HttpMethod httpMethod in httpMethodSelector.HttpMethods)
{
supportedHttpMethods.Add(httpMethod);
}
}
}
else
{
// Get HttpMethod from method name convention
for (int i = ; i < _supportedHttpMethodsByConvention.Length; i++)
{
if (methodInfo.Name.StartsWith(_supportedHttpMethodsByConvention[i].Method, StringComparison.OrdinalIgnoreCase))
{
supportedHttpMethods.Add(_supportedHttpMethodsByConvention[i]);
break;
}
}
} if (supportedHttpMethods.Count == )
{
// Use POST as the default HttpMethod
supportedHttpMethods.Add(HttpMethod.Post);
} return supportedHttpMethods;
}
private static readonly HttpMethod[] _supportedHttpMethodsByConvention =
{
HttpMethod.Get,
HttpMethod.Post,
HttpMethod.Put,
HttpMethod.Delete,
HttpMethod.Head,
HttpMethod.Options,
new HttpMethod("PATCH")
};
GetSupportedHttpMethods判断当前action支持的请求类型,首先读取HttpMethod attributes,如果没有我们就读取action的name(Get,Post,Put,Delete,Options),所以put 方法支持put httpmethod。实在没有httpmethod就添加默认的post。
现在我们来看看_cacheListVerbs里面放的是什么东西?
private readonly HttpMethod[] _cacheListVerbKinds = new HttpMethod[] { HttpMethod.Get, HttpMethod.Put, HttpMethod.Post };
private ReflectedHttpActionDescriptor[] FindActionsForVerbWorker(HttpMethod verb)
{
List<ReflectedHttpActionDescriptor> listMethods = new List<ReflectedHttpActionDescriptor>();
foreach (ReflectedHttpActionDescriptor descriptor in _actionDescriptors)
{
if (descriptor.SupportedHttpMethods.Contains(verb))
{
listMethods.Add(descriptor);
}
}
return listMethods.ToArray();
}
到这里么知道_cacheListVerbs里面放的就是Get,Put,Post对应的action,方便后面通过http request type来查找action。
现在action list已经准备好了,然后确定该调用哪个了?在ActionSelectorCacheItem类里面有SelectAction。主要逻辑如下:
string actionName;
bool useActionName = controllerContext.RouteData.Values.TryGetValue(ActionRouteKey, out actionName);
ReflectedHttpActionDescriptor[] actionsFoundByHttpMethods;
HttpMethod incomingMethod = controllerContext.Request.Method;
// First get an initial candidate list.
if (useActionName)
{
.......................................
}
else
{
// No {action} parameter, infer it from the verb.
actionsFoundByHttpMethods = FindActionsForVerb(incomingMethod);
}
// Throws HttpResponseException with MethodNotAllowed status because no action matches the Http Method
if (actionsFoundByHttpMethods.Length == 0)
{
throw new HttpResponseException(controllerContext.Request.CreateErrorResponse(
HttpStatusCode.MethodNotAllowed,
Error.Format(SRResources.ApiControllerActionSelector_HttpMethodNotSupported, incomingMethod)));
}
// Make sure the action parameter matches the route and query parameters. Overload resolution logic is applied when needed.
IEnumerable<ReflectedHttpActionDescriptor> actionsFoundByParams = FindActionUsingRouteAndQueryParameters(controllerContext, actionsFoundByHttpMethods, useActionName);
首先从路由里面获取actionname,restful请求地址都不含有actionname, 那么就从请求type里面获取action了,即这里的FindActionsForVerb方法,该方法首先从_cacheListVerbs里面找,如果没有找到再在当前control的所有action里面找,比如Delete,Options在_cacheListVerbs是没有的。 如果通过FindActionsForVerb找到的action是多个,那么久需要通过FindActionUsingRouteAndQueryParameters方法来过滤了,该方法首先读取route和query参数,查看是否满足action需要的参数。
如这里的get action,如果请求地址是http://localhost:6221/api/values 这个,那么Get(int id)肯定要被过滤掉,因为它需要参数id,但是这里没有参数id,所以只能返回Get() 了。如果地址http://localhost:6221/api/values/1的话,那么这里的2个action都满足条件 ,我们就取参数多的那个action。
if (actionsFound.Count() > 1)
{
// select the results that match the most number of required parameters
actionsFound = actionsFound
.GroupBy(descriptor => _actionParameterNames[descriptor].Length)
.OrderByDescending(g => g.Key)
.First();
}
到这里大家就应该知道后台是如何获取action的了吧。一句话,把Request.Method作为actionname。
2.浏览器跨域问题。
其实网上已经有很多说明:
在网上找的这张图,并不是所有的跨域请求 都有Options预请求,简单跨域是不需要。
一个简单的请求应该满足如下要求:
1.请求方法为GET,POST 这里是否包含HEAD我不怎么清楚,没测试过,还有HEAD我实际也没有用到
2.请求方法中没有设置请求头(Accept, Accept-Language, Content-Language, Content-Type除外)如果设置了Content-Type头,其值为application/x-www-form-urlencoded, multipart/form-data或 text/plain
常用的复杂请求是:发送PUT
、DELETE
等HTTP动作,或者发送Content-Type: application/json
的内容,来看看预请求的请求头和返回头:
Access-Control-Allow-Origin
(必含)- 不可省略,否则请求按失败处理。该项控制数据的可见范围,如果希望数据对任何人都可见,可以填写“*”。Access-Control-Allow-Methods
(必含) – 这是对预请求当中Access-Control-Request-Method
的回复,这一回复将是一个以逗号分隔的列表。尽管客户端或许只请求某一方法,但服务端仍然可以返回所有允许的方法,以便客户端将其缓存。Access-Control-Allow-Headers
(当预请求中包含Access-Control-Request-Headers
时必须包含) – 这是对预请求当中Access-Control-Request-Headers
的回复,和上面一样是以逗号分隔的列表,可以返回所有支持的头部。Access-Control-Allow-Credentials(可选) – 该项标志着请求当中是否包含cookies信息,只有一个可选值:
true
(必为小写)。如果不包含cookies,请略去该项,而不是填写false
。这一项与XmlHttpRequest2
对象当中的withCredentials
属性应保持一致,即withCredentials
为true
时该项也为true
;withCredentials
为false
时,省略该项不写。反之则导致请求失败。Access-Control-Max-Age
(可选) – 以秒为单位的缓存时间。预请求的的发送并非免费午餐,允许时应当尽可能缓存。
一旦预回应如期而至,所请求的权限也都已满足,则实际请求开始发送。
Credentials
在跨域请求中,默认情况下,HTTP Authentication信息,Cookie头以及用户的SSL证书无论在预检请求中或是在实际请求都是不会被发送的。但是,通过设置XMLHttpRequest的credentials为true,就会启用认证信息机制。
虽然简单请求还是不需要发送预检请求,但是此时判断请求是否成功需要额外判断Access-Control-Allow-Credentials,如果Access-Control-Allow-Credentials为false,请求失败。
十分需要注意的的一点就是此时Access-Control-Allow-Origin不能为通配符"*"(真是便宜了一帮偷懒的程序员),如果Access-Control-Allow-Origin是通配符"*"的话,仍将认为请求失败
即便是失败的请求,如果返回头中有Set-Cookie的头,浏览器还是会照常设置Cookie。
有不当之处欢迎拍砖。下载地址 http://download.csdn.net/detail/dz45693/9486586
Asp.net WebAPi Restful 的实现和跨域的更多相关文章
- 使Asp.net WebApi支持JSONP和Cors跨域访问
1.服务端处理 同源策略(Same Origin Policy)的存在导致了“源”自A的脚本只能操作“同源”页面的DOM,“跨源”操作来源于B的页面将会被拒绝.同源策略以及跨域资源共享在大部分情况下针 ...
- asp.net webAPI POST方法的CORS跨域问题
端口不同会判断为不同域 Method Not Allowed . web.config中设定·customHeaders 错误变化为 原因‘ post方法使用前会有一次OPTION方法的请求’ 解决: ...
- C# ASP.NET MVC/WebApi 或者 ASP.NET CORE 最简单高效的跨域设置
概述 前面写了一篇:<C# ASP.NET WebApi 跨域设置>的文章,主要针对 ASP.NET WebApi 项目. 今天遇到 ASP.NET MVC 项目也需要设置跨域,否则浏览器 ...
- 在ASP.NET 5应用程序中的跨域请求功能详解
在ASP.NET 5应用程序中的跨域请求功能详解 浏览器安全阻止了一个网页中向另外一个域提交请求,这个限制叫做同域策咯(same-origin policy),这组织了一个恶意网站从另外一个网站读取敏 ...
- Taurus.MVC 2.2 开源发布:WebAPI 功能增强(请求跨域及Json转换)
背景: 1:有用户反馈了关于跨域请求的问题. 2:有用户反馈了参数获取的问题. 3:JsonHelper的增强. 在综合上面的条件下,有了2.2版本的更新,也因此写了此文. 开源地址: https:/ ...
- 在ASP.NET Web API中实现CORS(跨域资源共享)
默认情况下,是不允许网页从不同的域访问服务器资源的,访问遵循"同源"策略的原则. 会遇到如下的报错: XMLHttpRequest cannot load http://local ...
- .net core3.1 webapi + vue.js + axios实现跨域
我所要讲述的是,基于.net core3.1环境下的webapi项目,如何去使用axios对接前端的vue项目 既然谈到axios,这里贴出axios的官方文档地址: http://www.axios ...
- Restful based service 的跨域调用
1.关于跨域, w3c的官方文档:https://www.w3.org/TR/cors/ 2.有时间再整理吧. <html> <head> <script src=&qu ...
- [翻译]创建ASP.NET WebApi RESTful 服务(8)
本章讨论创建安全的WebApi服务,到目前为止,我们实现的API都是基于未加密的HTTP协议,大家都知道在Web中传递身份信息必须通过HTTPS,接下来我们来实现这一过程. 使用HTTPS 其实可以通 ...
随机推荐
- 算法初级面试题01——认识时间复杂度、对数器、 master公式计算时间复杂度、小和问题和逆序对问题
虽然以前学过,再次回顾还是有别样的收获~ 认识时间复杂度 常数时间的操作:一个操作如果和数据量没有关系,每次都是固定时间内完成的操作,叫做常数操作. 时间复杂度为一个算法流程中,常数操作数量的指标.常 ...
- qq sid qq sid 是什么 qq sid 怎么用
韩梦飞沙 韩亚飞 313134555@qq.com yue31313 han_meng_fei_sha ======= qq sid qq sid 是什么 qq sid 怎么用 ===== ...
- android 进程间通信 messenger 是什么 binder 跟 aidl 区别 intent 进程间 通讯? android 消息机制 进程间 android 进程间 可以用 handler么 messenger 与 handler 机制 messenger 机制 是不是 就是 handler 机制 或 , 是不是就是 消息机制 android messenge
韩梦飞沙 韩亚飞 313134555@qq.com yue31313 han_meng_fei_sha messenger 是什么 binder 跟 aidl 区别 intent 进程间 通讯 ...
- BZOJ4042 : [Cerc2014] parades
设f[x]为x子树里能选的最多的路径数,h[x]为x子树里往上走的点的集合,且不与x子树内的最优解冲突 首先f[x]=sum(f[son]) 若h[son]与x可以直接匹配,则匹配上,f[x]++ 然 ...
- ROS知识(23)——行为树Behavio Tree原理
机器人的复杂行为的控制结构CA(Contrl Architecture)通常使用有限状态机来实现,例如ROS提供的smach.行为树是另外一种实现机器人控制的方法,ROS下代表的开源库有pi_tree ...
- java判断集合是否重复的一种便捷方法
内容来自其它网站,感谢原作者! import java.util.ArrayList; import java.util.HashSet; import java.util.List; /** * 通 ...
- Jedis使用总结【pipeline】【分布式的id生成器】【分布式锁【watch】【multi】】【redis分布式】(转)
前段时间细节的了解了Jedis的使用,Jedis是redis的java版本的客户端实现.本文做个总结,主要分享如下内容: [pipeline][分布式的id生成器][分布式锁[watch][multi ...
- 使用Puppeteer进行数据抓取(二)——Page对象
page对象是puppeteer最常用的对象,它可以认为是chrome的一个tab页,主要的页面操作都是通过它进行的.Google的官方文档详细介绍了page对象的使用,这里我只是简单的小结一下. 客 ...
- Dynamic-Link Library Redirection
Dynamic-Link Library Redirection Applications can depend on a specific version of a shared DLL and s ...
- 网站前端优化技术 BigPipe分块处理技术
前端优化已经到极致了么?业务还在为看到不停的而揪心么?还在为2秒率不达标苦恼么? 好吧我知道答案,大家一如既往的烦恼中... 那么接下来我们看看,facebook,淘宝,人人网,一淘都是怎么做前端优化 ...