1,创建Framework框架下的WebApi

1.1,添加项目模板

在这里插入图片描述

1.2,创建空项目

勿选择WebApi模板,因该模板是基于MVC,若选择该模板将创建很多与MVC相关的文件。

在这里插入图片描述

1.3,选中项目中的Controllers文件夹创建控制器。

在这里插入图片描述

在这里插入图片描述

2,配置

2.1,配置项目的URL

项目->右键属性->Web->项目URL配置项目启动的IPAddressPort

在这里插入图片描述

2.2,添加支持的内容类型

打开 App_Start文件下的WebApiConfig.cs

  public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API 配置和服务

            // Web API 路由
            config.MapHttpAttributeRoutes();
            //新增支持的内容类型
            config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue(System.Net.Mime.MediaTypeNames.Text.Plain));
            config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("text/html"));

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

3,WebApi参数

参数传递,接收规则(Get,Post均是如此):

  • 普通类型未加特性修饰的参数例如string、int,其参数值来源于查询字符串。
  • 使用[FromBody]修饰的普通类型参数,传递参数时不需要设置key,设置key则接收的值为null
  • 自定义的类未加特性修饰作为参数时,其参数值来源于Body(消息体),而非查询字符串。即使查询字符串设置的参数值,如果消息体未添加值,接收到的也是null
  • 使用[FromUri]修饰的自定义类作为参数时,其参数值来源查询字符串。
  • 使用[FromBody]修饰的自定义类作为参数时,其参数值来源于Body(消息体)。
3.1,返回类型为Void。

返回类型为Void时,返回的状态码为204(No Content)

public void Delete(int id)
        {
        }

在这里插入图片描述

3.2,无参的Get请求
 public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

在这里插入图片描述

3.2,有参的Get请求
3.2.1,通过Id传递参数
  public string Get2(int id)
        {
            return id.ToString();
        }

在这里插入图片描述

3.2.2,通过查询字符串传递参数
 [HttpGet]
        public string Get3(string userName,string roleName)
        {
            return $"{userName}----{roleName}";
        }

在这里插入图片描述

3.2.3,以模型作为参数进行传递
  • 使用[FromUri]进行修饰时([FromUri]只能在形参中使用一次):

    参数值来自于查询字符串

[HttpGet]
        public UserRole Get4([FromUri] UserRole userRole)
        {
            return userRole;
        }

在这里插入图片描述

  • 模型没有任何特性修饰时

    模型参数值来自于Body

 [HttpGet]
        public UserRole Get5( UserRole userRole)
        {
            return userRole;
        }

在这里插入图片描述

  • 使用特性[FromBody]修饰模型参数时([FromBody]只能在形参中使用一次):

    模型参数值来源于Body

 [HttpGet]
        public UserRole Get6([FromBody] UserRole userRole)
        {
            return userRole;
        }

    

在这里插入图片描述

3.3,无参的Post请求
 [HttpPost]
        public IEnumerable<string> Post1()
        {
            return new string[] { "value1", "value2" };
        }

在这里插入图片描述

3.4,有参的Post请求
3.4.1,通过Id传递参数
 [HttpPost]
        public string Post2(int id)
        {
            return id.ToString();
        }

在这里插入图片描述

3.4.2,通过查询字符串传递参数
 [HttpPost]
        public string Post4(string userName, string roleName)
        {
            return $"{userName}----{roleName}";
        }

在这里插入图片描述

3.4.3,传递[FromBody]修饰的普通类型的参数
[HttpPost]
        public string Post3([FromBody] string value)
        {
            return $"{value}----{DateTime.Now}";
        }

参数不能以常规的KeyValue形式传递,而是需要缺省Key,直接传递Value,若以KeyValue进行传递则Value接收到是null

例如以下:

在这里插入图片描述

实际上value获得的参数值为:null

正确做法是:缺省键

在这里插入图片描述

value接收到传递的参数值:aaa

3.4.4,以模型作为参数进行传递
  • 使用[FromUrl]特性修饰时:

    模型参数值来源于查询字符串。

    [HttpPost]
        public UserRole Post5([FromUri] UserRole userRole)
        {
            return userRole;
        }

