热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Asp.netWebAPiRestful的实现和跨域

现在实际开发中用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 _userList;static ValuesController(){_userList = new List{new Users {UserID = 1, UserName = "zzl", UserEmail = "bfyxzls@sina.com"},new Users {UserID = 2, UserName = "Spiderman", UserEmail = "Spiderman@cnblogs.com"},new Users {UserID = 3, UserName = "Batman", UserEmail = "Batman@cnblogs.com"}};}///

/// User Data List/// /// /// 得到列表对象/// /// public IEnumerable Get(){return _userList;}/// /// 得到一个实体,根据主键/// /// /// public Users Get(int id){return _userList.FirstOrDefault();// (i => i.UserID == id);
}/// /// 添加/// /// 表单对象,它是唯一的/// public Users Post([FromBody] Users entity){entity.UserID = _userList.Max(x => x.UserID) + 1;_userList.Add(entity);return entity;}/// /// 更新/// /// 主键/// 表单对象,它是唯一的/// 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;}/// /// 删除/// /// 主键/// 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:

WebAPI的配置:

"false" />"Access-Control-Allow-Origin" value="*" />"Access-Control-Allow-Headers" value="Content-Type" />"Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />

首先说明一下,配置中的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 {
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 {
_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(() => InitializeActionExecutor(_methodInfo));
_attrCached = _methodInfo.GetCustomAttributes(inherit: true);
CacheAttrsIActionMethodSelector = _attrCached.OfType().ToArray();
_actionName = GetActionName(_methodInfo, _attrCached);
_supportedHttpMethods = GetSupportedHttpMethods(_methodInfo, _attrCached);
}

private static Collection GetSupportedHttpMethods(MethodInfo methodInfo, object[] actionAttributes){Collection supportedHttpMethods &#61; new Collection();ICollection httpMethodProviders &#61; TypeHelper.OfType(actionAttributes);if (httpMethodProviders.Count > 0){// Get HttpMethod from attributesforeach (IActionHttpMethodProvider httpMethodSelector in httpMethodProviders){foreach (HttpMethod httpMethod in httpMethodSelector.HttpMethods){supportedHttpMethods.Add(httpMethod);}}}else{// Get HttpMethod from method name convention for (int i &#61; 0; i <_supportedHttpMethodsByConvention.Length; i&#43;&#43;){if (methodInfo.Name.StartsWith(_supportedHttpMethodsByConvention[i].Method, StringComparison.OrdinalIgnoreCase)){supportedHttpMethods.Add(_supportedHttpMethodsByConvention[i]);break;}}}if (supportedHttpMethods.Count &#61;&#61; 0){// Use POST as the default HttpMethod
supportedHttpMethods.Add(HttpMethod.Post);}return supportedHttpMethods;}private static readonly HttpMethod[] _supportedHttpMethodsByConvention &#61; { HttpMethod.Get, HttpMethod.Post, HttpMethod.Put, HttpMethod.Delete, HttpMethod.Head, HttpMethod.Options, new HttpMethod("PATCH") };

View Code

GetSupportedHttpMethods判断当前action支持的请求类型&#xff0c;首先读取HttpMethod attributes&#xff0c;如果没有我们就读取action的name&#xff08;Get,Post,Put,Delete,Options&#xff09;&#xff0c;所以put 方法支持put httpmethod。实在没有httpmethod就添加默认的post。

现在我们来看看_cacheListVerbs里面放的是什么东西&#xff1f; 

private readonly HttpMethod[] _cacheListVerbKinds &#61; new HttpMethod[] { HttpMethod.Get, HttpMethod.Put, HttpMethod.Post };

private ReflectedHttpActionDescriptor[] FindActionsForVerbWorker(HttpMethod verb)
{
List listMethods &#61; new List();

foreach (ReflectedHttpActionDescriptor descriptor in _actionDescriptors)
{
if (descriptor.SupportedHttpMethods.Contains(verb))
{
listMethods.Add(descriptor);
}
}

return listMethods.ToArray();
}

到这里么知道_cacheListVerbs里面放的就是Get&#xff0c;Put&#xff0c;Post对应的action&#xff0c;方便后面通过http request type来查找action。

现在action list已经准备好了&#xff0c;然后确定该调用哪个了&#xff1f;在ActionSelectorCacheItem类里面有SelectAction。主要逻辑如下&#xff1a;

string actionName;
bool useActionName &#61; controllerContext.RouteData.Values.TryGetValue(ActionRouteKey, out actionName);

ReflectedHttpActionDescriptor[] actionsFoundByHttpMethods;

HttpMethod incomingMethod &#61; controllerContext.Request.Method;

// First get an initial candidate list.
if (useActionName)
{
.......................................
}
else
{
// No {action} parameter, infer it from the verb.
actionsFoundByHttpMethods &#61; FindActionsForVerb(incomingMethod);
}

