"
ASP.NET (snapshot 2017) Microsoft documentation and samples

Custom formatters in ASP.NET Core MVC web APIs

By Tom Dykstra

ASP.NET Core MVC has built-in support for data exchange in web APIs by using JSON, XML, or plain text formats. This article shows how to add support for additional formats by creating custom formatters.

View or download sample from GitHub.

When to use custom formatters

Use a custom formatter when you want the (xref:)content negotiation process to support a content type that isn’t supported by the built-in formatters (JSON, XML, and plain text).

For example, if some of the clients for your web API can handle the Protobuf format, you might want to use Protobuf with those clients because it’s more efficient. Or you might want your web API to send contact names and addresses in vCard format, a commonly used format for exchanging contact data. The sample app provided with this article implements a simple vCard formatter.

Overview of how to use a custom formatter

Here are the steps to create and use a custom formatter:

The following sections provide guidance and code examples for each of these steps.

How to create a custom formatter class

To create a formatter:

Derive from the appropriate base class

For text media types (for example, vCard), derive from the TextInputFormatter or TextOutputFormatter base class.

[!code-csharpMain]

   1:  using Microsoft.AspNetCore.Mvc.Formatters;
   2:  using Microsoft.AspNetCore.Http;
   3:  using System;
   4:  using System.Collections.Generic;
   5:  using System.Threading.Tasks;
   6:  using System.Text;
   7:  using CustomFormatterDemo.Models;
   8:  using Microsoft.Net.Http.Headers;
   9:  using System.Reflection;
  10:  using Microsoft.Extensions.Logging;
  11:   
  12:  namespace CustomFormatterDemo.Formatters
  13:  {
  14:      #region classdef
  15:      public class VcardOutputFormatter : TextOutputFormatter
  16:      #endregion
  17:      {
  18:          #region ctor
  19:          public VcardOutputFormatter()
  20:          {
  21:              SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
  22:   
  23:              SupportedEncodings.Add(Encoding.UTF8);
  24:              SupportedEncodings.Add(Encoding.Unicode);
  25:          }
  26:          #endregion
  27:   
  28:          #region canwritetype
  29:          protected override bool CanWriteType(Type type)
  30:          {
  31:              if (typeof(Contact).IsAssignableFrom(type) 
  32:                  || typeof(IEnumerable<Contact>).IsAssignableFrom(type))
  33:              {
  34:                  return base.CanWriteType(type);
  35:              }
  36:              return false;
  37:          }
  38:          #endregion
  39:   
  40:          #region writeresponse
  41:          public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
  42:          {
  43:              IServiceProvider serviceProvider = context.HttpContext.RequestServices;
  44:              var logger = serviceProvider.GetService(typeof(ILogger<VcardOutputFormatter>)) as ILogger;
  45:   
  46:              var response = context.HttpContext.Response;
  47:   
  48:              var buffer = new StringBuilder();
  49:              if (context.Object is IEnumerable<Contact>)
  50:              {
  51:                  foreach (Contact contact in context.Object as IEnumerable<Contact>)
  52:                  {
  53:                      FormatVcard(buffer, contact, logger);
  54:                  }
  55:              }
  56:              else
  57:              {
  58:                  var contact = context.Object as Contact;
  59:                  FormatVcard(buffer, contact, logger);
  60:              }
  61:              return response.WriteAsync(buffer.ToString());
  62:          }
  63:   
  64:          private static void FormatVcard(StringBuilder buffer, Contact contact, ILogger logger)
  65:          {
  66:              buffer.AppendLine("BEGIN:VCARD");
  67:              buffer.AppendLine("VERSION:2.1");
  68:              buffer.AppendFormat($"N:{contact.LastName};{contact.FirstName}\r\n");
  69:              buffer.AppendFormat($"FN:{contact.FirstName} {contact.LastName}\r\n");
  70:              buffer.AppendFormat($"UID:{contact.ID}\r\n");
  71:              buffer.AppendLine("END:VCARD");
  72:              logger.LogInformation($"Writing {contact.FirstName} {contact.LastName}");
  73:          }
  74:          #endregion
  75:      }
  76:  }

For binary types, derive from the InputFormatter or OutputFormatter base class.

Specify valid media types and encodings

In the constructor, specify valid media types and encodings by adding to the SupportedMediaTypes and SupportedEncodings collections.