在这里插入图片描述

由结果截图可知:查询字符串与表单同时传参时,对于使用[FromUrl]特性修饰的模型参数,其参数值来源于查询字符串,而非消息Body的表单

  • 不使用任何特性修饰时:

    模型参数值来源于Body

    [HttpPost]
            public UserRole Post6(UserRole userRole)
            {
                return userRole;
            }
    

    在这里插入图片描述

  • 使用[FromBody]修饰时:

    模型参数来源于Body

  [HttpPost]
        public UserRole Post7([FromBody] UserRole userRole)
        {
            return userRole;
        }

在这里插入图片描述

3.5,路由参数

使用[RoutePrefix]路由前缀特性修饰控制器类,可跳过WebApiConfig.cs中的路由模板规则。

 [RoutePrefix("Role")]//路由前缀通过该可跳过WebApiConfig.cs中的路由模板
    public class RoleController : ApiController
    {
        // GET api/<controller>
        [Route("GetVal")]//自定义路由
        
        public IEnumerable<string> Get(string val1,string val2)
        {
            return new string[] { val1, val2 };
        }
        [Route("{userName}/{roleName}")]
        public string Post3(string userName, string roleName)
        {
            return $"{userName}---{roleName}";
        }
    }
  1. 使用Role/GetVal直接调用Get

      [Route("GetVal")]//自定义路由
            
            public IEnumerable<string> Get(string val1,string val2)
            {
                return new string[] { val1, val2 };
            }
    

    在这里插入图片描述

  2. 使用路由模板获取参数值。

     [Route("{userName}/{roleName}")]
            public string Post3(string userName, string roleName)
            {
                return $"{userName}---{roleName}";
            }
    

    在这里插入图片描述

4,WebAPI多版本管理

4.1,自定义路由实现多版本路由控制

[RoutePrefix("Role/v1")] [RoutePrefix("Role/v2")]

4.2,自定义IHttpControllerSelector实现版本控制
  1. WebApiConfig中的路由改成如下:

     config.Routes.MapHttpRoute(
                    name: "DefaultApiv1",
                    routeTemplate: "api/v1/{controller}/{id}",
                    defaults: new { id = RouteParameter.Optional }
                );
     config.Routes.MapHttpRoute(
                    name: "DefaultApiv2",
                    routeTemplate: "api/v2/{controller}/{id}",
                    defaults: new { id = RouteParameter.Optional }
                );
    
  2. 不同的Controller放在不同的namespace下

    在这里插入图片描述

