欢迎光临
我们一直在努力

WebApi详解+Unity注入--中篇:.net core的WebAPI

8,RESTful风格

8.1,什么是RESTful?

REST全称是Representational State Transfer,中文意思是表述性状态转移。

REST本身并没有创造新的技术、组件或服务

REST指的是一组架构约束条件和原则,如果一个架构符合REST的约束条件和原则,我们就称他为RESTfull架构。

理论上REST架构风格并不是绑定在Http上,只不过目前Http是唯一与REST相关的实例。所以我们这里描述的REST也是通过Http实现REST.

8.2,REST主要规则是什么?
8.2.1,资源与URL,所有的资源都有一个资源标志符

Core webApi 访问一块资源

https://loaclhost:8000/Company —代表Company的一块资源
https://localhost:8000/User —代表User的一块资源

8.2.2,同一资源接口

冥等性:对同一个Rest接口的多次访问,得到的资源状态时相同的。
安全性:对该REST接口访问,不会是服务器端资源的状态发生改变。

8.2.3,资源的表述

服务端可以通过Content-Type告诉客户端资源的表述形式。

资源的表述形式有xml,html,json等格式,图片可以使用PNG,Jpeg展现出来

8.2.4,资源的链接

在设计RESTfull架构时,使用很多时间来寻找漂亮的URI,而忽略了超媒体。所以,应给多花一些时间来给资源的表述提供链接,而不是专注于资源的CURD。

8.2.5,状态的转移

原则上要求无状态通信原则

9,Core WebApi返回结果中文转义问题

设置json序列化中支持的编码器解决返回结果中文转义问题

public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//设置序列化的编码器,解决编码转义问题
services.AddControllers().AddJsonOptions(jsonOption=> {
jsonOption.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(System.Text.Unicode.UnicodeRanges.All);
})
;
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebAPICore应用", Version = "v1" });
});
}
}

10,Swagger配置

作用: 根据Controller与Action自动生成OpenAPI风格的文档,该文档支持多版本文档的切换查阅。

在这里插入图片描述

10.1 安装Swagger

在这里插入图片描述 一般情况在创建WebApiCore项目时选择OpenApi支持选项,选择该选项后创建项目时自动安装该Nuget。 在这里插入图片描述

10.2 配置Swagger

配置均是在StartUp.cs文件进行

10.2.1,解决编码转义问题

public void ConfigureServices(IServiceCollection services)
{
//解决编码转义问题
services.AddControllers().AddJsonOptions(jsonOption=> {
jsonOption.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(System.Text.Unicode.UnicodeRanges.All);
})
}

10.2.2,版本控制(忽略这个,直接查阅第12节版本控制)

public void ConfigureServices(IServiceCollection services)
{
//解决编码转义问题
services.AddControllers().AddJsonOptions(jsonOption=> {
jsonOption.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(System.Text.Unicode.UnicodeRanges.All);
})
;
//若要启用Swagger的版本控制需要在api控制器或者方法上添加特性[ApiExplorerSettings(GroupName="版本号"]
//1,生成不同版本的json文档
#region 分版本Swagger配置1
Enum.GetNames(typeof(Utility.Swagger.ApiVersions)).ToList().ForEach(version =>
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc(version, new OpenApiInfo
{
Title = $"枫林OpenAPI {version}",
Version = "v1",
Description = $"通用版本的CoreApi版本{version}"
});
});

});
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
//2,SwaggerUI使用不同版本的Swagger json文件
#region 分版本的Swagger配置2
app.UseSwaggerUI(c =>
{
Enum.GetNames(typeof(Utility.Swagger.ApiVersions)).ToList().ForEach(version =>
c.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"WebAPICore应用 {version}"));
});
#endregion
}
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}

添加Swagger版本控制特性([ApiExplorerSettings])的控制器

[ApiController]
[Route("[controller]")]
//在Swagger版本1中呈现
[ApiExplorerSettings(GroupName =nameof(Utility.Swagger.ApiVersions.V1),IgnoreApi =false)]
public class WeatherForecastController : ControllerBase
{

}

10.2.3,配置Swagger显示注释

1,在项目属性中设置输出xml文档文件

