Der Datenaustausch kann in ASP.NET Core MVC per JSON, XML bzw. reinem Text geschehen. Dies ist allerdings nicht für alle Fälle ausreichend. Daher kann man selbst eingreifen und für zusätzliche Unterstützung sorgen. Dieser Artikel zeigt, wie man das iCalendar-Format unterstützt.
Grundlagen
Formatter werden verwendet, um Inhaltsanfragen (siehe Accept
-Header – Stichwort Content Negotiation) zu verarbeiten. Das Standard-Format in ASP.NET Core MVC ist JSON (application/json
). Sendet der Client einen Accept-Header
mit einem unbekannten Inhaltstyp, erfolgt eine Umleitung auf den Standard-Typ. Das ist (sofern keine Änderung erfolgte), JSON. Die Verwendung von XML muss explizit konfiguriert werden (siehe Startup.cs
):
services.AddMvc()
.AddXmlSerializerFormatters();
Formatter können für beide Richtungen, also Input und Output, implementiert werden. Ebenfalls gibt es die Möglichkeit per Text (siehe TextInputFormatter
und TextOutputFormatter
), sowie per Streams (siehe StreamInputFormatter
und StreamOutputFormatter
) zu arbeiten.
Konkrete Implementierung
Im Beispiel soll es folgende Möglichkeiten geben:
- Rückgabe eines Termins als JSON
- Rückgabe eines Termins im iCalendar-Format
- Direktaufruf Link ohne eigenen
Accept
-Header für Download
Da nur Daten zurückgegeben, aber nicht angenommen werden, ist nur ein Output-Formatter zu implementieren. Der nachfolgende Code zeigt die Details. Im Konstruktor werden die unterstützten MediaTypes angegeben.
public class IcsOutputFormatter : TextOutputFormatter
{
public IcsOutputFormatter()
{
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/x-ical"));
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/ical"));
}
protected override bool CanWriteType(Type type)
{
// this should only be available for appointments
if (typeof(Appointment).IsAssignableFrom(type)
|| typeof(IEnumerable<Appointment>).IsAssignableFrom(type))
{
return base.CanWriteType(type);
}
return false;
}
public override Task WriteResponseBodyAsync(
OutputFormatterWriteContext context,
Encoding selectedEncoding
)
{
IServiceProvider serviceProvider = context.HttpContext.RequestServices;
var logger = serviceProvider.GetService(typeof(ILogger<IcsOutputFormatter>)) as ILogger;
var response = context.HttpContext.Response;
var buffer = new StringBuilder();
if (context.Object is IEnumerable<Appointment>)
{
foreach (Appointment appointment in context.Object as IEnumerable<Appointment>)
{
FormatIcs(buffer, appointment, logger);
}
}
else
{
var appointment = context.Object as Appointment;
FormatIcs(buffer, appointment, logger);
}
return response.WriteAsync(buffer.ToString());
}
// more code
}
In der Methode CanWriteType
wird auf einen konkreten Typ eingeschränkt. Zugelassen wird werden alle Objekte, die ein Appointment
oder eine Liste davon darstellen.
Sollen auch Daten im iCalendar-Format empfangen und verarbeitet werden können, müsste ein Input-Formatter implementiert werden. Dazu wäre eine Ableitung von TextInputFormatter
vorzunehmen.
Konfiguration
In der Methode ConfigureServices
wird der neue IcsOutputFormatter
gesetzt. Ebenfalls wird ein MediaType-Mapping von ics
auf application/x-ical
gesetzt. Darüber wird die Extension ics
in der URL ermöglicht. Wird diese gesetzt, erfolgt eine Behandlung, für den dazu gemappten MediaType, also unserem Formatter.
services.AddMvc(options =>
{
options.OutputFormatters.Insert(0, new IcsOutputFormatter());
// for accessing via URL
options.FormatterMappings.SetMediaTypeMappingForFormat("ics", "application/x-ical");
});
Damit das MediaType-Mapping funktioniert, müssen die dafür in Frage kommenden Methoden mit zusätzlichen Attributen versehen werden:
[Route("api/1/appointment"), EnableCors("AllowAllOrigins")]
public class AppointmentController : Controller
{
[FormatFilter]
[HttpGet("{id}"), HttpGet("{id}.{format}")]
public Appointment Get(int id)
{
var organizer = new User()
{
FirstName = "Norbert",
LastName = "Eder",
Organization = "NE",
Email = "thisismyemail@herewego"
};
return new Appointment()
{
IsPublic = true,
Organizer = organizer,
Description = "Wall of text",
Title = "So important",
Location = "Vienna",
Start = DateTime.Now,
End = DateTime.Now.AddHours(2)
};
}
}
Das FormatFilterAttribute
definiert, dass ein format
-Wert der Route verwendet wird. So wird im Beispiel die Route mit{id}.{format}
definiert. d.h. mit /api/1/appointment/1.ics
werden die Daten im iCalendar-Format zurückgeliefert.
Das FormatFilterAttribute
erzwingt die format
-Angabe. Damit dies auch ohne möglich wird, kann die Route zweimal konfiguriert werden:
[HttpGet("{id}"), HttpGet("{id}.{format}")]
Nun sind beide Varianten möglich. Ohne Format wird die Default-Verarbeitung angeworfen. In der Regel wird also JSON zurückgegeben.
Demo-Anwendung
Das gezeigte Beispiel steht via GitHub zur Verfügung und zeigt den gezeigten IcsOutputFormatter
in Aktion.
Viel Spaß beim Entwickeln!