//v1
namespace MultiVersionControl.Controllers.v1
{
    public class UserController : ApiController
    {
        // GET api/<controller>
        public IEnumerable<string> Get()
        {
           
            return new string[] { "value1", "value2" };
        }
    }
}
//v2
namespace MultiVersionControl.Controllers.v2
{
    public class UserController : ApiController
    {
        // GET api/<controller>
        [HttpGet]
        public IEnumerable<string> Get1()
        {
            return new string[] { "value1", "value2" };
        }
        [HttpGet]
        public IEnumerable<string> Get2(string userName,string roleName)
        {
            return new string[] { userName, roleName };
        }
      
    }
}
  1. 创建派生自DefaultHttpControllerSelector的类,并进行相应配置

    DefaultHttpControllerSelector是实现接口IHttpControllerSelector的类。

 public class HttpControllerSelector : DefaultHttpControllerSelector
    {
        HttpConfiguration configuration;
        public HttpControllerSelector(HttpConfiguration configuration) : base(configuration)
        {
            this.configuration = configuration;
        }
        public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
        {
            
            var pathStr = request.RequestUri.PathAndQuery;
            Match match = Regex.Match(pathStr, @"\/api\/(?<versions>\w+)\/(?<controllerName>\w+)",RegexOptions.IgnoreCase);
            // 示例: controllerName:V1.User
            if (match.Success)
            {
                string key = $"{match.Groups["versions"].Value}.{match.Groups["controllerName"].Value}".ToLower();
                var dic = GetControllerMapping();
                if (dic.Keys.Contains(key))
                {
                    return dic[key];
                }
            }
            //其他类型交由父类处理
            return base.SelectController(request);
         
        }
        public override IDictionary<string, HttpControllerDescriptor> GetControllerMapping()
        {
            Dictionary<string, HttpControllerDescriptor> dic = new Dictionary<string, HttpControllerDescriptor>();
            var assemblies = configuration.Services.GetAssembliesResolver().GetAssemblies();
            //Controllers.v2
            foreach (var assem in assemblies)
            {
                foreach (Type type in assem.GetTypes())
                {
                    if (!type.IsAbstract && type.IsSubclassOf(typeof(ApiController)))
                    {
                        Match match = Regex.Match(type.FullName, @"Controllers\.(?<Name>v\d+\.\w+)Controller",RegexOptions.IgnoreCase);
                        if (match.Success)
                        {
                            string controllerName = match.Groups["Name"].Value?.ToLower();
                            if (controllerName == null) continue;
                            HttpControllerDescriptor httpControllerDescriptor = new HttpControllerDescriptor(configuration, type.Name, type);
                            dic.Add(controllerName, httpControllerDescriptor);
                        }
                    }
                }
            }
            return dic;
        }
    }
  1. 将该选择器注册至配置。

    namespace MultiVersionControl
    {
        public static class WebApiConfig
        {
            public static void Register(HttpConfiguration config)
            {
                // Web API 配置和服务
    
                // Web API 路由
                config.MapHttpAttributeRoutes();
    
                config.Routes.MapHttpRoute(
                    name: "DefaultApiv1",
                    routeTemplate: "api/v1/{controller}/{action}/{id}",
                    defaults: new { id = RouteParameter.Optional }
                );
                config.Routes.MapHttpRoute(
                    name: "DefaultApi",
                    routeTemplate: "api/v2/{controller}/{action}/{id}",
                    defaults: new { id = RouteParameter.Optional }
                );
                //注册控制器选择器
                config.Services.Replace(typeof(IHttpControllerSelector), new HttpControllerSelector(config));
                
               
            }
            
        }
    }
    

5,WebAPI的Filter

5.1,权限认证的过滤
5.1.1,注册**实现接口:IAuthorizationFilter**的类实现过滤。

作用范围:所有的Controller与Action

  1. 定义类。
public class MyAuthorFilter : IAuthorizationFilter
    {
        public bool AllowMultiple => true;

        public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
        {
            //筛选出报文头中的Username
            if(actionContext.Request.Headers.TryGetValues("UserName",out IEnumerable<string> values))
            {
                string userName = values.FirstOrDefault();
                if (userName.Equals("admin", StringComparison.OrdinalIgnoreCase))
                {
                    //验证通过
                    return continuation();
                }
                else
                {
                    return Task.FromResult(new HttpResponseMessage {
                        StatusCode = System.Net.HttpStatusCode.OK, 
                        Content = new StringContent("该用户:"+userName+"无权限"),
                         
                        
                    });
                }
            }
            else
            {
                return Task.FromResult(new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized));
            }

        }
    }
  1. 注册过滤器
namespace WebApiFilter
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API 配置和服务

            // Web API 路由
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
            //添加过滤器
            config.Filters.Add(new MyAuthorFilter());
        }
    }
}
  1. 使用。

    注意:使用[AllowAnonymous]特性不能跳过通过IAuthorizationFilter要求的授权验证。

      public class UserController : ApiController
        {
            // GET api/<controller>
            public IEnumerable<string> Get()
            {
                return new string[] { "value1", "value2" };
            }
    
            // GET api/<controller>/5
           // [AllowAnonymous]//该特性不能跳过实现于IAuthorizationFilter的授权要求
            public string Get(int id)
            {
                return id.ToString();
            }
      }
    

在这里插入图片描述

在这里插入图片描述

5.1.2,使用[Authorize]特性实现过滤

通过派生自[Authorize]特性的自定义特性可以自定义授权验证条件。