[!code-csharpMain]

   1:  using Microsoft.AspNetCore.Mvc.Formatters;
   2:  using Microsoft.AspNetCore.Http;
   3:  using System;
   4:  using System.Collections.Generic;
   5:  using System.Threading.Tasks;
   6:  using System.Text;
   7:  using CustomFormatterDemo.Models;
   8:  using Microsoft.Net.Http.Headers;
   9:  using System.Reflection;
  10:  using Microsoft.Extensions.Logging;
  11:   
  12:  namespace CustomFormatterDemo.Formatters
  13:  {
  14:      #region classdef
  15:      public class VcardOutputFormatter : TextOutputFormatter
  16:      #endregion
  17:      {
  18:          #region ctor
  19:          public VcardOutputFormatter()
  20:          {
  21:              SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
  22:   
  23:              SupportedEncodings.Add(Encoding.UTF8);
  24:              SupportedEncodings.Add(Encoding.Unicode);
  25:          }
  26:          #endregion
  27:   
  28:          #region canwritetype
  29:          protected override bool CanWriteType(Type type)
  30:          {
  31:              if (typeof(Contact).IsAssignableFrom(type) 
  32:                  || typeof(IEnumerable<Contact>).IsAssignableFrom(type))
  33:              {
  34:                  return base.CanWriteType(type);
  35:              }
  36:              return false;
  37:          }
  38:          #endregion
  39:   
  40:          #region writeresponse
  41:          public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
  42:          {
  43:              IServiceProvider serviceProvider = context.HttpContext.RequestServices;
  44:              var logger = serviceProvider.GetService(typeof(ILogger<VcardOutputFormatter>)) as ILogger;
  45:   
  46:              var response = context.HttpContext.Response;
  47:   
  48:              var buffer = new StringBuilder();
  49:              if (context.Object is IEnumerable<Contact>)
  50:              {
  51:                  foreach (Contact contact in context.Object as IEnumerable<Contact>)
  52:                  {
  53:                      FormatVcard(buffer, contact, logger);
  54:                  }
  55:              }
  56:              else
  57:              {
  58:                  var contact = context.Object as Contact;
  59:                  FormatVcard(buffer, contact, logger);
  60:              }
  61:              return response.WriteAsync(buffer.ToString());
  62:          }
  63:   
  64:          private static void FormatVcard(StringBuilder buffer, Contact contact, ILogger logger)
  65:          {
  66:              buffer.AppendLine("BEGIN:VCARD");
  67:              buffer.AppendLine("VERSION:2.1");
  68:              buffer.AppendFormat($"N:{contact.LastName};{contact.FirstName}\r\n");
  69:              buffer.AppendFormat($"FN:{contact.FirstName} {contact.LastName}\r\n");
  70:              buffer.AppendFormat($"UID:{contact.ID}\r\n");
  71:              buffer.AppendLine("END:VCARD");
  72:              logger.LogInformation($"Writing {contact.FirstName} {contact.LastName}");
  73:          }
  74:          #endregion
  75:      }
  76:  }

[!NOTE]
You can’t do constructor dependency injection in a formatter class. For example, you can’t get a logger by adding a logger parameter to the constructor. To access services, you have to use the context object that gets passed in to your methods. A code example below shows how to do this.

Override CanReadType/CanWriteType

Specify the type you can deserialize into or serialize from by overriding the CanReadType or CanWriteType methods. For example, you might only be able to create vCard text from a Contact type and vice versa.