// Throws HttpResponseException with MethodNotAllowed status because no action matches the Http Method
if (actionsFoundByHttpMethods.Length &#61;&#61; 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 actionsFoundByParams &#61; FindActionUsingRouteAndQueryParameters(controllerContext, actionsFoundByHttpMethods, useActionName);

首先从路由里面获取actionname&#xff0c;restful请求地址都不含有actionname&#xff0c; 那么就从请求type里面获取action了&#xff0c;即这里的FindActionsForVerb方法&#xff0c;该方法首先从_cacheListVerbs里面找&#xff0c;如果没有找到再在当前control的所有action里面找&#xff0c;比如Delete,Options在_cacheListVerbs是没有的。 如果通过FindActionsForVerb找到的action是多个&#xff0c;那么久需要通过FindActionUsingRouteAndQueryParameters方法来过滤了&#xff0c;该方法首先读取route和query参数&#xff0c;查看是否满足action需要的参数。

如这里的get action&#xff0c;如果请求地址是http://localhost:6221/api/values 这个&#xff0c;那么Get(int id)肯定要被过滤掉&#xff0c;因为它需要参数id&#xff0c;但是这里没有参数id&#xff0c;所以只能返回Get() 了。如果地址http://localhost:6221/api/values/1的话&#xff0c;那么这里的2个action都满足条件 &#xff0c;我们就取参数多的那个action。

if (actionsFound.Count() > 1)
{
// select the results that match the most number of required parameters
actionsFound &#61; actionsFound
.GroupBy(descriptor &#61;> _actionParameterNames[descriptor].Length)
.OrderByDescending(g &#61;> g.Key)
.First();
}

到这里大家就应该知道后台是如何获取action的了吧。一句话&#xff0c;把Request.Method作为actionname

2.浏览器跨域问题。

其实网上已经有很多说明&#xff1a;

详解XMLHttpRequest的跨域资源共享

Angular通过CORS实现跨域方案

利用CORS实现跨域请求

在网上找的这张图&#xff0c;并不是所有的跨域请求 都有Options预请求&#xff0c;简单跨域是不需要。

一个简单的请求应该满足如下要求&#xff1a;

1.请求方法为GET&#xff0c;POST 这里是否包含HEAD我不怎么清楚&#xff0c;没测试过&#xff0c;还有HEAD我实际也没有用到
2.请求方法中没有设置请求头(Accept, Accept-Language, Content-Language, Content-Type除外)如果设置了Content-Type头&#xff0c;其值为application/x-www-form-urlencoded, multipart/form-data或 text/plain

常用的复杂请求是&#xff1a;发送PUTDELETE等HTTP动作&#xff0c;或者发送Content-Type: application/json的内容&#xff0c;来看看预请求的请求头和返回头&#xff1a;

  • Access-Control-Allow-Origin&#xff08;必含&#xff09;- 不可省略&#xff0c;否则请求按失败处理。该项控制数据的可见范围&#xff0c;如果希望数据对任何人都可见&#xff0c;可以填写“*”。
  • Access-Control-Allow-Methods&#xff08;必含&#xff09; – 这是对预请求当中Access-Control-Request-Method的回复&#xff0c;这一回复将是一个以逗号分隔的列表。尽管客户端或许只请求某一方法&#xff0c;但服务端仍然可以返回所有允许的方法&#xff0c;以便客户端将其缓存。
  • Access-Control-Allow-Headers&#xff08;当预请求中包含Access-Control-Request-Headers时必须包含&#xff09; – 这是对预请求当中Access-Control-Request-Headers的回复&#xff0c;和上面一样是以逗号分隔的列表&#xff0c;可以返回所有支持的头部。
  • Access-Control-Allow-Credentials&#xff08;可选&#xff09; – 该项标志着请求当中是否包含COOKIEs信息&#xff0c;只有一个可选值&#xff1a;true&#xff08;必为小写&#xff09;。如果不包含COOKIEs&#xff0c;请略去该项&#xff0c;而不是填写false。这一项与XmlHttpRequest2对象当中的withCredentials属性应保持一致&#xff0c;即withCredentialstrue时该项也为true&#xff1b;withCredentialsfalse时&#xff0c;省略该项不写。反之则导致请求失败。
  • Access-Control-Max-Age&#xff08;可选&#xff09; – 以秒为单位的缓存时间。预请求的的发送并非免费午餐&#xff0c;允许时应当尽可能缓存。

一旦预回应如期而至&#xff0c;所请求的权限也都已满足&#xff0c;则实际请求开始发送。

Credentials

在跨域请求中&#xff0c;默认情况下&#xff0c;HTTP Authentication信息&#xff0c;COOKIE头以及用户的SSL证书无论在预检请求中或是在实际请求都是不会被发送的。但是&#xff0c;通过设置XMLHttpRequest的credentials为true&#xff0c;就会启用认证信息机制。

虽然简单请求还是不需要发送预检请求&#xff0c;但是此时判断请求是否成功需要额外判断Access-Control-Allow-Credentials&#xff0c;如果Access-Control-Allow-Credentials为false&#xff0c;请求失败。

十分需要注意的的一点就是此时Access-Control-Allow-Origin不能为通配符"*"(真是便宜了一帮偷懒的程序员)&#xff0c;如果Access-Control-Allow-Origin是通配符"*"的话&#xff0c;仍将认为请求失败

即便是失败的请求&#xff0c;如果返回头中有Set-COOKIE的头&#xff0c;浏览器还是会照常设置COOKIE。

有不当之处欢迎拍砖。下载地址 http://download.csdn.net/detail/dz45693/9486586



推荐阅读
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • 开发笔记:Java是如何读取和写入浏览器Cookies的
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 本文详细介绍了GetModuleFileName函数的用法,该函数可以用于获取当前模块所在的路径,方便进行文件操作和读取配置信息。文章通过示例代码和详细的解释,帮助读者理解和使用该函数。同时,还提供了相关的API函数声明和说明。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 导出功能protectedvoidbtnExport(objectsender,EventArgse){用来打开下载窗口stringfileName中 ... [详细]
  • iOS Swift中如何实现自动登录?
    本文介绍了在iOS Swift中如何实现自动登录的方法,包括使用故事板、SWRevealViewController等技术,以及解决用户注销后重新登录自动跳转到主页的问题。 ... [详细]
author-avatar
爱你一辈子2502860605
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有