[Authorize]特性作用于控制器时,可通过[AllowAnonymous]特性使该控制器下的某个Action跳过授权验证

  1. 自定义派生自AuthorizeAttribute的类。

     public class UserAuthorizationFilter : AuthorizeAttribute
        {
            public override void OnAuthorization(HttpActionContext actionContext)
            {
                base.OnAuthorization(actionContext);
            }
            protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
            {
                //base.HandleUnauthorizedRequest(actionContext);
                actionContext.Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized)
                {
                    Content = new System.Net.Http.StringContent(
                        $"{{\"success\":false,\"msg\":\"没有权限,拒绝访问\"}}",
                        System.Text.Encoding.UTF8,
                        "application/json")
                };
            }
            protected override bool IsAuthorized(HttpActionContext actionContext)
            {
                if (actionContext.Request.Headers.TryGetValues("userName", out IEnumerable<string> values))
                {
                    string userName = values.FirstOrDefault();
                    return (userName?.Equals("admin")) == true;
                }
                else
                {
                    return false;
                }
            }
        }
    
  2. 在控制器上使用该特性。

     [UserAuthorizationFilter]
        public class UserController : ApiController
        {
            // GET api/<controller>
            public IEnumerable<string> Get()
            {
                return new string[] { "value1", "value2" };
            }
        }
    

在这里插入图片描述

在这里插入图片描述

  1. 通过[AllowAnonymous]特性使控制器类的某个操作跳过授权验证。
  [UserAuthorizationFilter]
    public class UserController : ApiController
    {
        // GET api/<controller>
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/<controller>/5
      //  [Authorize]
      [AllowAnonymous]//该操作跳过授权验证
        public string Get(int id)
        {
            return "value";
        }
    }

在这里插入图片描述

5.2,WebAPI异常处理过滤

1,默认情况服务器抛出异常包含了服务器相关信息

 public class UserController : ApiController
    {
        // GET api/<controller>
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/<controller>/5
        public string Get(int id)
        {
            if (id == 10) throw new ArgumentException("参数错误,Id不能为10");
            return id.ToString();
        }
 }

在这里插入图片描述

2,注册实现接口IExceptionFilter的类对异常进行处理

 public class UserController : ApiController
    {
        // GET api/<controller>
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/<controller>/5
        public string Get(int id)
        {
            if (id == 10) throw new ArgumentException("参数错误,Id不能为10");
            return id.ToString();
        }
 }
public class MyExceptionFilter : IExceptionFilter
    {
        public bool AllowMultiple => true;

        public Task ExecuteExceptionFilterAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
        {
            //使用日志记录异常....
            Exception ex = actionExecutedContext.Exception;
//自定义异常返回信息
            actionExecutedContext.Response = new System.Net.Http.HttpResponseMessage {
                StatusCode = System.Net.HttpStatusCode.InternalServerError,
                Content = new System.Net.Http.StringContent($"{{\"success\":false,\"msg\":\"{ex.Message}\"}}", System.Text.Encoding.UTF8, "application/json")
            }
            ;
            return Task.CompletedTask;
        }
    }

注册过滤器

 public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API 配置和服务

            // Web API 路由
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
            //注册异常处理过滤
            config.Filters.Add(new MyExceptionFilter());
        }
    }

在这里插入图片描述

6,接口的安全性问题

6.1,JWT介绍

JWT由三块组成,可以把用户名,用户id等保存都Payload部分

在这里插入图片描述

注意PayloadHeader部分都是Base64编码,可以轻松的Base64解码回来,因此Payload部分约等于是明文,因此不能在Payload中保存不能让他人看到的敏感信息。虽然Payload部分约等于明文,但是不用担心Payload被篡改,因为Signature部分是根据header+payload+secretkey进行加密算出来的,如果payload被篡改,就可以根据Signature解密时候校验。

例如以下计算出来的token:

在这里插入图片描述

在这里插入图片描述

Header部分可直接Base64解码

在这里插入图片描述

Payload部分可直接Base6464解码

在这里插入图片描述

Signature部分为加密算法加密,Base64无法解码

在这里插入图片描述

6.2,安装JWT