在这里插入图片描述

2,配置展示注释

#region 配置展示注释
services.AddSwaggerGen(option =>
{
//注释路径位置
var xmlfile = System.Reflection.Assembly.GetExecutingAssembly().ManifestModule.FullyQualifiedName;
string commentfile = xmlfile.Substring(0, xmlfile.LastIndexOf('.')) + ".xml";
option.IncludeXmlComments(commentfile, true);
//对action的名称进行排序,如果有多个,就可以看见效果
option.OrderActionsBy(apiDesciotion => apiDesciotion.RelativePath);
});
#endregion

在这里插入图片描述

10.2.4,Token传递

添加访问认证Token要求

#region 扩展传入token
//添加安全定义
option.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "请输入token,格式为 Bearer **** (注意中间必须是空格)",
Name = "Authorization",
In = ParameterLocation.Header,
BearerFormat = "JWT",
Scheme = "Bearer"
});
//添加安全要求
option.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference=new OpenApiReference
{
Type= ReferenceType.SecurityScheme,
Id="Bearer"
}
},new List<string>() }

});
#endregion

在这里插入图片描述

在这里插入图片描述

10.2.5,扩展文件上传按钮

#region 扩展文件上传,展示文件上传按钮
option.OperationFilter<Utility.Swagger.FileUploadFilter>();
#endregion

public class FileUploadFilter : IOperationFilter
{
/// <summary>
/// 扩展文件上传,展示文件上传按钮
/// </summary>
/// <param name="operation"></param>
/// <param name="context"></param>
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
const string FileUploadContentType = "multipart/form-data";
if (operation.RequestBody == null || !operation.RequestBody.Content.Any(
x => x.Key.Equals(FileUploadContentType, StringComparison.InvariantCultureIgnoreCase)))
{
return;
}
//只有Action的形参类型为IFormCollection才展示按钮
if (context.ApiDescription.ParameterDescriptions[0].Type == typeof(IFormCollection))
{
operation.RequestBody = new OpenApiRequestBody
{
Description = "文件上传",
Content = new Dictionary<string, OpenApiMediaType>
{
{ FileUploadContentType,new OpenApiMediaType
{
Schema=new OpenApiSchema
{
Type="object",
Required=new HashSet<string>{"file"},
Properties=new Dictionary<string, OpenApiSchema>
{
{"file",new OpenApiSchema{Type="string",Format="binary"} }
}
}
}
}
}
};
}
}
}

在这里插入图片描述

11,全局路由扩展

添加全局路由前缀

public void ConfigureServices(IServiceCollection services)
{
//配置路由前缀
services.AddControllers(option =>
{
option.Conventions.Insert(0, new Utility.Route.RouteConvention(new RouteAttribute("api/")));
});
//配置Swagger
services.AddSwaggerGenExt();

}

/// <summary>
/// 全局路由前缀配置
/// </summary>
public class RouteConvention : IApplicationModelConvention
{
/// <summary>
/// 定义一个路由前缀变量
/// </summary>
readonly AttributeRouteModel _centralPrefix;
/// <summary>
/// 调用时传入指定的路由前缀
/// </summary>
/// <param name="routeTemplateProvider"></param>
public RouteConvention(IRouteTemplateProvider routeTemplateProvider)
{
_centralPrefix = new AttributeRouteModel(routeTemplateProvider);
}
public void Apply(ApplicationModel application)
{
foreach (var controller in application.Controllers)
{
//1,已经标记了RouteAttribute的Controller
foreach (var selectorModel in controller.Selectors.Where(item=>item.AttributeRouteModel!=null))
{
//在当前路由上再添加一个路由前缀
selectorModel.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(_centralPrefix, selectorModel.AttributeRouteModel);
}
//2,没有标记RouteAttribute的Controller

foreach (var unSelectorModel in controller.Selectors.Where(item => item.AttributeRouteModel != null))
{
//在当前路由上再添加一个路由前缀
unSelectorModel.AttributeRouteModel = _centralPrefix;
}
}
}
}

12,版本控制

Nuget:
Microsoft.AspNetCore.Mvc.Versioning
Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer

