时间:2018年05月22日 | 作者 : aaronyang | 分类 : .NET CORE | 浏览: 2232次 | 评论 0 人
2. Custom Formatter 知识
====================www.ayjs.net 杨洋 wpfui.com ayui ay aaronyang=======请不要转载谢谢了。=========
Core2 内置 支持JSON XML 纯文本 格式的 数据交换,这个知识可以帮你 自定义自己的格式的数据 来实现一个新的数据交换。
当以上3种满足不了你,你需要使用Custom Formatter知识,比如说你的 web api可以处理 Protobuf 的数据,还比如能发送 vCard 格式(一种交换联系人信息的格式)
下面例子讲 实现 vCard
====================www.ayjs.net 杨洋 wpfui.com ayui ay aaronyang=======请不要转载谢谢了。=========
创建一个输出格式类,你想序列化返回给客户端的
创建一个输入格式类,你想反序列化给客户端给你的数据
分别添加到 MvcOptions里的InputFormatters和OutputFormatters集合中
重写 CanReadType/CanWriteType
public class VcardOutputFormatter : Microsoft.AspNetCore.Mvc.Formatters.TextOutputFormatter { public VcardOutputFormatter() { SupportedMediaTypes.Add(Microsoft.Net.Http.Headers.MediaTypeHeaderValue.Parse("text/vcard")); SupportedEncodings.Add(System.Text.Encoding.UTF8); SupportedEncodings.Add(System.Text.Encoding.Unicode); } public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { throw new NotImplementedException(); } protected override bool CanWriteType(Type type) { if (typeof(Contact).IsAssignableFrom(type) || typeof(IEnumerable<Contact>).IsAssignableFrom(type)) { return base.CanWriteType(type); } return false; } } public class Contact { }
继承Microsoft.AspNetCore.Mvc.Formatters.TextOutputFormatter 方法,实现 CanWriteType方法,查看父类的 父类
还有个CanWriteResult方法,什么时候使用呢?
1) 当 一个action返回一个 model 类
2) 运行时, 返回一个派生类
3)运行时,返回一个不确定的派生类,你想判断处理
举个例子,你的方法签名返回一个 Person 类型,然后可能返回Student或者Instructor这样的派生类。如果你只想处理Student的类型,肯定先在上下文对象拿到Object判断类型,上下文对象是从CanWriteResult方法提供的。当一个action返回IActionResult时候,就没必要使用CanWriteResult了,你可以用CanWriteType在运行时候就可以获得类型了。
重写ReadRequestBodyAsync/WriteResponseBodyAsync
通过ReadRequestBodyAsync 反序列化获得数据,通过WriteResponseBodyAsync序列化数据
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { IServiceProvider serviceProvider = context.HttpContext.RequestServices; var logger = serviceProvider.GetService(typeof(ILogger<VcardOutputFormatter>)) as ILogger; var response = context.HttpContext.Response; var buffer = new StringBuilder(); if (context.Object is IEnumerable<Contact>) { foreach (Contact contact in context.Object as IEnumerable<Contact>) { FormatVcard(buffer, contact, logger); } } else { var contact = context.Object as Contact; FormatVcard(buffer, contact, logger); } return response.WriteAsync(buffer.ToString()); } private static void FormatVcard(StringBuilder buffer, Contact contact, ILogger logger) { buffer.AppendLine("BEGIN:VCARD"); buffer.AppendLine("VERSION:2.1"); buffer.AppendFormat($"N:{contact.LastName};{contact.FirstName}\r\n"); buffer.AppendFormat($"FN:{contact.FirstName} {contact.LastName}\r\n"); buffer.AppendFormat($"UID:{contact.ID}\r\n"); buffer.AppendLine("END:VCARD"); logger.LogInformation($"Writing {contact.FirstName} {contact.LastName}"); }
高亮行,这里是通过 依赖注入DI,注入进来的,构造函数里 是拿不到的
CClass1ass.isAssignableFrom(Class2)是用来判断一个类Class1和另一个类Class2是否相同或是另一个类的子类或接口。
完整Formatter代码:
输出类
using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Http; using System; using System.Collections.Generic; using System.Threading.Tasks; using System.Text; using Microsoft.Net.Http.Headers; using System.Reflection; using Microsoft.Extensions.Logging; namespace AyCoreChap1.Formatters { #region classdef public class VcardOutputFormatter : TextOutputFormatter #endregion { #region ctor public VcardOutputFormatter() { SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard")); SupportedEncodings.Add(Encoding.UTF8); SupportedEncodings.Add(Encoding.Unicode); } #endregion #region canwritetype protected override bool CanWriteType(Type type) { if (typeof(Contact).IsAssignableFrom(type) || typeof(IEnumerable<Contact>).IsAssignableFrom(type)) { return base.CanWriteType(type); } return false; } #endregion #region writeresponse public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { IServiceProvider serviceProvider = context.HttpContext.RequestServices; var logger = serviceProvider.GetService(typeof(ILogger<VcardOutputFormatter>)) as ILogger; var response = context.HttpContext.Response; var buffer = new StringBuilder(); if (context.Object is IEnumerable<Contact>) { foreach (Contact contact in context.Object as IEnumerable<Contact>) { FormatVcard(buffer, contact, logger); } } else { var contact = context.Object as Contact; FormatVcard(buffer, contact, logger); } return response.WriteAsync(buffer.ToString()); } private static void FormatVcard(StringBuilder buffer, Contact contact, ILogger logger) { buffer.AppendLine("BEGIN:VCARD"); buffer.AppendLine("VERSION:2.1"); buffer.AppendFormat($"N:{contact.LastName};{contact.FirstName}\r\n"); buffer.AppendFormat($"FN:{contact.FirstName} {contact.LastName}\r\n"); buffer.AppendFormat($"UID:{contact.ID}\r\n"); buffer.AppendLine("END:VCARD"); logger.LogInformation($"Writing {contact.FirstName} {contact.LastName}"); } #endregion } }
输入类
using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.Net.Http.Headers; using System; using System.IO; using System.Text; using System.Threading.Tasks; namespace AyCoreChap1.Formatters { #region classdef public class VcardInputFormatter : TextInputFormatter #endregion { #region ctor public VcardInputFormatter() { SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard")); SupportedEncodings.Add(Encoding.UTF8); SupportedEncodings.Add(Encoding.Unicode); } #endregion #region canreadtype protected override bool CanReadType(Type type) { if (type == typeof(Contact)) { return base.CanReadType(type); } return false; } #endregion #region readrequest public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding effectiveEncoding) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (effectiveEncoding == null) { throw new ArgumentNullException(nameof(effectiveEncoding)); } var request = context.HttpContext.Request; using (var reader = new StreamReader(request.Body, effectiveEncoding)) { try { await ReadLineAsync("BEGIN:VCARD", reader, context); await ReadLineAsync("VERSION:2.1", reader, context); var nameLine = await ReadLineAsync("N:", reader, context); var split = nameLine.Split(";".ToCharArray()); var contact = new Contact() { LastName = split[0].Substring(2), FirstName = split[1] }; await ReadLineAsync("FN:", reader, context); var idLine = await ReadLineAsync("UID:", reader, context); contact.ID = idLine.Substring(4); await ReadLineAsync("END:VCARD", reader, context); return await InputFormatterResult.SuccessAsync(contact); } catch { return await InputFormatterResult.FailureAsync(); } } } private async Task<string> ReadLineAsync(string expectedText, StreamReader reader, InputFormatterContext context) { var line = await reader.ReadLineAsync(); if (!line.StartsWith(expectedText)) { var errorMessage = $"Looked for '{expectedText}' and got '{line}'"; context.ModelState.TryAddModelError(context.ModelName, errorMessage); throw new Exception(errorMessage); } return line; } #endregion } }
这里CanReadType 感觉类似wpf的命令,是否可以继续执行,如果true,就序列化或者反序列化
====================www.ayjs.net 杨洋 wpfui.com ayui ay aaronyang=======请不要转载谢谢了。=========
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace AyCoreChap1.Formatters { public class Contact { public string ID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } } }
配置和使用Formatter,在Startup文件中配置
services.AddMvc(options => { options.InputFormatters.Insert(0, new VcardInputFormatter()); options.OutputFormatters.Insert(0, new VcardOutputFormatter()); });
写完我们测试使用
新增一批操作
using AyCoreChap1.Formatters; using System.Collections.Generic; namespace AyCoreChap1 { public interface IContactRepository { void Add(Contact contact); IEnumerable<Contact> GetAll(); Contact Get(string key); Contact Remove(string key); void Update(Contact contact); } }
实现这批操作
using AyCoreChap1.Formatters; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace AyCoreChap1 { public class ContactRepository : IContactRepository { private static ConcurrentDictionary<string, Contact> _contacts = new ConcurrentDictionary<string, Contact>(); public ContactRepository() { Add(new Contact() { FirstName = "Nancy", LastName = "Davolio" }); } public void Add(Contact contact) { contact.ID = Guid.NewGuid().ToString(); _contacts[contact.ID] = contact; } public Contact Get(string id) { Contact contact; _contacts.TryGetValue(id, out contact); return contact; } public IEnumerable<Contact> GetAll() { return _contacts.Values; } public Contact Remove(string id) { Contact contact; _contacts.TryRemove(id, out contact); return contact; } public void Update(Contact contact) { _contacts[contact.ID] = contact; } } }
新增ContactsController,对外暴露
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using AyCoreChap1.Formatters; using Microsoft.AspNetCore.Mvc; namespace AyCoreChap1.Controllers { [Route("api/[controller]")] [ApiController] public class ContactsController : Controller { public ContactsController(IContactRepository contacts) { Contacts = contacts; } public IContactRepository Contacts { get; set; } // GET api/contacts [HttpGet] public IEnumerable<Contact> Get() { return Contacts.GetAll(); } // GET api/contacts/{guid} [HttpGet("{id}", Name="Get")] public IActionResult Get(string id) { var contact = Contacts.Get(id); if (contact == null) { return NotFound(); } return Ok(contact); } // POST api/contacts [HttpPost] public IActionResult Post([FromBody]Contact contact) { if (ModelState.IsValid) { Contacts.Add(contact); return CreatedAtRoute("Get", new { id = contact.ID }, contact); } return BadRequest(); } // PUT api/contacts/{guid} [HttpPut("{id}")] public IActionResult Put(string id, [FromBody]Contact contact) { if (ModelState.IsValid && id == contact.ID) { var contactToUpdate = Contacts.Get(id); if (contactToUpdate != null) { Contacts.Update(contact); return new NoContentResult(); } return NotFound(); } return BadRequest(); } // DELETE api/contacts/{guid} [HttpDelete("{id}")] public IActionResult Delete(string id) { var contact = Contacts.Get(id); if (contact == null) { return NotFound(); } Contacts.Remove(id); return NoContent(); } } }
打开Startup
在ConfigureServices方法下,注册接口和实现,
services.AddSingleton<IContactRepository, ContactRepository>();
打开https://localhost:44337/api/contacts
可以访问下,返回一个contacts.vcf文件,双击打开
====================www.ayjs.net 杨洋 wpfui.com ayui ay aaronyang=======请不要转载谢谢了。=========
关于 在MVC中,继承Controller,你可以调用很多辅助方法,来返回值,比如 JSON,返回json数据给客户端
http://localhost:5756/api/authors
返回纯文本
或者直接文本
现在浏览器的请求有很多种header,有的可能是通配符,框架默认会忽略来自浏览器的header,然后返回默认的格式(比如json,也可以配置其他的),
如果你想你的程序,根据浏览器的header来,你可以在Startup下的ConfigureServices配置
services.AddMvc(options => { options.RespectBrowserAcceptHeader = true; // false by default });
如果你想你的程序返回其他格式数据,你可以通过Nuget包,然后MVC配置它
如果非常特殊,自己实现,你可以参考 Model Binding技术,它把 格式分为 Input和Output 两块,正如上篇AY博客所说,我们实现了vCard格式。
我们来配置支持XML,通过Nuget安装,安装后,配置
Microsoft.AspNetCore.Mvc.Formatters.Xml
services.AddMvc(options => { options.RespectBrowserAcceptHeader = true; // false by default options.OutputFormatters.Add(new XmlSerializerOutputFormatter()); options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter()); }) .AddXmlSerializerFormatters();
如果你只想返回json格式,可以使用Produces过滤器,跟其他过滤器一样,可以全局,可以controller级别,也可以action级别
当然我们也可以移除 一些格式
如果以下代码设置了
没有了TextOutputFormatter
返回string,就会返回406错误,not acceptable。此时如果xml格式化存在,那么会格式化string,然后返回。
没有HttpNoContentOutputFormatter
json序列化,就会封装个body,里面是个null,然后返回给客户端。如果是xml格式化,就会返回个具有xsi:nil="true"特性的空节点
说了这么多,怎么使用呢?
打开一开始的项目,WebApi的
ContactsController ,用 [Produces("application/json")]修饰
打开Startup.cs文件,增加AddXmlSerializerFormatters
运行项目,貌似没有效果
加上options.RespectBrowserAcceptHeader = true;代码
效果如下:
我们此时打开Contacts的,由于使用 [Produces("application/json")] 所以下面所有的 action都是返回json格式的。
能不能灵活切换指定呢?
继续配置,
options.FormatterMappings.SetMediaTypeMappingForFormat( "xml", "application/xml"); options.FormatterMappings.SetMediaTypeMappingForFormat( "json", "application/json");
然后,我在ValuesController上方用FormatFilter修饰了一下,然后再某个action上方修饰下
运行一下,访问方式1:
访问方式2:
访问方式3
如果不在controller上方修饰 FormatFilter修饰,你可以在下方action单独使用
,
如截图,使用Add new XmlXXXXXXXXXXXX啥的已过时,我们直接
直接使用下面的AddXmlSerializerFormatters方法就行了。
能不能配置默认的呢?这里默认是xml了
我们注释options.RespectBrowserAcceptHeader = true; 即可,
====================www.ayjs.net 杨洋 wpfui.com ayui ay aaronyang=======请不要转载谢谢了。=========
这些知识点msdn 都没,Ay自己查资料,慢慢找到,做实验 搞出来 的。
====================www.ayjs.net 杨洋 wpfui.com ayui ay aaronyang=======请不要转载谢谢了。=========
推荐您阅读更多有关于“net core2,”的文章
抖音:wpfui 工作wpf
目前在合肥企迈科技公司上班,加我QQ私聊
2023年11月网站停运,将搬到CSDN上
AYUI8全源码 Github地址:前往获取
杨洋(AaronYang简称AY,安徽六安人)和AY交流
高中学历,2010年开始web开发,2015年1月17日开始学习WPF
声明:AYUI7个人与商用免费,源码可购买。部分DEMO不免费
查看捐赠AYUI7.X MVC教程 更新如下:
第一课 第二课 程序加密教程
额 本文暂时没人评论 来添加一个吧
发表评论