在这里插入图片描述

6.3,过期时间

Payload中增加一个名字为exp的值(exp为固定名),值为过期时间(格林时间非本地时间)和1970/1/1 00:00:00相差的秒数,需要注意的是时间是Utc标准时间而不是本地时间。

在这里插入图片描述

6.4,应用示例
public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            var payload = new Dictionary<string, object>
            {
                { "UserId",12},
                {"UserName","admin" },
                //过期时间(格林时间非本地时间)和1970/1/1 00:00:00相差的秒数
                //exp为固定名
                //这里表示这个token的有效时间为10s
                {"exp",DateTime.UtcNow.AddSeconds(10).Subtract(DateTime.Parse("1970/1/1 00:00:00")).TotalSeconds }
            };
            string secret = "12345abcde";//秘钥
            IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
            IJsonSerializer jsonSerializer = new JsonNetSerializer();
            IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
            IJwtEncoder encoder = new JwtEncoder(algorithm, jsonSerializer, urlEncoder);
            var token = encoder.Encode(payload, secret);
            textBox1.Text = token;
        }

        private void button2_Click(object sender, EventArgs e)
        {
            //解密
            IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
            IJsonSerializer jsonSerializer = new JsonNetSerializer();
            IDateTimeProvider dateTimeProvider = new UtcDateTimeProvider();

            IJwtValidator validator = new JwtValidator(jsonSerializer, dateTimeProvider);
            IBase64UrlEncoder base64UrlEncoder = new JwtBase64UrlEncoder();
            IJwtDecoder decoder = new JwtDecoder(jsonSerializer, validator, base64UrlEncoder, algorithm);
            string secret = "12345abcde";//秘钥
            try
            {
                textBox2.Text = decoder.Decode(textBox1.Text,secret);
            }
            catch (TokenExpiredException )
            {
                //该token过期

                MessageBox.Show("Token has expired");
            }
            catch (SignatureVerificationException)
            {
                //该签名无效
                MessageBox.Show("Token has invalid signature");
            }
            
        }
    }

在这里插入图片描述

6.5,注意事项

启用https

  • https可以增加被抓包的难度,所以只要是部署到客户端,必须启用https
  • 篡改请求,比如用户只能看到/user?id=5,但是用户截获请求后修改id从而能够查看其它数据,把所有请求参数和value按照名字排序后拼接在一起,加上AppSerect计算散列值作为Signature,传送过去,服务器在校验一次。
  • 请求重放,重复的发送请求。每次请求的时间都带有当前时间(时间戳),服务器端比较一下如果这个时间和当前时间相差超过一定时间,则失效。因此最多被重放一段时间,这个要求客户端的时间和服务器的时间要保持相差不大。
  • sign、时间戳也是可以通过表单、QueryString或者报文头等传递。没有绝对安全,所有还是需要通过业务流程来保证安全,比如后端再次校验权限、重要操作要短信验证等。

7,.net core的WebAPI

  1. Controller继承自和 MVC一样的Controller路由配置,在Controller上标注[Rounte("api/[controller]")],在方法上标注[HttpGet],[HttpPost]等,也可以使用[HttpGet("id")]这样的格式,可以使用[HttpPost("Test")]这样的方式标注在方法上。这样用http/localhost:9000/api/values/Test访问。
  2. Action支持IActionResult作为返回值,但不支持HttpResponseMessage作为返回值。
  3. 不再支持IHttpControllerSelector,而是用IApplicationModelConvertion实现多版本控制。

Demo链接

https://download.csdn.net/download/lingxiao16888/92544537?spm=1001.2014.3001.5501

Logo

这里是一个专注于游戏开发的社区,我们致力于为广大游戏爱好者提供一个良好的学习和交流平台。我们的专区包含了各大流行引擎的技术博文,涵盖了从入门到进阶的各个阶段,无论你是初学者还是资深开发者,都能在这里找到适合自己的内容。除此之外,我们还会不定期举办游戏开发相关的活动,让大家更好地交流互动。加入我们,一起探索游戏开发的奥秘吧!

更多推荐