↓
前语
在上一篇《ASP.NET Core 设置Web API 呼应的数据格局——Produces 特性篇》老周已向各位介绍过 Produces 特性的运用,本文将介绍另一个特性类:
FormatFilterAttribute。
这个特性算得上是
挑选器
的马
甲,除了从 Attribute 类派生外,还完成了 IFilterFactory 接口。
之所以说它是个马甲,是由于 IFilterFactory 接口要求类型完成 CreateInstance 办法来发生挑选器的目标实例。
也就是说,FormatFilterAttribute 类并没有真实做挑选的代码,而是创立一个 FormatFilter 类的实例。
是
怎样作业的
这个特性类能够应用在类(控制器)和办法(控制器中的 Action)上,它答应 API 的调用方主动挑选回来数据的格局。
这是什么操作呢?
假如你曾经(我说的是曾经,由于现在许多都只支撑JSON格局)做过像微博敞开渠道的 API 调用,或许还记得在 URL 上经过参数来挑选回来 XML 仍是 JSON。
比方这样:
http://what.com/api/getlist?t=xml
http://what.com/api/getlist?t=json
当然了,条件是你写的 API 支撑被指定的格局,要是调用者指定了 jpg,而你编写的 API 不支撑是会报错的。格局称号是怎么让 ASP.NET Core 辨认出要回来的 Content-Type 的呢?别急,往下看就知道了。
先说说 FormatFilter 特性是怎么获取到 API 调用方指定的格局的。办法有二:
1、从路由规矩查找名为“format”的关键字。就像 MVC 路由规矩中的“controller”、"action"关键字相同。假如“format”关键字辨认出 json,那就回来 JSON 格局的数据;若辨认出 xml 就回来 XML 格局的数据。
2、从恳求 URL 的查询字符串中找到名为“format”的字段,若它的值为 json 表明回来 JSON 格局的数据;若为 xml 就回来 XML 格局的数据。若为其他值,你得自界说完成。
最好经过路由规矩的办法来处理,一则此法比较灵敏,二则不用占用 URL 查询字符串,免得把 URL 弄得太长。
刚刚老周说路由规矩能够用“format”关键字来辨认格局,要想知道为什么,我们能够看看 FormatFilter 类的源代码(FormatFilter 特性仅仅个壳,没啥美观)。
public virtual string? GetFormat(ActionContext context)
{
if (context.RouteData.Values.TryGetValue("format", out var obj))
{
// null and string.Empty are equivalent for route values.
var routeValue = Convert.ToString(obj, CultureInfo.InvariantCulture);
return string.IsNullOrEmpty(routeValue) ? null : routeValue;
}
var query = context.HttpContext.Request.Query["format"];
if (query.Count 0)
{
return query.ToString();
}
return null;
}
它先是从 RouteData 字典中找一找有没有与“format”对应的值,假如有,就回来;假如没有,再去找 URL 查询字符串中是否存在“format”字段。
如你所见,在 FormatFilter 类中,这个 GetFormat 办法是声明为 virtual 的,说白了,你能够自界说你的查找办法,或许你找的不是名为“format”的关键字,而是叫“type”。你只要从 FormatFilter 类派生,然后覆写 GetFormat 办法。最终把你自己写的新 FormatFilter 注册到 MVC 选项的 Filters 列表中即可。
动
手一试
此处用的测验数据类为 Book。
public class Book
{
/// summary
/// 编号
/// /summary
public uint ID { get; set; }
/// summary
/// 书名
/// /summary
public string Title { get; set; }
/// summary
///
/// /summary
public string Author { get; set; }
/// summary
/// 发行时刻
/// /summary
public DateTime PublishTime { get; set; }
}
我们假定 Book 目标表明一本图书的基本信息。
然后,我们弄个控制器。
[Route("api/bkstore")]
[ApiController, FormatFilter]
public class BooksController : ControllerBase
{
[HttpGet("list/{format?}")]
public IEnumerableBook ListBooks() = new Book[]
{
new() {ID=5112, Title="C言语从入门到割腕", Author="老周", PublishTime = new(2011,10,12)},
new() {ID=72543, Title="下水道里的英豪", Author="老周", PublishTime= new(2021,4,17)},
new() {ID=28565, Title="领饭盒年代", Author="老张", PublishTime= new(2022,5,1)},
new() {ID=80251, Title="钱多脑傻的城里人", Author="光头强", PublishTime= new(2017,6,8)}
};
}
Books 控制器应用了 FormatFilter 特性,使得在整个控制器内的操作办法均支撑经过 format 关键字来挑选数据格局。调用的 URL 格局如下:
http://localhost/api/bkstore/list/json
http://localhost/api/bkstore/list/xml
“{format?}”中有个问号,表明这个路由参数是可选的,即能够省掉。假如省掉,ASP.NET Core 应用程序就会从现已注册的格局列表中查找匹配的第一个项作为默许格局。例如,MVC 格局列表中注册了json、xml、audio/wav 等格局,当 {format} 参数省掉后,默许会挑选 json。
在 Program.cs 文件中补上其他代码,在注册 API 控制器功用时,要调用 AddXmlSerializerFormatters 办法,这样才支撑回来 XML 格局的数据。
var builder = WebApplication.CreateBuilder(args);
// 增加XML格局的支撑需求调用 AddXmlSerializerFormatters 办法
builder.Services.AddControllers().AddXmlSerializerFormatters();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
//================================
var app = builder.Build();
//================================
app.UseSwaggerUI(o =
{
o.RoutePrefix = "";
o.SwaggerEndpoint("swagger/v1/swagger.json", "swg");
});
上面代码中,调用了 UseSwaggerUI 等办法,使项目支撑 Web API 的测验,这个当地老周修改了一些默许装备。
app.UseSwaggerUI(o =
{
o.RoutePrefix = "";
o.SwaggerEndpoint("swagger/v1/swagger.json", "swg");
});
RoutePrefix 特点设置拜访 Swagger 页面的途径,默许要到 /swagger 下,我把它改为空字符串,表明在根途径就能拜访,首要是为了测验便利。
直接拜访 http://localhost:xxx/ 就 OK。由于默许的前缀 /swagger 被去掉了,所以,获取描绘 API 的 JSON 文档的获取途径要手动设置回默许的途径 /swagger/v1/swagger.json,不然运转后会找不到 API 信息。
由于 Swagger UI 的测验页不能将 {format?} 辨认为可选参数,所以在调用时要显式加上 xxx/json 或 xxx/xml。
http://localhost:5228/api/bkstore/list/json
http://localhost:5228/api/bkstore/list/xml
用 XML 格局时回来的成果:

