• Restful Api 最佳实践


    重新梳理了下基于net webapi 搭建restful api,也重新翻查了一些资料:

    RESTful API 设计最佳实践

    https://www.zcfy.cc/article/restful-api-design-best-practices-in-a-nutshell

    查询(Query)分析总结

    http://www.cnblogs.com/xishuai/p/designing-rest-api-uri-query-and-identify.html

    github api url

    {
      "current_user_url": "https://api.github.com/user",
      "current_user_authorizations_html_url": "https://github.com/settings/connections/applications{/client_id}",
      "authorizations_url": "https://api.github.com/authorizations",
      "code_search_url": "https://api.github.com/search/code?q={query}{&page,per_page,sort,order}",
      "commit_search_url": "https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}",
      "emails_url": "https://api.github.com/user/emails",
      "emojis_url": "https://api.github.com/emojis",
      "events_url": "https://api.github.com/events",
      "feeds_url": "https://api.github.com/feeds",
      "followers_url": "https://api.github.com/user/followers",
      "following_url": "https://api.github.com/user/following{/target}",
      "gists_url": "https://api.github.com/gists{/gist_id}",
      "hub_url": "https://api.github.com/hub",
      "issue_search_url": "https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}",
      "issues_url": "https://api.github.com/issues",
      "keys_url": "https://api.github.com/user/keys",
      "notifications_url": "https://api.github.com/notifications",
      "organization_repositories_url": "https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}",
      "organization_url": "https://api.github.com/orgs/{org}",
      "public_gists_url": "https://api.github.com/gists/public",
      "rate_limit_url": "https://api.github.com/rate_limit",
      "repository_url": "https://api.github.com/repos/{owner}/{repo}",
      "repository_search_url": "https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}",
      "current_user_repositories_url": "https://api.github.com/user/repos{?type,page,per_page,sort}",
      "starred_url": "https://api.github.com/user/starred{/owner}{/repo}",
      "starred_gists_url": "https://api.github.com/gists/starred",
      "team_url": "https://api.github.com/teams",
      "user_url": "https://api.github.com/users/{user}",
      "user_organizations_url": "https://api.github.com/user/orgs",
      "user_repositories_url": "https://api.github.com/users/{user}/repos{?type,page,per_page,sort}",
      "user_search_url": "https://api.github.com/search/users?q={query}{&page,per_page,sort,order}"
    }
    

    总结

    具体概念及设计原则,参见以上博客比我写的更好。主要有几个点需要注意下:

    查询(get)

    当多参数查询时,大多方案都是通过url,需要考虑url长度限制,否则需要考虑其它方案:

     "user_search_url": "https://api.github.com/search/users?q={query}{&page,per_page,sort,order}"
    

    HttpPut请求总是405

    http://www.cnblogs.com/shanyou/archive/2012/03/23/2414252.html

            [HttpPut]
            public string EidtOrder(string id,[FromBody]FooBar m)
            {
                return id+ Newtonsoft.Json.JsonConvert.SerializeObject(m);
            }
    

      

    针对api多版本管理路由

    当然通过Route注解指定action版本是可以的,但是特繁琐。通用的解决方案是自定义版本选择器替换掉原有的HttpControllerSelector即可。

    namespace CYTS.Ticket.Expo.Api.App_Start
    {
        public class VersionHttpControllerSelector : IHttpControllerSelector
        {
            private const string VersionKey = "version";
            private const string ControllerKey = "controller";
    
            private readonly HttpConfiguration _configuration;
            private readonly Lazy<Dictionary<string, HttpControllerDescriptor>> _controllers;
            private readonly HashSet<string> _duplicates;
    
            public VersionHttpControllerSelector(HttpConfiguration config)
            {
                _configuration = config;
                _duplicates = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
                _controllers = new Lazy<Dictionary<string, HttpControllerDescriptor>>(InitializeControllerDictionary);
            }
    
            private Dictionary<string, HttpControllerDescriptor> InitializeControllerDictionary()
            {
                var dictionary = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);
    
                // Create a lookup table where key is "namespace.controller". The value of "namespace" is the last
                // segment of the full namespace. For example:
                // MyApplication.Controllers.V1.ProductsController => "V1.Products"
                IAssembliesResolver assembliesResolver = _configuration.Services.GetAssembliesResolver();
                IHttpControllerTypeResolver controllersResolver = _configuration.Services.GetHttpControllerTypeResolver();
    
                ICollection<Type> controllerTypes = controllersResolver.GetControllerTypes(assembliesResolver);
    
                foreach (Type t in controllerTypes)
                {
                    var segments = t.Namespace.Split(Type.Delimiter);
    
                    // For the dictionary key, strip "Controller" from the end of the type name.
                    // This matches the behavior of DefaultHttpControllerSelector.
                    var controllerName = t.Name.Remove(t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length);
                    string version = segments[segments.Length - 1];
                    var key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", version, controllerName);
                    if (version == "Controllers")
                    {
                        key = String.Format(CultureInfo.InvariantCulture, "{0}", controllerName);
                    }
                    // Check for duplicate keys.
                    if (dictionary.Keys.Contains(key))
                    {
                        _duplicates.Add(key);
                    }
                    else
                    {
                        dictionary[key] = new HttpControllerDescriptor(_configuration, t.Name, t);
                    }
                }
    
                // Remove any duplicates from the dictionary, because these create ambiguous matches. 
                // For example, "Foo.V1.ProductsController" and "Bar.V1.ProductsController" both map to "v1.products".
                foreach (string s in _duplicates)
                {
                    dictionary.Remove(s);
                }
                return dictionary;
            }
    
            // Get a value from the route data, if present.
            private static T GetRouteVariable<T>(IHttpRouteData routeData, string name)
            {
                object result = null;
                if (routeData.Values.TryGetValue(name, out result))
                {
                    return (T)result;
                }
                return default(T);
            }
    
            public HttpControllerDescriptor SelectController(HttpRequestMessage request)
            {
                IHttpRouteData routeData = request.GetRouteData();
                if (routeData == null)
                {
                    throw new HttpResponseException(HttpStatusCode.NotFound);
                }
    
                // Get the version and controller variables from the route data.
                string version = GetRouteVariable<string>(routeData, VersionKey);
                if (string.IsNullOrEmpty(version))
                {
                    version = GetVersionFromHTTPHeaderAndAcceptHeader(request);
                }
                string controllerName = GetRouteVariable<string>(routeData, ControllerKey);
                if (controllerName == null)
                {
                    throw new HttpResponseException(HttpStatusCode.NotFound);
                }
    
                // Find a matching controller.
                string key = String.Format(CultureInfo.InvariantCulture, "{0}", controllerName);
                if (!string.IsNullOrEmpty(version))
                {
                    key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", version, controllerName);
                }
    
                HttpControllerDescriptor controllerDescriptor;
                if (_controllers.Value.TryGetValue(key, out controllerDescriptor))
                {
                    return controllerDescriptor;
                }
                else if (_duplicates.Contains(key))
                {
                    throw new HttpResponseException(
                        request.CreateErrorResponse(HttpStatusCode.InternalServerError,
                        "Multiple controllers were found that match this request."));
                }
                else
                {
                    throw new HttpResponseException(HttpStatusCode.NotFound);
                }
            }
    
            public IDictionary<string, HttpControllerDescriptor> GetControllerMapping()
            {
                return _controllers.Value;
            }
            private string GetVersionFromHTTPHeaderAndAcceptHeader(HttpRequestMessage request)
            {
                if (request.Headers.Contains(VersionKey))
                {
                    var versionHeader = request.Headers.GetValues(VersionKey).FirstOrDefault();
                    if (versionHeader != null)
                    {
                        return versionHeader;
                    }
                }
                var acceptHeader = request.Headers.Accept;
                foreach (var mime in acceptHeader)
                {
                    if (mime.MediaType == "application/json" || mime.MediaType == "text/html")
                    {
                        var version = mime.Parameters
                                         .Where(v => v.Name.Equals(VersionKey, StringComparison.OrdinalIgnoreCase))
                                          .FirstOrDefault();
    
                        if (version != null)
                        {
                            return version.Value;
                        }
                        return string.Empty;
                    }
                }
                return string.Empty;
            }
        }
    }
    

    路由配置:

    config.Routes.MapHttpRoute(
                       name: "DefaultVersion",
                       routeTemplate: "api/{version}/{controller}/{id}",
                       defaults: new { id = RouteParameter.Optional }
                   );
    

    将自定义的VersionControllerSelector注册到系统中:

            config.Services.Replace(typeof(IHttpControllerSelector), new VersionHttpControllerSelector(config));

    在Controller创建版本:

    访问:

  • 相关阅读:
    LeetCode 167. 两数之和 II
    LeetCode 97. 交错字符串
    LeetCode 35. 搜索插入位置
    LeetCode 120. 三角形最小路径和
    LeetCode 350. 两个数组的交集 II
    LeetCode 174. 地下城游戏
    LeetCode 315. 计算右侧小于当前元素的个数
    LeetCode 309. 最佳买卖股票时机含冷冻期
    面试题 17.13. 恢复空格
    去除字符串首尾空格
  • 原文地址:https://www.cnblogs.com/tianboblog/p/9936569.html
Copyright © 2020-2023  润新知