Training
Module
Interact with an ASP.NET Core minimal API - Training
Interact with an ASP.NET Core minimal API
This browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
ASP.NET Core MVC supports data exchange in Web APIs using input and output formatters. Input formatters are used by Model Binding. Output formatters are used to format responses.
The framework provides built-in input and output formatters for JSON and XML. It provides a built-in output formatter for plain text, but doesn't provide an input formatter for plain text.
This article shows how to add support for additional formats by creating custom formatters. For an example of a custom plain text input formatter, see TextPlainInputFormatter on GitHub.
View or download sample code (how to download)
Use a custom formatter to add support for a content type that isn't handled by the built-in formatters.
To create a custom formatter:
To create a formatter:
The following code shows the VcardOutputFormatter
class from the sample:
public class VcardOutputFormatter : TextOutputFormatter
{
public VcardOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
protected override bool CanWriteType(Type? type)
=> typeof(Contact).IsAssignableFrom(type)
|| typeof(IEnumerable<Contact>).IsAssignableFrom(type);
public override async Task WriteResponseBodyAsync(
OutputFormatterWriteContext context, Encoding selectedEncoding)
{
var httpContext = context.HttpContext;
var serviceProvider = httpContext.RequestServices;
var logger = serviceProvider.GetRequiredService<ILogger<VcardOutputFormatter>>();
var buffer = new StringBuilder();
if (context.Object is IEnumerable<Contact> contacts)
{
foreach (var contact in contacts)
{
FormatVcard(buffer, contact, logger);
}
}
else
{
FormatVcard(buffer, (Contact)context.Object!, logger);
}
await httpContext.Response.WriteAsync(buffer.ToString(), selectedEncoding);
}
private static void FormatVcard(
StringBuilder buffer, Contact contact, ILogger logger)
{
buffer.AppendLine("BEGIN:VCARD");
buffer.AppendLine("VERSION:2.1");
buffer.AppendLine($"N:{contact.LastName};{contact.FirstName}");
buffer.AppendLine($"FN:{contact.FirstName} {contact.LastName}");
buffer.AppendLine($"UID:{contact.Id}");
buffer.AppendLine("END:VCARD");
logger.LogInformation("Writing {FirstName} {LastName}",
contact.FirstName, contact.LastName);
}
}
For text media types (for example, vCard), derive from the TextInputFormatter or TextOutputFormatter base class:
public class VcardOutputFormatter : TextOutputFormatter
For binary types, derive from the InputFormatter or OutputFormatter base class.
In the constructor, specify supported media types and encodings by adding to the SupportedMediaTypes and SupportedEncodings collections:
public VcardOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
A formatter class can not use constructor injection for its dependencies. For example, ILogger<VcardOutputFormatter>
can't be added as a parameter to the constructor. To access services, use the context object that gets passed in to the methods. A code example in this article and the sample show how to do this.
Specify the type to deserialize into or serialize from by overriding the CanReadType or CanWriteType methods. For example, to create vCard text from a Contact
type and vice versa:
protected override bool CanWriteType(Type? type)
=> typeof(Contact).IsAssignableFrom(type)
|| typeof(IEnumerable<Contact>).IsAssignableFrom(type);
In some scenarios, CanWriteResult must be overridden rather than CanWriteType. Use CanWriteResult
if the following conditions are true:
For example, suppose the action method:
Person
type.Student
or Instructor
type that derives from Person
.For the formatter to handle only Student
objects, check the type of Object in the context object provided to the CanWriteResult
method. When the action method returns IActionResult:
CanWriteResult
.CanWriteType
method receives the runtime type.Deserialization or serialization is performed in ReadRequestBodyAsync or WriteResponseBodyAsync. The following example shows how to get services from the dependency injection container. Services can't be obtained from constructor parameters:
public override async Task WriteResponseBodyAsync(
OutputFormatterWriteContext context, Encoding selectedEncoding)
{
var httpContext = context.HttpContext;
var serviceProvider = httpContext.RequestServices;
var logger = serviceProvider.GetRequiredService<ILogger<VcardOutputFormatter>>();
var buffer = new StringBuilder();
if (context.Object is IEnumerable<Contact> contacts)
{
foreach (var contact in contacts)
{
FormatVcard(buffer, contact, logger);
}
}
else
{
FormatVcard(buffer, (Contact)context.Object!, logger);
}
await httpContext.Response.WriteAsync(buffer.ToString(), selectedEncoding);
}
private static void FormatVcard(
StringBuilder buffer, Contact contact, ILogger logger)
{
buffer.AppendLine("BEGIN:VCARD");
buffer.AppendLine("VERSION:2.1");
buffer.AppendLine($"N:{contact.LastName};{contact.FirstName}");
buffer.AppendLine($"FN:{contact.FirstName} {contact.LastName}");
buffer.AppendLine($"UID:{contact.Id}");
buffer.AppendLine("END:VCARD");
logger.LogInformation("Writing {FirstName} {LastName}",
contact.FirstName, contact.LastName);
}
To use a custom formatter, add an instance of the formatter class to the MvcOptions.InputFormatters or MvcOptions.OutputFormatters collection:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(options =>
{
options.InputFormatters.Insert(0, new VcardInputFormatter());
options.OutputFormatters.Insert(0, new VcardOutputFormatter());
});
Formatters are evaluated in the order they're inserted, where the first one takes precedence.
The following code shows the VcardInputFormatter
class from the sample:
public class VcardInputFormatter : TextInputFormatter
{
public VcardInputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
protected override bool CanReadType(Type type)
=> type == typeof(Contact);
public override async Task<InputFormatterResult> ReadRequestBodyAsync(
InputFormatterContext context, Encoding effectiveEncoding)
{
var httpContext = context.HttpContext;
var serviceProvider = httpContext.RequestServices;
var logger = serviceProvider.GetRequiredService<ILogger<VcardInputFormatter>>();
using var reader = new StreamReader(httpContext.Request.Body, effectiveEncoding);
string? nameLine = null;
try
{
await ReadLineAsync("BEGIN:VCARD", reader, context, logger);
await ReadLineAsync("VERSION:", reader, context, logger);
nameLine = await ReadLineAsync("N:", reader, context, logger);
var split = nameLine.Split(";".ToCharArray());
var contact = new Contact(FirstName: split[1], LastName: split[0].Substring(2));
await ReadLineAsync("FN:", reader, context, logger);
await ReadLineAsync("END:VCARD", reader, context, logger);
logger.LogInformation("nameLine = {nameLine}", nameLine);
return await InputFormatterResult.SuccessAsync(contact);
}
catch
{
logger.LogError("Read failed: nameLine = {nameLine}", nameLine);
return await InputFormatterResult.FailureAsync();
}
}
private static async Task<string> ReadLineAsync(
string expectedText, StreamReader reader, InputFormatterContext context,
ILogger logger)
{
var line = await reader.ReadLineAsync();
if (line is null || !line.StartsWith(expectedText))
{
var errorMessage = $"Looked for '{expectedText}' and got '{line}'";
context.ModelState.TryAddModelError(context.ModelName, errorMessage);
logger.LogError(errorMessage);
throw new Exception(errorMessage);
}
return line;
}
}
Run the sample app for this article, which implements basic vCard input and output formatters. The app reads and writes vCards similar to the following format:
BEGIN:VCARD
VERSION:2.1
N:Davolio;Nancy
FN:Nancy Davolio
END:VCARD
To see vCard output, run the app and send a Get request with Accept header text/vcard
to https://localhost:<port>/api/contacts
.
To add a vCard to the in-memory collection of contacts:
Post
request to /api/contacts
with a tool like http-repl.Content-Type
header to text/vcard
.vCard
text in the body, formatted like the preceding example.ASP.NET Core MVC supports data exchange in Web APIs using input and output formatters. Input formatters are used by Model Binding. Output formatters are used to format responses.
The framework provides built-in input and output formatters for JSON and XML. It provides a built-in output formatter for plain text, but doesn't provide an input formatter for plain text.
This article shows how to add support for additional formats by creating custom formatters. For an example of a custom plain text input formatter, see TextPlainInputFormatter on GitHub.
View or download sample code (how to download)
Use a custom formatter to add support for a content type that isn't handled by the built-in formatters.
To create a custom formatter:
To create a formatter:
The following code shows the VcardOutputFormatter
class from the sample:
public class VcardOutputFormatter : TextOutputFormatter
{
public VcardOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
protected override bool CanWriteType(Type type)
{
return typeof(Contact).IsAssignableFrom(type) ||
typeof(IEnumerable<Contact>).IsAssignableFrom(type);
}
public override async Task WriteResponseBodyAsync(
OutputFormatterWriteContext context, Encoding selectedEncoding)
{
var httpContext = context.HttpContext;
var serviceProvider = httpContext.RequestServices;
var logger = serviceProvider.GetRequiredService<ILogger<VcardOutputFormatter>>();
var buffer = new StringBuilder();
if (context.Object is IEnumerable<Contact> contacts)
{
foreach (var contact in contacts)
{
FormatVcard(buffer, contact, logger);
}
}
else
{
FormatVcard(buffer, (Contact)context.Object, logger);
}
await httpContext.Response.WriteAsync(buffer.ToString(), selectedEncoding);
}
private static void FormatVcard(
StringBuilder buffer, Contact contact, ILogger logger)
{
buffer.AppendLine("BEGIN:VCARD");
buffer.AppendLine("VERSION:2.1");
buffer.AppendLine($"N:{contact.LastName};{contact.FirstName}");
buffer.AppendLine($"FN:{contact.FirstName} {contact.LastName}");
buffer.AppendLine($"UID:{contact.Id}");
buffer.AppendLine("END:VCARD");
logger.LogInformation("Writing {FirstName} {LastName}",
contact.FirstName, contact.LastName);
}
}
For text media types (for example, vCard), derive from the TextInputFormatter or TextOutputFormatter base class:
public class VcardOutputFormatter : TextOutputFormatter
For binary types, derive from the InputFormatter or OutputFormatter base class.
In the constructor, specify supported media types and encodings by adding to the SupportedMediaTypes and SupportedEncodings collections:
public VcardOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
A formatter class can not use constructor injection for its dependencies. For example, ILogger<VcardOutputFormatter>
can't be added as a parameter to the constructor. To access services, use the context object that gets passed in to the methods. A code example in this article and the sample show how to do this.
Specify the type to deserialize into or serialize from by overriding the CanReadType or CanWriteType methods. For example, to create vCard text from a Contact
type and vice versa:
protected override bool CanWriteType(Type type)
{
return typeof(Contact).IsAssignableFrom(type) ||
typeof(IEnumerable<Contact>).IsAssignableFrom(type);
}
In some scenarios, CanWriteResult must be overridden rather than CanWriteType. Use CanWriteResult
if the following conditions are true:
For example, suppose the action method:
Person
type.Student
or Instructor
type that derives from Person
.For the formatter to handle only Student
objects, check the type of Object in the context object provided to the CanWriteResult
method. When the action method returns IActionResult:
CanWriteResult
.CanWriteType
method receives the runtime type.Deserialization or serialization is performed in ReadRequestBodyAsync or WriteResponseBodyAsync. The following example shows how to get services from the dependency injection container. Services can't be obtained from constructor parameters:
public override async Task WriteResponseBodyAsync(
OutputFormatterWriteContext context, Encoding selectedEncoding)
{
var httpContext = context.HttpContext;
var serviceProvider = httpContext.RequestServices;
var logger = serviceProvider.GetRequiredService<ILogger<VcardOutputFormatter>>();
var buffer = new StringBuilder();
if (context.Object is IEnumerable<Contact> contacts)
{
foreach (var contact in contacts)
{
FormatVcard(buffer, contact, logger);
}
}
else
{
FormatVcard(buffer, (Contact)context.Object, logger);
}
await httpContext.Response.WriteAsync(buffer.ToString(), selectedEncoding);
}
private static void FormatVcard(
StringBuilder buffer, Contact contact, ILogger logger)
{
buffer.AppendLine("BEGIN:VCARD");
buffer.AppendLine("VERSION:2.1");
buffer.AppendLine($"N:{contact.LastName};{contact.FirstName}");
buffer.AppendLine($"FN:{contact.FirstName} {contact.LastName}");
buffer.AppendLine($"UID:{contact.Id}");
buffer.AppendLine("END:VCARD");
logger.LogInformation("Writing {FirstName} {LastName}",
contact.FirstName, contact.LastName);
}
To use a custom formatter, add an instance of the formatter class to the MvcOptions.InputFormatters or MvcOptions.OutputFormatters collection:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
{
options.InputFormatters.Insert(0, new VcardInputFormatter());
options.OutputFormatters.Insert(0, new VcardOutputFormatter());
});
}
Formatters are evaluated in the order you insert them. The first one takes precedence.
The following code shows the VcardInputFormatter
class from the sample:
public class VcardInputFormatter : TextInputFormatter
{
public VcardInputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
protected override bool CanReadType(Type type)
{
return type == typeof(Contact);
}
public override async Task<InputFormatterResult> ReadRequestBodyAsync(
InputFormatterContext context, Encoding effectiveEncoding)
{
var httpContext = context.HttpContext;
var serviceProvider = httpContext.RequestServices;
var logger = serviceProvider.GetRequiredService<ILogger<VcardInputFormatter>>();
using var reader = new StreamReader(httpContext.Request.Body, effectiveEncoding);
string nameLine = null;
try
{
await ReadLineAsync("BEGIN:VCARD", reader, context, logger);
await ReadLineAsync("VERSION:", reader, context, logger);
nameLine = await ReadLineAsync("N:", reader, context, logger);
var split = nameLine.Split(";".ToCharArray());
var contact = new Contact
{
LastName = split[0].Substring(2),
FirstName = split[1]
};
await ReadLineAsync("FN:", reader, context, logger);
await ReadLineAsync("END:VCARD", reader, context, logger);
logger.LogInformation("nameLine = {nameLine}", nameLine);
return await InputFormatterResult.SuccessAsync(contact);
}
catch
{
logger.LogError("Read failed: nameLine = {nameLine}", nameLine);
return await InputFormatterResult.FailureAsync();
}
}
private static async Task<string> ReadLineAsync(
string expectedText, StreamReader reader, InputFormatterContext context,
ILogger logger)
{
var line = await reader.ReadLineAsync();
if (!line.StartsWith(expectedText))
{
var errorMessage = $"Looked for '{expectedText}' and got '{line}'";
context.ModelState.TryAddModelError(context.ModelName, errorMessage);
logger.LogError(errorMessage);
throw new Exception(errorMessage);
}
return line;
}
}
Run the sample app for this article, which implements basic vCard input and output formatters. The app reads and writes vCards similar to the following format:
BEGIN:VCARD
VERSION:2.1
N:Davolio;Nancy
FN:Nancy Davolio
END:VCARD
To see vCard output, run the app and send a Get request with Accept header text/vcard
to https://localhost:5001/api/contacts
.
To add a vCard to the in-memory collection of contacts:
Post
request to /api/contacts
with a tool like curl.Content-Type
header to text/vcard
.vCard
text in the body, formatted like the preceding example.ASP.NET Core feedback
ASP.NET Core is an open source project. Select a link to provide feedback:
Training
Module
Interact with an ASP.NET Core minimal API - Training
Interact with an ASP.NET Core minimal API
Events
Apr 8, 3 PM - May 28, 7 AM
Sharpen your AI skills and enter the sweepstakes to win a free Certification exam
Register now!