• WebApi简介


    简单创建.NET Core WebApi:https://www.cnblogs.com/yanbigfeg/p/9197375.html

    登陆验证四种方式:https://www.cnblogs.com/zuowj/p/5123943.html

    解决跨域的8种方法:https://blog.csdn.net/weixin_39939012/article/details/83822126

    WebApi深入学习--特性路由:https://www.cnblogs.com/TiestoRay/p/5755454.html
    本文的示例代码是基于.NET Framework下的,.NET WebApi与.NET Core WebApi的区别,个人认为主要是来自框架的不一样。可以参照官网https://docs.microsoft.com/en-us/aspnet/core/migration/webapi?view=aspnetcore-2.2#migrate-models-and-controllers后续介绍到.NET Core的时候再详细做下这两个框架的不同。

    在WebApi中,方法名以Get开头,WebApi会自动默认之歌请求是Get请求,而如果你以其他名称开头而又不标注这个方法的请求方式,那么这个时候服务器虽然找到了这个方法,但是由于请求方式不确定,所以直接返回给你405---方法不被允许的错误
    最后结论:所有的WebApi方法最好是加上请求的方式[HttpGet]/[HttpPost]/[HttpPut]/[HttpDelete],不要偷懒,这样既能防止类似的错误,也有利于方法的维护,被人一看就知道这个方法是什么请求

     网站在启动时执行Application_Start(),给Route增加地址规则,请求进来时,会经过路由匹配找到合适的控制器。

    那怎么找Action?

      1、根据HttpMethod找方法---用的方法名字开头,Get就是对应的Get请求

      2、如果名字不是Get开头,可以加上[HttpGet]

      3、按照参数找最吻合

    其实资源是这样定义的,不是一个学生,而可能是一个学校。可能是一个订单----多件商品,一次查询,订单-商品,数据之间嵌套关系很复杂。还有个特性路由,可以单独定制(config.MapHttpAttributeRoutes()、标机特性)

     IOC容器+配置文件初始化

    控制器也要注入--完成容器和WebApi框架融合--实现IDependencyResolver,将容器放进去--初始化

    config.DependencyResolver 换成自定义的Resolver

     public class IOCController : ApiController
     {
         private IUserService _UserService = null;
         public IOCController(IUserService userService)
         {
             this._UserService = userService;
         }
    
         public string Get(int id)
         {
             //IUserService service = new UserService();
             //IUserService service = ContainerFactory.BuildContainer().Resolve<IUserService>();
             return Newtonsoft.Json.JsonConvert.SerializeObject(this._UserService.Query(id));
         }
     }

    在WebApiConfig中加上:

     // Web API 配置和服务
     config.DependencyResolver = new UnityDependencyResolver(ContainerFactory.BuildContainer());

    UnityDependencyResolver:

     public class UnityDependencyResolver : IDependencyResolver
     {
         private IUnityContainer _UnityContainer = null;
         public UnityDependencyResolver(IUnityContainer container)
         {
             _UnityContainer = container;
         }
    
         public IDependencyScope BeginScope()//Scope
         {
             return new UnityDependencyResolver(this._UnityContainer.CreateChildContainer());
         }
    
         public void Dispose()
         {
             this._UnityContainer.Dispose();
         }
    
         public object GetService(Type serviceType)
         {
             try
             {
                 return this._UnityContainer.Resolve(serviceType);
             }
             catch (Exception ex)
             {
                 Console.WriteLine(ex.Message);
                 return null;
             }
         }
    
         public IEnumerable<object> GetServices(Type serviceType)
         {
             try
             {
                 return this._UnityContainer.ResolveAll(serviceType);
             }
             catch (Exception ex)
             {
                 Console.WriteLine(ex.Message);
                 return null;
             }
         }
     }
    View Code

    ContainerFactory:

    /// <summary>
    /// 需要在nuget引用之后,单独引用Unity.Configuration
    /// 如果有AOP扩展,还需要引用Unity.Interception.Configuration
    /// 因为我们是用配置文件来做的配置
    /// </summary>
    public class ContainerFactory
    {
        public static IUnityContainer BuildContainer()
        {
            //get
            //{
            return _Container;
            //}
        }
    
        private static IUnityContainer _Container = null;
        static ContainerFactory()
        {
            ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
            fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "CfgFiles\Unity.Config");//找配置文件的路径
            Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
            UnityConfigurationSection section = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName);
            _Container = new UnityContainer();
            section.Configure(_Container, "WebApiContainer");
        }
    }
    View Code

    Basic授权认证&权限Filter

      在Basic授权认证,(.NET Core下面用OAuth2,后续的随笔会记载到OAuth2),两者的区别可以参考博客https://blog.csdn.net/qq_15028299/article/details/89028774

    权限认证,是需要的,因为是Http地址,如果不加权限认证,别人不就可以直接拿到这边的请求去进行操作了,然后再去猜测别的WebApi。

    Session不可以吗?WebApi默认是不支持的Session的,因为RESTFul风格,因为是状态的。

    无状态:第二次请求和第一次请求不联系,没关系。

    步骤:

      1、登录过程,是为了拿到令牌,tooken/ticket/许可证

      2、验证成功,把账号+密码+其他信息+时间,加密一下,得到ticket,返回给客户端

      3、请求时,Ajax里面就带上这个tooken/ticket(在header里面验证)

      4、接口调用时,就去验证下ticket,解密一下,看看信息,看看时间

      5、每个方法都验证下ticket?不是这样的,可以基于filter来实现,FormAuthenticationTicket

    用户登录返回的结果中会是这个样子的。

    这边用到了AuthorizeAttribute,现在我们自定义个类CustomBasicAuthorizeAttribute继承自AuthorizeAttribute。

    1、方法注册,标机在action上

    2、控制器生效,方法全部生效,标机在Controller上

    3、全局注册:

    在WebApiConfig里面加上

     问题来了,现在不能每一个action都标机吧,就要用到控制器或者全局的。那么登录的时候也要验证token,每次都登录,就是登录不上去,然后循环下去。。。

    有一个特性AllowAnonymous,我们可以自己写一个CustomAllowAnonymousAttribute,其实就是

    下面是示例代码:

     #region 用户登陆
     [CustomAllowAnonymousAttribute]
     [HttpGet]
     public string Login(string account, string password)
     {
         if ("Admin".Equals(account) && "123456".Equals(password))//应该数据库校验
         {
             FormsAuthenticationTicket ticketObject = new FormsAuthenticationTicket(0, account, DateTime.Now, DateTime.Now.AddHours(1), true, string.Format("{0}&{1}", account, password), FormsAuthentication.FormsCookiePath);
             var result = new
             {
                 Result = true,
                 Ticket = FormsAuthentication.Encrypt(ticketObject)
             };
             return JsonConvert.SerializeObject(result);
         }
         else
         {
             var result = new { Result = false };
             return JsonConvert.SerializeObject(result);
         }
     }
     #endregion
    
    
    
    public class CustomAllowAnonymousAttribute : Attribute
    {
    }
    
    
    
     public class CustomBasicAuthorizeAttribute : AuthorizeAttribute
     {
         /// <summary>
         /// action前会先来这里完成权限校验
         /// </summary>
         /// <param name="actionContext"></param>
         public override void OnAuthorization(HttpActionContext actionContext)
         {
             //actionContext.Request.Headers["Authorization"]
             if (actionContext.ActionDescriptor.GetCustomAttributes<CustomAllowAnonymousAttribute>().FirstOrDefault() != null)
             {
                 return;//继续
             }
             else if (actionContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<CustomAllowAnonymousAttribute>().FirstOrDefault() != null)
             {
                 return;//继续
             }
             else
             {
                 var authorization = actionContext.Request.Headers.Authorization;
                 if (authorization == null)
                 {
                     this.HandlerUnAuthorization();
                 }
                 else if (this.ValidateTicket(authorization.Parameter))
                 {
                     return;//继续
                 }
                 else
                 {
                     this.HandlerUnAuthorization();
                 }
             }
         }
    
         private void HandlerUnAuthorization()
         {
             throw new HttpResponseException(System.Net.HttpStatusCode.Unauthorized);
         }
         private bool ValidateTicket(string encryptTicket)
         {
             ////解密Ticket
             //if (string.IsNullOrWhiteSpace(encryptTicket))
             //    return false;
             try
             {
                 var strTicket = FormsAuthentication.Decrypt(encryptTicket).UserData;
                 //FormsAuthentication.Decrypt(encryptTicket).
                 return string.Equals(strTicket, string.Format("{0}&{1}", "Admin", "123456"));//应该分拆后去数据库验证
             }
             catch (Exception ex)
             {
                 return false;
             }
    
         }
     }
    View Code

    异常处理Filter,ExceptionFilterAttribute

    1、自己定义一个类,继承自ExceptionFilterAttribute

    2、实现OnException方法

    3、标机到需要的控制钱Action

    4、注册到全局

    5、Filter的范围仅仅局限在Action里面

     public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
     {
         /// <summary>
         /// 异常发生后(没有被catch),会进到这里
         /// </summary>
         /// <param name="actionExecutedContext"></param>
         public override void OnException(HttpActionExecutedContext actionExecutedContext)
         {
             //actionExecutedContext.Response. 可以获取很多信息,日志一下
             Console.WriteLine(actionExecutedContext.Exception.Message);//日志一下
             actionExecutedContext.Response = actionExecutedContext.Request.CreateResponse(
                 System.Net.HttpStatusCode.OK, new
                 {
                     Result = false,
                     Msg = "出现异常,请联系管理员",
                     //Value=
                 });//创造一个返回
             //base.OnException(actionExecutedContext);
             //ExceptionHandler
         }
     }
    View Code

    自定义的异常类,要在全局中进行注册:config.Filters.Add(new CustomExceptionFilterAttribute());

    WebApi全局异常处理

    自定义一个类,继承自ExceptionHandler,重写Handle,初始化项目时,服务替换上。

     /// <summary>
     /// WEBApi的全局异常处理
     /// </summary>
     public class CustomExceptionHandler : ExceptionHandler
     {
         /// <summary>
         /// 判断是否要进行异常处理,规则自己定
         /// </summary>
         /// <param name="context"></param>
         /// <returns></returns>
         public override bool ShouldHandle(ExceptionHandlerContext context)
         {
             string url = context.Request.RequestUri.AbsoluteUri;
             return url.Contains("/api/");
    
             //return base.ShouldHandle(context);
         }
         /// <summary>
         /// 完成异常处理
         /// </summary>
         /// <param name="context"></param>
         public override void Handle(ExceptionHandlerContext context)
         {
             //Console.WriteLine(context);//log
             context.Result = new ResponseMessageResult(context.Request.CreateResponse(
                 System.Net.HttpStatusCode.OK, new
                 {
                     Result = false,
                     Msg = "出现异常,请联系管理员",
                     Debug = context.Exception.Message
                 }));
    
             //if(context.Exception is HttpException)
         }
     }
    
    
    
     config.Services.Replace(typeof(IExceptionHandler), new CustomExceptionHandler());//替换全局异常处理类
    View Code

    ActionFilter,可以在Action前/后增加逻辑

     public class CustomActionFilterAttribute : ActionFilterAttribute
     {
         public override void OnActionExecuting(HttpActionContext actionContext)
         {
             Console.WriteLine("1234567");
         }
    
         public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
         {
             Console.WriteLine("2345678");
             actionExecutedContext.Response.Headers.Add("Access-Control-Allow-Origin",
    "*");
         }
     }
    View Code

    WebApi跨域请求

      这里对WebApi跨域请求做下简单介绍。什么是跨域请求呢?就是浏览器请求时,如果A网站(域名+端口)页面里面,通过XMLHTTPRequest去请求B域名,这个就是跨域。这个请求时是跨域正常到达B服务器后端的,正常的响应(200)。但是浏览器是不允许这样操作的,除非在相应里面有声明(Access-Control-Allow-Origin)。

      这个是因为浏览器同源策略。出于安全考虑,浏览器限制脚本去发起蛞蝓请求。比如你想攻击别人的网站,得自己服务器发起请求,如果你搞个js,等于在客户端的电脑上去攻击别人。但是页面是script-src、img-href、jss/css/图片,这些是浏览器自己发起的,是可以跨域的。还有a标签,iframe标签也是可以的。浏览器自己的可以,但是用XHR去请求就是不行。

    解决跨域的方法

      1、Jsonp:脚本标签自动请求,请求回来的内容执行回调方法,解析数据

      2、CORS,跨域资源共享。允许服务器在响应时,指定Access-Control-Allow-Origin,浏览器按照响应来操作

      nuget下面添加cors

     

     全局的都允许:

    Action级别的:

    自定义一个一个标签,继承ActionFilter Attribute,执行完城后,加上actionExecutedContext.Response.Headers.Add("Access-Control-Allow-Origin", "*");

    public class CustomActionFilterAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            Console.WriteLine("1234567");
        }
    
        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
            Console.WriteLine("2345678");
            actionExecutedContext.Response.Headers.Add("Access-Control-Allow-Origin", "*");
        }
    }
    View Code

    因为跨域是浏览器控制的,在后端并没有跨域。

    自动生成WebApi文档(.NET Core下面基于Swagger生成文档)

     可以在浏览器中直接访问。

     WebApi深坑系列

    1、RESTFul  Action共存

      在get情况下,相同路由的Action无法共存,运行报错会匹配到多个Action

      在post情况下,相同路由的Action可以共存,会存在优先级,但是只会执行一个正则匹配到的Action

    2、RESTFul相同路由的Action如何并存,并访问指定的Action

      [Route("list"),HttpPost]

      路由如下 api/{controller}/list

    3、RESTFul如何设置多参数

      [Route("list"),HttpPost("{a}/{b}/{c}")]

      路由如下 api/{controller}/list/1/2/3

    4、Post提交多参数,[FromBody]跨域问题

      去注册中间件,让WebApi支持跨域

    5、Post提交多参数,[FromBody]ajax提交类型错误

      必须设置:contentType:'application/json;charset=utf-8'

      参数必须格式化成json字符串:data:Json.stringify(data)

    6、关于[FormBody]表示的参数不能设置基础类型,如string.....

      错误书法,跨域设置成string类型

    7、为什么Post提交参数必须要标识[FromBody]

      [FromBody]标识该参数值应该从请求的Body中获取,而不是从URL中获取,URL有长度限制,在不超多长度限制的情况下,可以随意。

  • 相关阅读:
    五小步让VS Code支持AngularJS智能提示
    AngularJS----服务,表单,模块
    AJAX 动态加载后台数据 绑定select
    连接mysql 报错 Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)
    MAC中向阿里云服务器上传文件
    使用Navicat连接阿里云ECS服务器上的MySQL数据库
    mysql面试题:字段中@之前字符相同且大于等于2条的所有记录
    2018 最新手机号正则(最新最全)
    php同一个用户同时只能登陆一个, 后登陆者踢掉前登陆者(排他登陆)
    php 单冒号 、双冒号的用法
  • 原文地址:https://www.cnblogs.com/taotaozhuanyong/p/11567017.html
Copyright © 2020-2023  润新知