在这里插入图片描述

public void ConfigureServices(IServiceCollection services)
{

//配置路由前缀
//services.AddControllers(option =>
//{
// option.Conventions.Insert(0, new Utility.Route.RouteConvention(new RouteAttribute("api/")));
//});
//配置Swagger
services.AddSwaggerGenExt();
//配置Swagger多版本控制
services.AddSwaggerGenWithVersioning(Enum.GetNames(typeof(Utility.Swagger.ApiVersions)));

}

/// <summary>
/// 控制swagger多版本生成
/// </summary>
/// <param name="services"></param>
/// <param name="apiVersions"></param>
public static void AddSwaggerGenWithVersioning(this IServiceCollection services, string[] apiVersions)
{
services.AddSwaggerGen(m =>
{
// 注册每个版本
foreach (var version in apiVersions)
{
m.SwaggerDoc(version, new OpenApiInfo
{
Title = $"枫林 API {version.ToUpper()}",
Version = version
});
}
// 冲突处理
m.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
// 根据控制器上的 ApiVersionAttribute 筛选文档
m.DocInclusionPredicate((docName, apiDesc) =>
{
if (!apiDesc.TryGetMethodInfo(out var methodInfo)) return false;
var versions = methodInfo.DeclaringType?
.GetCustomAttributes(true)
.OfType<ApiVersionAttribute>()
.SelectMany(attr => attr.Versions)
.Select(v => $"v{v.MajorVersion}")
.ToList();
return versions?.Contains(docName) ?? false;
});
});
// 配置 API 版本控制
services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0); // 默认版本为 1.0
options.AssumeDefaultVersionWhenUnspecified = true; // 未指定版本时使用默认版本
options.ReportApiVersions = true; // 返回支持的版本信息
options.ApiVersionReader = ApiVersionReader.Combine(
new QueryStringApiVersionReader("version"), // 从查询字符串读取版本号
new UrlSegmentApiVersionReader(), // 从 URL 路径读取版本号
new HeaderApiVersionReader("X-API-Version"), // 从请求头读取版本号
new MediaTypeApiVersionReader("version") // 从媒体类型读取版本号
);
});
}

namespace WebAPICore应用.Controllers.v1
{
//[Route("api/[controller]")]
[ApiController]
[ApiVersion("1.0")]//指明版本
[Route("api/v{version:apiVersion}/[controller]")]
//控制器上添加swagger的
// [ApiExplorerSettings(IgnoreApi =false,GroupName =nameof(Utility.Swagger.ApiVersions.V1))]
public class UserController : ControllerBase
{
// GET: api/<UserController>
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "V1版本", "V1版本" };
}
}
}

namespace WebAPICore应用.Controllers.v2
{
//[Route("api/[controller]")]
[ApiController]
// [ApiExplorerSettings(IgnoreApi = false, GroupName = nameof(Utility.Swagger.ApiVersions.V2))]
[ApiVersion("2.0")]//指明版本
[Route("api/v{version:apiVersion}/[controller]")]
public class UserController : ControllerBase
{
// GET: api/<UserController>
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "V2版本", "V2版本" };
}
}
}

在这里插入图片描述

在这里插入图片描述

13,API参数特性修饰

