现在实际开发中用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.浏览器跨域问题。

其实网上已经有很多说明:

详解XMLHttpRequest的跨域资源共享

Angular通过CORS实现跨域方案

利用CORS实现跨域请求

在网上找的这张图,并不是所有的跨域请求 都有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

常用的复杂请求是:发送PUTDELETE等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属性应保持一致,即withCredentialstrue时该项也为truewithCredentialsfalse时,省略该项不写。反之则导致请求失败。
  • 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 的实现和跨域的更多相关文章

  1. 使Asp.net WebApi支持JSONP和Cors跨域访问

    1.服务端处理 同源策略(Same Origin Policy)的存在导致了“源”自A的脚本只能操作“同源”页面的DOM,“跨源”操作来源于B的页面将会被拒绝.同源策略以及跨域资源共享在大部分情况下针 ...

  2. asp.net webAPI POST方法的CORS跨域问题

    端口不同会判断为不同域 Method Not Allowed . web.config中设定·customHeaders 错误变化为 原因‘ post方法使用前会有一次OPTION方法的请求’ 解决: ...

  3. C# ASP.NET MVC/WebApi 或者 ASP.NET CORE 最简单高效的跨域设置

    概述 前面写了一篇:<C# ASP.NET WebApi 跨域设置>的文章,主要针对 ASP.NET WebApi 项目. 今天遇到 ASP.NET MVC 项目也需要设置跨域,否则浏览器 ...

  4. 在ASP.NET 5应用程序中的跨域请求功能详解

    在ASP.NET 5应用程序中的跨域请求功能详解 浏览器安全阻止了一个网页中向另外一个域提交请求,这个限制叫做同域策咯(same-origin policy),这组织了一个恶意网站从另外一个网站读取敏 ...

  5. Taurus.MVC 2.2 开源发布:WebAPI 功能增强(请求跨域及Json转换)

    背景: 1:有用户反馈了关于跨域请求的问题. 2:有用户反馈了参数获取的问题. 3:JsonHelper的增强. 在综合上面的条件下,有了2.2版本的更新,也因此写了此文. 开源地址: https:/ ...

  6. 在ASP.NET Web API中实现CORS(跨域资源共享)

    默认情况下,是不允许网页从不同的域访问服务器资源的,访问遵循"同源"策略的原则. 会遇到如下的报错: XMLHttpRequest cannot load http://local ...

  7. .net core3.1 webapi + vue.js + axios实现跨域

    我所要讲述的是,基于.net core3.1环境下的webapi项目,如何去使用axios对接前端的vue项目 既然谈到axios,这里贴出axios的官方文档地址: http://www.axios ...

  8. Restful based service 的跨域调用

    1.关于跨域, w3c的官方文档:https://www.w3.org/TR/cors/ 2.有时间再整理吧. <html> <head> <script src=&qu ...

  9. [翻译]创建ASP.NET WebApi RESTful 服务(8)

    本章讨论创建安全的WebApi服务,到目前为止,我们实现的API都是基于未加密的HTTP协议,大家都知道在Web中传递身份信息必须通过HTTPS,接下来我们来实现这一过程. 使用HTTPS 其实可以通 ...

随机推荐

  1. jquery multi-select 实例demo

    运行效果: 其他的不多说了,都是用的jquery.multiSelect.js组件实现的,直接看代码吧 代码下载地址: http://download.csdn.net/detail/ajavabir ...

  2. [同步脚本]mysql-elasticsearch同步

    公司项目搜索部分用的elasticsearch,那么这两个之间的数据同步就是一个问题. 网上找了几个包,但都有各自的缺点,最后决定还是自己写一个脚本,大致思路如下: 1.在死循环中不断的select指 ...

  3. 【BZOJ 1180】 (LCT)

    1180: [CROATIAN2009]OTOCI Time Limit: 50 Sec  Memory Limit: 162 MBSubmit: 1078  Solved: 662 Descript ...

  4. SPFA算法 O(kE)

    主要思想是:     初始时将起点加入队列.每次从队列中取出一个元素,并对所有与它相邻的点进行修改,若某个相邻的点修改成功,则将其入队.直到队列为空时算法结束.     这个算法,简单的说就是队列优化 ...

  5. Redis和数据库 数据同步问题

    Redis和数据库同步问题 缓存充当数据库 比如说Session这种访问非常频繁的数据,就适合采用这种方案:当然了,既然没有涉及到数据库,那么也就不会存在一致性问题: 缓存充当数据库热点缓存 读操作 ...

  6. Centos7 安装 ActiveMQ 5.15.1

    环境 [root@node1 ~]# cat /etc/redhat-release CentOS Linux release (Core) [root@node1 ~]# uname -r -.el ...

  7. ASP.NET Core 中的框架级依赖注入

    https://tech.io/playgrounds/5040/framework-level-dependency-injection-with-asp-net-core 作者: Gunnar P ...

  8. 接口开发-基于SpringBoot创建基础框架

    说到接口开发,能想到的开发语言有很多种,像什么Java啊..NET啊.PHP啊.NodeJS啊,太多可以用.为什么选择Java,究其原因,最后只有一个解释,那就是“学Java的人多,人员招聘范围大,有 ...

  9. [Go] sync.Once 的用法

    sync.Once.Do(f func()) 是一个非常有意思的东西,能保证 once 只执行一次,无论你是否更换 once.Do(xx) 这里的方法,这个 sync.Once块 只会执行一次. pa ...

  10. ajax jquery 异步表单验证

    文件目录: html代码: <html> <head> <title>异步表单验证</title> <script type='text/java ...