[!code-csharpMain]

   1:  using Microsoft.AspNetCore.Mvc.Formatters;
   2:  using Microsoft.AspNetCore.Http;
   3:  using System;
   4:  using System.Collections.Generic;
   5:  using System.Threading.Tasks;
   6:  using System.Text;
   7:  using CustomFormatterDemo.Models;
   8:  using Microsoft.Net.Http.Headers;
   9:  using System.Reflection;
  10:  using Microsoft.Extensions.Logging;
  11:   
  12:  namespace CustomFormatterDemo.Formatters
  13:  {
  14:      #region classdef
  15:      public class VcardOutputFormatter : TextOutputFormatter
  16:      #endregion
  17:      {
  18:          #region ctor
  19:          public VcardOutputFormatter()
  20:          {
  21:              SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
  22:   
  23:              SupportedEncodings.Add(Encoding.UTF8);
  24:              SupportedEncodings.Add(Encoding.Unicode);
  25:          }
  26:          #endregion
  27:   
  28:          #region canwritetype
  29:          protected override bool CanWriteType(Type type)
  30:          {
  31:              if (typeof(Contact).IsAssignableFrom(type) 
  32:                  || typeof(IEnumerable<Contact>).IsAssignableFrom(type))
  33:              {
  34:                  return base.CanWriteType(type);
  35:              }
  36:              return false;
  37:          }
  38:          #endregion
  39:   
  40:          #region writeresponse
  41:          public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
  42:          {
  43:              IServiceProvider serviceProvider = context.HttpContext.RequestServices;
  44:              var logger = serviceProvider.GetService(typeof(ILogger<VcardOutputFormatter>)) as ILogger;
  45:   
  46:              var response = context.HttpContext.Response;
  47:   
  48:              var buffer = new StringBuilder();
  49:              if (context.Object is IEnumerable<Contact>)
  50:              {
  51:                  foreach (Contact contact in context.Object as IEnumerable<Contact>)
  52:                  {
  53:                      FormatVcard(buffer, contact, logger);
  54:                  }
  55:              }
  56:              else
  57:              {
  58:                  var contact = context.Object as Contact;
  59:                  FormatVcard(buffer, contact, logger);
  60:              }
  61:              return response.WriteAsync(buffer.ToString());
  62:          }
  63:   
  64:          private static void FormatVcard(StringBuilder buffer, Contact contact, ILogger logger)
  65:          {
  66:              buffer.AppendLine("BEGIN:VCARD");
  67:              buffer.AppendLine("VERSION:2.1");
  68:              buffer.AppendFormat($"N:{contact.LastName};{contact.FirstName}\r\n");
  69:              buffer.AppendFormat($"FN:{contact.FirstName} {contact.LastName}\r\n");
  70:              buffer.AppendFormat($"UID:{contact.ID}\r\n");
  71:              buffer.AppendLine("END:VCARD");
  72:              logger.LogInformation($"Writing {contact.FirstName} {contact.LastName}");
  73:          }
  74:          #endregion
  75:      }
  76:  }

The CanWriteResult method

In some scenarios you have to override CanWriteResult instead of CanWriteType. Use CanWriteResult if the following conditions are true:

For example, suppose your action method signature returns a Person type, but it may return a Student or Instructor type that derives from Person. If you want your formatter to handle only Student objects, check the type of Object in the context object provided to the CanWriteResult method. Note that it’s not necessary to use CanWriteResult when the action method returns IActionResult; in that case, the CanWriteType method receives the runtime type.

### Override ReadRequestBodyAsync/WriteResponseBodyAsync

You do the actual work of deserializing or serializing in ReadRequestBodyAsync or WriteResponseBodyAsync. The highlighted lines in the following example show how to get services from the dependency injection container (you can’t get them from constructor parameters).

[!code-csharpMain]

   1:  using Microsoft.AspNetCore.Mvc.Formatters;
   2:  using Microsoft.AspNetCore.Http;
   3:  using System;
   4:  using System.Collections.Generic;
   5:  using System.Threading.Tasks;
   6:  using System.Text;
   7:  using CustomFormatterDemo.Models;
   8:  using Microsoft.Net.Http.Headers;
   9:  using System.Reflection;
  10:  using Microsoft.Extensions.Logging;
  11:   
  12:  namespace CustomFormatterDemo.Formatters
  13:  {
  14:      #region classdef
  15:      public class VcardOutputFormatter : TextOutputFormatter
  16:      #endregion
  17:      {
  18:          #region ctor
  19:          public VcardOutputFormatter()
  20:          {
  21:              SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
  22:   
  23:              SupportedEncodings.Add(Encoding.UTF8);
  24:              SupportedEncodings.Add(Encoding.Unicode);
  25:          }
  26:          #endregion
  27:   
  28:          #region canwritetype
  29:          protected override bool CanWriteType(Type type)
  30:          {
  31:              if (typeof(Contact).IsAssignableFrom(type) 
  32:                  || typeof(IEnumerable<Contact>).IsAssignableFrom(type))
  33:              {
  34:                  return base.CanWriteType(type);
  35:              }
  36:              return false;
  37:          }
  38:          #endregion
  39:   
  40:          #region writeresponse
  41:          public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
  42:          {
  43:              IServiceProvider serviceProvider = context.HttpContext.RequestServices;
  44:              var logger = serviceProvider.GetService(typeof(ILogger<VcardOutputFormatter>)) as ILogger;
  45:   
  46:              var response = context.HttpContext.Response;
  47:   
  48:              var buffer = new StringBuilder();
  49:              if (context.Object is IEnumerable<Contact>)
  50:              {
  51:                  foreach (Contact contact in context.Object as IEnumerable<Contact>)
  52:                  {
  53:                      FormatVcard(buffer, contact, logger);
  54:                  }
  55:              }
  56:              else
  57:              {
  58:                  var contact = context.Object as Contact;
  59:                  FormatVcard(buffer, contact, logger);
  60:              }
  61:              return response.WriteAsync(buffer.ToString());
  62:          }
  63:   
  64:          private static void FormatVcard(StringBuilder buffer, Contact contact, ILogger logger)
  65:          {
  66:              buffer.AppendLine("BEGIN:VCARD");
  67:              buffer.AppendLine("VERSION:2.1");
  68:              buffer.AppendFormat($"N:{contact.LastName};{contact.FirstName}\r\n");
  69:              buffer.AppendFormat($"FN:{contact.FirstName} {contact.LastName}\r\n");
  70:              buffer.AppendFormat($"UID:{contact.ID}\r\n");
  71:              buffer.AppendLine("END:VCARD");
  72:              logger.LogInformation($"Writing {contact.FirstName} {contact.LastName}");
  73:          }
  74:          #endregion
  75:      }
  76:  }