13.1,FromServices
  • 表示来自于IOC容器创建。
  • 必然需要在IOC容器先注册
  • 如果没有标记[FromServices],则默认这个参数需要通过调用方传递。
  • 13.2,FromBody

    搜集来自于客户端请求的参数中来自于Body的数据。

    13.3,FromForm

    搜集来自于客户端Form表单中的参数。

    13.4,FromHeader

    搜集来自于客户端请求的Header参数数据。

    13.5,FromQuery

    搜集来自客户端请求的查询字符串参数数据。

    13.6,FromRoute

    在来自客户端请求的路由中去收集这个参数的数据

    14,日志记录

    14.1,log4net日志记录

    支持文本日志,数据库日志

  • Nuget引入程序包log4net+Microsoft.Extensions.Logging.Log4Net.AspNetCore

  • 准备配置文件【设置为始终复制】

  • 植入log4net

    builder.Logging.AddLog4Net("log4net.config");

  • 注入log对象,写日志。

  • 15,AOP面向切面编程

    15.1,什么是AOP?

    AOP(Aspect orient Programming),面向切面编程,作为面向对象编程的一种补充,可以在不破坏之前的封装为基础动态增加一些功能,从而让系统更具可扩展性。

    15.2,Core WebApi中的AOP支持有哪些?
    • 授权——Authorize
    • 资源——Resource
    • 异常——Exception
    • 方法前后——Action
    • AlwayRunResult
    • 结果前后——Result
    15.3,如何使用带有注入参数的Fillter

    **使用[ServiceFilter]或者[TypeFilter]**为方法或者类引入带有注入参数Fillter特性。实际上测试发现使用[ServiceFilter]抛异常提示需要注册,而[TypeFiler]正常。

    15.3,IResourceFilter扩展定制
    • 定义类,实现IResourceFilter/IAsyncRessourceFilter接口,继承Attribute类

    • 实现方法

    • 标记在Api方法上

    • 执行时机:API方法执行前与执行后

    • 适用场景:为缓存而生。缓存:历史存储区域,以key-value格式保存数据。

    /// <summary>
    /// 扩展缓存
    /// </summary>
    public class CustomCacheResourceFilterAttribute : Attribute, IResourceFilter
    {
    //定义一个缓存字典
    static Dictionary<string, object> cacheDictionary = new Dictionary<string, object>();
    //顺序:A
    /// <summary>
    /// api执行后执行
    /// </summary>
    /// <param name="context"></param>
    public void OnResourceExecuted(ResourceExecutedContext context)
    {
    //api 执行完成后缓存请求url
    string requestPath = context.HttpContext.Request.Path;
    cacheDictionary[requestPath] = context.Result;
    }
    //顺序:B
    /// <summary>
    /// api执行前执行
    /// </summary>
    /// <param name="context"></param>
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
    //查询是否已经存储过该请求
    string requestPath = context.HttpContext.Request.Path;//Url地址
    if (cacheDictionary.ContainsKey(requestPath))
    {
    //如果包含则获取键值后直接返回不再执行后面的api方法,
    //如果Result==null,则继续执行后面的api方法
    context.Result = cacheDictionary[requestPath] as IActionResult;
    }

    }
    }

    //顺序:C
    [HttpGet("{id}")]
    [Utility.Filters.CustomCacheResourceFilter]//扩展缓存功能
    public IActionResult Get(int id)
    {
    return new JsonResult(new {
    Id=id,
    Method=nameof(Get),
    DataTime=DateTime.Now
    });
    }

    执行顺序为:B->构造方法->C->A

    第一次请求

    在这里插入图片描述

    第二次请求

    在这里插入图片描述

    因为获取的是上次的缓存所以结果一模一样。

    15.4,IActionFilter扩展定制
    • 定义类,实现IActionFilter/IAsyncActionFilter接口,继承Attribute类

    • 实现方法

    • 标记在Api方法上

    • 执行时机:API方法执行前与执行后

    • 特点

      • 与ResourceFilter相比
        • ResourceFilter使用连个方法包裹了控制器的构造方法+Api
        • ActionFilter使用连个办法只包裹了API逻辑部分。
    • ActionFilter场景使用:

      • 可以扩展为缓存,但是相对而言ResourceFilter性能更高些,请求处理的环节更少
      • ActionFillter更靠近api所以一般用于扩展日志功能

    ResourceFilter流程图(以上文自定义的CustomCacheResourceFilterAttribute为例)

    #mermaid-svg-94Pv2nNUFgLeAOae{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-94Pv2nNUFgLeAOae .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-94Pv2nNUFgLeAOae .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-94Pv2nNUFgLeAOae .error-icon{fill:#552222;}#mermaid-svg-94Pv2nNUFgLeAOae .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-94Pv2nNUFgLeAOae .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-94Pv2nNUFgLeAOae .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-94Pv2nNUFgLeAOae .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-94Pv2nNUFgLeAOae .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-94Pv2nNUFgLeAOae .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-94Pv2nNUFgLeAOae .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-94Pv2nNUFgLeAOae .marker{fill:#333333;stroke:#333333;}#mermaid-svg-94Pv2nNUFgLeAOae .marker.cross{stroke:#333333;}#mermaid-svg-94Pv2nNUFgLeAOae svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-94Pv2nNUFgLeAOae p{margin:0;}#mermaid-svg-94Pv2nNUFgLeAOae .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-94Pv2nNUFgLeAOae .cluster-label text{fill:#333;}#mermaid-svg-94Pv2nNUFgLeAOae .cluster-label span{color:#333;}#mermaid-svg-94Pv2nNUFgLeAOae .cluster-label span p{background-color:transparent;}#mermaid-svg-94Pv2nNUFgLeAOae .label text,#mermaid-svg-94Pv2nNUFgLeAOae span{fill:#333;color:#333;}#mermaid-svg-94Pv2nNUFgLeAOae .node rect,#mermaid-svg-94Pv2nNUFgLeAOae .node circle,#mermaid-svg-94Pv2nNUFgLeAOae .node ellipse,#mermaid-svg-94Pv2nNUFgLeAOae .node polygon,#mermaid-svg-94Pv2nNUFgLeAOae .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-94Pv2nNUFgLeAOae .rough-node .label text,#mermaid-svg-94Pv2nNUFgLeAOae .node .label text,#mermaid-svg-94Pv2nNUFgLeAOae .image-shape .label,#mermaid-svg-94Pv2nNUFgLeAOae .icon-shape .label{text-anchor:middle;}#mermaid-svg-94Pv2nNUFgLeAOae .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-94Pv2nNUFgLeAOae .rough-node .label,#mermaid-svg-94Pv2nNUFgLeAOae .node .label,#mermaid-svg-94Pv2nNUFgLeAOae .image-shape .label,#mermaid-svg-94Pv2nNUFgLeAOae .icon-shape .label{text-align:center;}#mermaid-svg-94Pv2nNUFgLeAOae .node.clickable{cursor:pointer;}#mermaid-svg-94Pv2nNUFgLeAOae .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-94Pv2nNUFgLeAOae .arrowheadPath{fill:#333333;}#mermaid-svg-94Pv2nNUFgLeAOae .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-94Pv2nNUFgLeAOae .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-94Pv2nNUFgLeAOae .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-94Pv2nNUFgLeAOae .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-94Pv2nNUFgLeAOae .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-94Pv2nNUFgLeAOae .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-94Pv2nNUFgLeAOae .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-94Pv2nNUFgLeAOae .cluster text{fill:#333;}#mermaid-svg-94Pv2nNUFgLeAOae .cluster span{color:#333;}#mermaid-svg-94Pv2nNUFgLeAOae div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-94Pv2nNUFgLeAOae .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-94Pv2nNUFgLeAOae rect.text{fill:none;stroke-width:0;}#mermaid-svg-94Pv2nNUFgLeAOae .icon-shape,#mermaid-svg-94Pv2nNUFgLeAOae .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-94Pv2nNUFgLeAOae .icon-shape p,#mermaid-svg-94Pv2nNUFgLeAOae .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-94Pv2nNUFgLeAOae .icon-shape rect,#mermaid-svg-94Pv2nNUFgLeAOae .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-94Pv2nNUFgLeAOae .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-94Pv2nNUFgLeAOae .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-94Pv2nNUFgLeAOae :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    客户端缓存存在

    缓存不存在

    客户端请求

    OnResourceExecuting

    获取缓存值

    取缓存值推送至客户端

    执行控制器构造方法

    执行API方法

    结果推送至客户端

    执行OnResourceExecuted保存此次结果用于缓存

    ActionFilter流程图

    #mermaid-svg-jtI71X5CvxosmRne{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jtI71X5CvxosmRne .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jtI71X5CvxosmRne .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jtI71X5CvxosmRne .error-icon{fill:#552222;}#mermaid-svg-jtI71X5CvxosmRne .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jtI71X5CvxosmRne .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jtI71X5CvxosmRne .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jtI71X5CvxosmRne .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jtI71X5CvxosmRne .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jtI71X5CvxosmRne .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jtI71X5CvxosmRne .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jtI71X5CvxosmRne .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jtI71X5CvxosmRne .marker.cross{stroke:#333333;}#mermaid-svg-jtI71X5CvxosmRne svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jtI71X5CvxosmRne p{margin:0;}#mermaid-svg-jtI71X5CvxosmRne .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-jtI71X5CvxosmRne .cluster-label text{fill:#333;}#mermaid-svg-jtI71X5CvxosmRne .cluster-label span{color:#333;}#mermaid-svg-jtI71X5CvxosmRne .cluster-label span p{background-color:transparent;}#mermaid-svg-jtI71X5CvxosmRne .label text,#mermaid-svg-jtI71X5CvxosmRne span{fill:#333;color:#333;}#mermaid-svg-jtI71X5CvxosmRne .node rect,#mermaid-svg-jtI71X5CvxosmRne .node circle,#mermaid-svg-jtI71X5CvxosmRne .node ellipse,#mermaid-svg-jtI71X5CvxosmRne .node polygon,#mermaid-svg-jtI71X5CvxosmRne .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jtI71X5CvxosmRne .rough-node .label text,#mermaid-svg-jtI71X5CvxosmRne .node .label text,#mermaid-svg-jtI71X5CvxosmRne .image-shape .label,#mermaid-svg-jtI71X5CvxosmRne .icon-shape .label{text-anchor:middle;}#mermaid-svg-jtI71X5CvxosmRne .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jtI71X5CvxosmRne .rough-node .label,#mermaid-svg-jtI71X5CvxosmRne .node .label,#mermaid-svg-jtI71X5CvxosmRne .image-shape .label,#mermaid-svg-jtI71X5CvxosmRne .icon-shape .label{text-align:center;}#mermaid-svg-jtI71X5CvxosmRne .node.clickable{cursor:pointer;}#mermaid-svg-jtI71X5CvxosmRne .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jtI71X5CvxosmRne .arrowheadPath{fill:#333333;}#mermaid-svg-jtI71X5CvxosmRne .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jtI71X5CvxosmRne .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jtI71X5CvxosmRne .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jtI71X5CvxosmRne .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jtI71X5CvxosmRne .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jtI71X5CvxosmRne .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jtI71X5CvxosmRne .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jtI71X5CvxosmRne .cluster text{fill:#333;}#mermaid-svg-jtI71X5CvxosmRne .cluster span{color:#333;}#mermaid-svg-jtI71X5CvxosmRne div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jtI71X5CvxosmRne .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jtI71X5CvxosmRne rect.text{fill:none;stroke-width:0;}#mermaid-svg-jtI71X5CvxosmRne .icon-shape,#mermaid-svg-jtI71X5CvxosmRne .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jtI71X5CvxosmRne .icon-shape p,#mermaid-svg-jtI71X5CvxosmRne .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jtI71X5CvxosmRne .icon-shape rect,#mermaid-svg-jtI71X5CvxosmRne .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jtI71X5CvxosmRne .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jtI71X5CvxosmRne .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jtI71X5CvxosmRne :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    缓存值存在

    缓存不存在

    客户端请求

    执行控制器构造方法

    OnActionExecuting

    取缓存值返回至客户端

    执行API方法

    结果推送至客户端

    执行OnResourceExecuted保存此次结果用于缓存

    ResourceFilter在缓存存在时不用调用控制器构造方法,而ActionFilter不管缓存存不存在每次都需要调用构造方法。所以在缓存方面ResourceFilter性能优于ActionFilter。

    实现IActionFilter的自定义类

    public class CustomLogActionFilterAttribute : Attribute, IActionFilter
    {
    log4net.ILog logger;
    public CustomLogActionFilterAttribute(log4net.ILog logger)
    {
    this.logger = logger;
    }
    public void OnActionExecuted(ActionExecutedContext context)
    {
    string controllerName = context.HttpContext.Request.RouteValues["controller"]?.ToString();
    string methodName = context.ActionDescriptor.RouteValues["action"];
    logger.Info($"访问{controllerName}.{methodName}执行OnActionExecuted");
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {

    string controllerName = context.HttpContext.Request.RouteValues["controller"]?.ToString();
    string methodName = context.HttpContext.Request.RouteValues["action"]?.ToString();

    logger.Info($"访问{controllerName}.{methodName}执行OnActionExecuting");
    }
    }

    [HttpGet]
    //[Utility.Filters.CustomCacheResourceFilter]//扩展缓存功能
    [TypeFilter(typeof(Utility.Filters.CustomLogActionFilterAttribute))]
    //使用ServicceFilter需要注册否则抛异常
    // [ServiceFilter(typeof(Utility.Filters.CustomLogActionFilterAttribute))]
    public IEnumerable<string> Get()
    {
    logger.Info($"访问{nameof(FiltersController)}.{nameof(Get)}");
    return new string[] { "value1", "value2" };
    }

    2025-12-29 21:16:51,025 [4] INFO loggerAppender [29] – 访问Filters.Get执行OnActionExecuting
    2025-12-29 21:16:53,130 [4] INFO loggerAppender [30] – 访问FiltersController.Get
    2025-12-29 21:17:14,546 [4] INFO loggerAppender [20] – 访问Filters.Get执行OnActionExecuted

    15.5,IExceptionFilter扩展定制

    public class CustomExceptionFilterAttribute : Attribute, IExceptionFilter
    {
    public void OnException(ExceptionContext context)
    {
    if (!context.ExceptionHandled)
    {
    context.Result = new JsonResult(new
    {
    success=false,
    msg=context.Exception.Message,

    });
    context.ExceptionHandled = true;
    }
    }
    }

    [HttpGet("Calculate/{val}")]
    [Utility.Filters.CustomExceptionFilter]
    public double Calculate(int val)
    {
    return 12 / val;
    }

    正常时:

    在这里插入图片描述

    异常时:以自定义的形式向客户端推送信息。

    在这里插入图片描述

    15.6,IAsyncAuthorizationFilter鉴权实现整个过程

    引入NuGet包:Microsoft.IdentityModel.JsonWebTokens

    在这里插入图片描述

    public class AppCredential
    {

    public string ClientId { get; set; }
    public string Secret { get; set; }
    }
    public class Application
    {
    public int ApplicationId { get; set; }
    public string ApplicationName { get; set; }
    public string ClientId { get; set; }
    public string Secret { get; set; }
    public string Scopes { get; set; }
    }
    public class AppRepository
    {
    static List<Application> _applications = new List<Application>
    {
    new Application
    {
    ApplicationId=1,
    ApplicationName="webapi",
    ClientId="aa-bb-cc",
    Secret="893F4720-F3E5-46A4-BBE9-B3B2BB6E0F2E",
    Scopes="read,write"
    }
    };
    public static bool Authenticate(string ClientId, string Secret)
    {
    return _applications.Exists(application =>
    {
    return application.ClientId.Equals(ClientId, StringComparison.OrdinalIgnoreCase) && application.Secret.Equals(Secret, StringComparison.OrdinalIgnoreCase);
    });
    }
    public static Application GetApplicationByClientId(string clientId)
    {
    return _applications.FirstOrDefault(app => app.ClientId.Equals(clientId, StringComparison.OrdinalIgnoreCase));
    }
    }

    public class Authenticator
    {
    public static string CreateToken(string clientId, DateTime expiresAt, string strSecurityKey)
    {
    //包含三部分 header,payload,Signature
    //Algorithm:算法。秘钥+算法
    //Sha256的SecurityKey的长度需要是'256'个 bit,即32个byte
    //所以为兼容所有秘钥长度这里转换为md5
    string securityKey = string.Empty;
    using (var md5 = MD5.Create())
    {
    //md5计算结果为16个byte
    byte[] keyBytes = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(strSecurityKey));
    securityKey = keyBytes.Select(b => b.ToString("X2")).Aggregate((a, b) => a + b);
    }
    var signingCredentials = new SigningCredentials(
    new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(securityKey)),
    SecurityAlgorithms.HmacSha256Signature
    );
    //Payload(Claims)
    Application app = AppRepository.GetApplicationByClientId(clientId);
    var claimsDictionary = new Dictionary<string, object>
    {
    {"AppName",app.ApplicationName },
    {"Read",app.Scopes?.Contains("read",StringComparison.OrdinalIgnoreCase) },
    {"Write",app.Scopes?.Contains("write",StringComparison.OrdinalIgnoreCase) }
    };
    var tokenDescriptior = new SecurityTokenDescriptor
    {
    SigningCredentials = signingCredentials,
    Claims = claimsDictionary,//负载
    Expires = expiresAt,//过期时间
    NotBefore = DateTime.UtcNow//不能早于这个时间
    };
    var tokenHandler = new JsonWebTokenHandler();
    return tokenHandler.CreateToken(tokenDescriptior);
    }
    public static async Task<bool> VerifyTokenAsync(string tokenString, string securityKey)
    {
    if (string.IsNullOrEmpty(tokenString) || string.IsNullOrEmpty(securityKey))
    {
    return false;
    }
    string strSecurityKey = string.Empty;
    using (var md5 = MD5.Create())
    {
    //md5计算结果为16个byte
    byte[] keyBytes = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(securityKey));
    strSecurityKey = keyBytes.Select(b => b.ToString("X2")).Aggregate((a, b) => a + b);
    }
    var keyByetes = System.Text.Encoding.UTF8.GetBytes(strSecurityKey);
    var tokenHander = new JsonWebTokenHandler();
    var validationParameters = new TokenValidationParameters
    {
    ValidateIssuerSigningKey = true,
    IssuerSigningKey = new SymmetricSecurityKey(keyByetes),
    ValidateIssuer = false,
    ValidateAudience = false,
    ValidateLifetime = true,//验证有效期
    ClockSkew = TimeSpan.Zero//时钟偏差量
    };
    try
    {
    var result = await tokenHander.ValidateTokenAsync(tokenString, validationParameters);
    return result.IsValid;
    }
    catch (SecurityTokenMalformedException)
    {
    return false;
    }
    catch (SecurityTokenExpiredException)
    {
    return false;
    }
    catch (SecurityTokenInvalidSignatureException)
    {
    return false;
    }
    catch (Exception ex)
    {
    throw ex;
    }
    }
    }

    public class JwtTokenAuthFilterAttribute : Attribute, IAsyncAuthorizationFilter
    {
    public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
    {
    //1,Get Authorization header from the request
    if(!context.HttpContext.Request.Headers.TryGetValue("Authorization",out var authorizationHeader))
    {
    context.Result = new UnauthorizedResult();
    return;
    }
    //2,Get rid of the Bearer prefix
    string tokenString = authorizationHeader;
    if (tokenString.StartsWith("Bearer", StringComparison.OrdinalIgnoreCase))
    {
    tokenString = tokenString.Substring("Bearer".Length).Trim();
    }
    else
    {
    context.Result = new UnauthorizedResult();
    return;
    }
    //3,Get Configuration and the Secret Key

    var configuration = context.HttpContext.RequestServices.GetService(typeof(IConfiguration)) as IConfiguration;
    var securityKey = configuration?.GetValue<string>("SecurityKey");
    //4,Verify the token
    if(!await Authenticator.VerifyTokenAsync(tokenString, securityKey))
    {
    context.Result = new UnauthorizedResult();
    }
    }
    }

    使用特性

    [HttpGet]
    //添加鉴权要求
    [JwtTokenAuthFilter]
    public IEnumerable<string> Get()
    {
    return new string[] { "V1版本", "V1版本" };
    }

    未添加Authorization Bearer时:

    在这里插入图片描述

    添加 Bearer Token后返回所需值

    在这里插入图片描述

    15.7,Filter的生效范围和依赖注入
    • 方法注册——当前方法有效
    • 控制器注册——当前控制器有效
    • 全局注册——全局有效

    全局注册示例:

    在StartUp.cs中进行配置:

    //配置全局ExceptionFilter
    services.AddControllers(option =>
    {
    option.Filters.Add(typeof(Utility.Filters.CustomExceptionFilterAttribute));
    });

    Demo链接

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

    赞(0)
    未经允许不得转载:171主机测评 » WebApi详解+Unity注入--中篇:.net core的WebAPI
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址