用 JSON 格局时回来的成果:

自己加个格局
json、xml 是 ASP.NET Core 主动注册的格局称号,我们也能够自己加一些格局。
builder.Services.AddControllers()
.AddXmlSerializerFormatters()
.AddFormatterMappings(mappings =
{
"txtj", "text/json");
});
http://localhost:5228/api/bkstore/list/txtj

像 json -- application/json,xml -- application/xml、abc -- image/png 这样。
可是,若增加 txt -- text/plain 的映射,就会失利。
builder.Services.AddControllers().AddXmlSerializerFormatters()
.AddFormatterMappings(mappings =
{
"txt", "text/plain");
});

原因并不是 ASP.NET Core 不答应你这样做,而是格局不匹配。还记得老周在上一篇水文中说过吗,text/plain 默许由 StringOutputFormatter 类来处理的,只支撑回来值为 string 类型的办法。而我们上例中的 ListBooks 办法是回来一个 Book 目标的列表的,类型上不匹配。
所以,假如你想映射 txt -- text/plain 上,需求自界说一个 Formatter,让其将 Book 列表变为字符串。这个大伙能够自己试试(这个最好不要太自界说了,不然有数组有类,比较难搞,能够考虑在 Book 类中重写 ToString 办法,或许好弄些),老周接下来用另一个比如来阐明一下,由于这个比如不回来数组,只回来单个实例,能够用反射来扫描一切公共特点,然后连接成字符串。当然了,这种做法局限性大,也没办法通用于一切类型,仅作演示。
先界说我们需求的数据类,这儿命名为 Goods,表明一件产品(由于老周是开杂货店的,所以用 Goods 类)。
public class Goods
{
/// summary
/// 产品ID
/// /summary
public uint ID { get; set; }
/// summary
/// 产品标题
/// /summary
public string Name { get; set; } = "none";
/// summary
/// 单价
/// /summary
public decimal Price { get; set; }
/// summary
/// 补白
/// /summary
public string Remark { get; set; } = string.Empty;
}
接着,完成自界说的 Formatter 类,这儿我们所需的功用是将目标的公共特点拼接为字符串回来给客户端。故我们不需求彻底自己去完成 IOutputFormatter 接口,直接从 TextOutputFormatter 类派生就行了。这货是个抽象类,我们要做两件事:
1、在结构函数中向 SupportedMediaTypes 列表中增加受支撑的 MIME 类型。你期望它兼容哪些格局,就别离 Add 进去就 OK 了。此例中老周仅期望它支撑 text/plain 格局,所以只加这个就能够了。然后还要向 SupportedEncodings 列表增加受支撑的字符编码,现在一般用 UTF-8 就好,削减许多费事。
2、完成 WriteResponseBodyAsync 办法,将待处理目标转化为字符串,并回写到呼应流中。
public class MyOutputFormatter : TextOutputFormatter
{
public MyOutputFormatter()
{
/*
* 下面这两行必不能少
*/
// 增加所支撑的 MIME 类型
SupportedMediaTypes.Add("text/plain");
// 增加支撑的字符编码
SupportedEncodings.Add(Encoding.UTF8);
}
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
{
// 获取被处理的目标实例
object obj = context.Object;
// 获取目标的 Type
Type objtype = context.ObjectType;
if (obj is null || objtype is null)
{
return;
}
// 找出公共特点
var props = objtype.GetProperties(BindingFlags.Public | BindingFlags.Instance);
StringBuilder strbf = new();
// 逐一读取出来
foreach (var p in props)
{
strbf.Append($"{p.Name}=");
object val = p.GetValue(obj);
if (!(val is null))
{
strbf.Append(val);
}
strbf.AppendLine();
}
// 写呼应内容
await context.HttpContext.Response.WriteAsync(strbf.ToString());
}
}
在 Program.cs 文件中,调用 AddControllers 办法,把刚刚界说的 Formatter 实例增加到 OutputFormatters 列表中。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(opt =
{
opt.OutputFormatters.Add(new MyOutputFormatter());
})
.AddXmlSerializerFormatters()
.AddFormatterMappings(mappings =
{
"txt", "text/plain");
});
……
最终,我们回过头来向控制器类增加一个操作办法。
[HttpGet("buy/{format?}")]
public Goods BuySomething() = new Goods
{
ID = 93257,
Name = "恐龙皮做的女士背包",
Price = 58888.03M,
Remark = "直播带货,无需出产答应,无合格证,无需品控,无售后;无退换货,产品若有质量问题,请买家自行毁掉"
};
然后运转测验一下(拜访 http://localhost:xxxx/api/bkstore/buy/txt)。回来成果:
ID=93257
Name=恐龙皮做的女士背包
Price=58888.03
Remark=直播带货,无需出产答应,无合格证,无需品控,无售后;无退换货,产品若有质量问题,请买家自行毁掉
转自:东邪独孤
链接:cnblogs.com/tcjiaan/p/15888394.html
- EOF -
C# 10的新特性
看看这套WPF开源根底控件库:WPFDevelopers
优化.NET 应用程序 CPU 和内存的11 个实践