How to configure MVC to use a custom formatter

To use a custom formatter, add an instance of the formatter class to the InputFormatters or OutputFormatters collection.

[!code-csharpMain]

   1:  using CustomFormatterDemo.Formatters;
   2:  using CustomFormatterDemo.Models;
   3:  using Microsoft.AspNetCore.Builder;
   4:  using Microsoft.AspNetCore.Hosting;
   5:  using Microsoft.Extensions.Configuration;
   6:  using Microsoft.Extensions.DependencyInjection;
   7:  using Microsoft.Extensions.Logging;
   8:   
   9:  namespace CustomFormatterDemo
  10:  {
  11:      public class Startup
  12:      {
  13:          public Startup(IHostingEnvironment env)
  14:          {
  15:              var builder = new ConfigurationBuilder()
  16:                  .SetBasePath(env.ContentRootPath)
  17:                  .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
  18:                  .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
  19:                  .AddEnvironmentVariables();
  20:              Configuration = builder.Build();
  21:          }
  22:   
  23:          public IConfigurationRoot Configuration { get; }
  24:   
  25:          // This method gets called by the runtime. Use this method to add services to the container.
  26:          public void ConfigureServices(IServiceCollection services)
  27:          {
  28:              // Add framework services.
  29:              #region mvcoptions
  30:              services.AddMvc(options =>
  31:              {
  32:                  options.InputFormatters.Insert(0, new VcardInputFormatter());
  33:                  options.OutputFormatters.Insert(0, new VcardOutputFormatter());
  34:              });
  35:              #endregion
  36:   
  37:              services.AddSingleton<IContactRepository, ContactRepository>();
  38:          }
  39:   
  40:          // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
  41:          public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
  42:          {
  43:              loggerFactory.AddConsole(Configuration.GetSection("Logging"));
  44:              loggerFactory.AddDebug();
  45:   
  46:              app.UseMvc();
  47:          }
  48:      }
  49:  }

Formatters are evaluated in the order you insert them. The first one takes precedence.

Next steps

See the sample application, which implements simple vCard input and output formatters. The application reads and writes vCards that look like the following example:

BEGIN:VCARD
VERSION:2.1
N:Davolio;Nancy
FN:Nancy Davolio
UID:20293482-9240-4d68-b475-325df4a83728
END:VCARD

To see vCard output, run the application and send a Get request with Accept header “text/vcard” to http://localhost:63313/api/contacts/ (when running from Visual Studio) or http://localhost:5000/api/contacts/ (when running from the command line).

To add a vCard to the in-memory collection of contacts, send a Post request to the same URL, with Content-Type header “text/vcard” and with vCard text in the body, formatted like the example above.



Comments ( )
Link to this page: //www.vb-net.com/AspNet-DocAndSamples-2017/aspnetcore/mvc/advanced/custom-formatters.htm
< THANKS ME>