当前位置:网站首页 / .NET CORE / 正文

AY写给国人的ASP.NET Core2教程【6/30】

时间: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方法,查看父类的 父类

image.png

还有个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,注入进来的,构造函数里  是拿不到的

image.png

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());
            });

image.png

写完我们测试使用

新增一批操作

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>();

image.png

打开https://localhost:44337/api/contacts

可以访问下,返回一个contacts.vcf文件,双击打开

image.png

image.png


====================www.ayjs.net       杨洋    wpfui.com        ayui      ay  aaronyang=======请不要转载谢谢了。=========


关于 在MVC中,继承Controller,你可以调用很多辅助方法,来返回值,比如 JSON,返回json数据给客户端

image.png

http://localhost:5756/api/authors

image.png

image.png

返回纯文本

image.png

image.png

或者直接文本

image.png

image.png

现在浏览器的请求有很多种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();

image.png

如果你只想返回json格式,可以使用Produces过滤器,跟其他过滤器一样,可以全局,可以controller级别,也可以action级别

image.png

当然我们也可以移除 一些格式

如果以下代码设置了

image.png

没有了TextOutputFormatter

返回string,就会返回406错误,not acceptable。此时如果xml格式化存在,那么会格式化string,然后返回。

没有HttpNoContentOutputFormatter

json序列化,就会封装个body,里面是个null,然后返回给客户端。如果是xml格式化,就会返回个具有xsi:nil="true"特性的空节点



说了这么多,怎么使用呢?

打开一开始的项目,WebApi的

ContactsController  ,用 [Produces("application/json")]修饰

image.png

打开Startup.cs文件,增加AddXmlSerializerFormatters

image.png

运行项目,貌似没有效果

image.png

加上options.RespectBrowserAcceptHeader = true;代码

image.png

效果如下:

我们此时打开Contacts的,由于使用    [Produces("application/json")] 所以下面所有的 action都是返回json格式的。

image.png

能不能灵活切换指定呢?

继续配置, 

                options.FormatterMappings.SetMediaTypeMappingForFormat(
                                  "xml", "application/xml");
                options.FormatterMappings.SetMediaTypeMappingForFormat(
                          "json", "application/json");

image.png

然后,我在ValuesController上方用FormatFilter修饰了一下,然后再某个action上方修饰下

image.png

运行一下,访问方式1:

image.png

访问方式2:

image.png

访问方式3

image.png

如果不在controller上方修饰 FormatFilter修饰,你可以在下方action单独使用

image.png

image.png

image.png     

image.png

如截图,使用Add new XmlXXXXXXXXXXXX啥的已过时,我们直接

image.png

直接使用下面的AddXmlSerializerFormatters方法就行了。

image.png

能不能配置默认的呢?这里默认是xml了

我们注释options.RespectBrowserAcceptHeader = true; 即可,

====================www.ayjs.net       杨洋    wpfui.com        ayui      ay  aaronyang=======请不要转载谢谢了。=========

这些知识点msdn 都没,Ay自己查资料,慢慢找到,做实验 搞出来 的。

image.png


====================www.ayjs.net       杨洋    wpfui.com        ayui      ay  aaronyang=======请不要转载谢谢了。=========






推荐您阅读更多有关于“net core2,”的文章

猜你喜欢

额 本文暂时没人评论 来添加一个吧

发表评论

必填

选填

选填

必填

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

  查看权限

抖音:wpfui 工作wpf

目前在合肥企迈科技公司上班,加我QQ私聊

2023年11月网站停运,将搬到CSDN上

AYUI8全源码 Github地址:前往获取

杨洋(AaronYang简称AY,安徽六安人)AY唯一QQ:875556003和AY交流

高中学历,2010年开始web开发,2015年1月17日开始学习WPF

声明:AYUI7个人与商用免费,源码可购买。部分DEMO不免费

查看捐赠

AYUI7.X MVC教程 更新如下:

第一课 第二课 程序加密教程

标签列表