现在实际开发中用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 = 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"} }; } /// <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) + 1; _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": 4, "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": 1, "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 > 0) { // 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 = 0; i < _supportedHttpMethodsByConvention.Length; i++) { if (methodInfo.Name.StartsWith(_supportedHttpMethodsByConvention[i].Method, StringComparison.OrdinalIgnoreCase)) { supportedHttpMethods.Add(_supportedHttpMethodsByConvention[i]); break; } } } if (supportedHttpMethods.Count == 0) { // 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") };