Manipulate response in web api in .net Core MVC Pipeline - Solution 1

Manipulate response in web api in .net Core MVC Pipeline - Solution 1

Using Action Filter to clear the output formatter

I ran into a problem where I had a web api and the endpoints had to serve data in XML format for two different vendors. The result of both the endpoints is the same object and data but with some minor differences like namespaces. I didn't want to go with the obvious solution of serializing the object with the different namespaces and returning them from the endpoint. I wanted to explore other ways to tackle this problem

This blog post is in 3 parts because I found three different ways to Intercept and manipulate the response, each with its pros and cons, which I will be discussing too.

To demonstrate the solution I'm using a weather station api as my example, the api has two endpoints one returns the weather data for Quebec, Canada and other reports the weather data of Washington, U.S. Both the endpoint returns the data in xml format the data set schema is same, but the namespaces are different.

Using Action Filters and Output formatter

Using Action Filter to clear the output formatter of the MVC pipeline and adding the vendor-specific formatter

Details

I created two output formatters inheriting from XmlSerializerOutputFormatter. I serialize the object to the correct format using the vendor-specific XML namespaces.

code_output_formatters.jpeg

 namespace OutputFormatter.API
{
    public class WashingtonOutputFormatter : XmlSerializerOutputFormatter
    {
        public WashingtonOutputFormatter() { }
        public WashingtonOutputFormatter(XmlWriterSettings xmlWriterSettings) : base(xmlWriterSettings) { }

        protected override void Serialize(XmlSerializer xmlSerializer, XmlWriter xmlWriter, object value)
        {
            var namespaces = new XmlSerializerNamespaces();
            namespaces.Add("WS", "http://www.wausweatherStation.com/xml/namespaces/type");
            namespaces.Add("Metric", "http://www.wausweatherstation.com/xml/namespaces/siunit/temperature");
            xmlSerializer.Serialize(xmlWriter, value, namespaces);
        }
    }
}

Create two ActionFilterAttribute, in this case for Quebec Canada, and another for Washington. These two action filters will clear the existing output filters in the MVC pipeline and add the corresponding output filter. Quebec Canada Action filter will add the Quebec Canada output formatter to the pipeline and the Washington Action filter will add only the Washington output formatter.

code_action_filter.jpeg

public class WashingtonOutputFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        var washingtonOutputFormat = new WashingtonOutputFormatter(new XmlWriterSettings() {OmitXmlDeclaration = true});

        var outputFormattersToRemove = new List<IOutputFormatter>()
        {
            new XmlSerializerOutputFormatter(),
            new SystemTextJsonOutputFormatter(new System.Text.Json.JsonSerializerOptions()),
            new QuebecCanadaOutputFormatter()
        };
        var commonFormatter = new FilterActionCommon();
        commonFormatter.ClearOutputFormatters(context, outputFormattersToRemove);
        commonFormatter.AddOutputFormatter(context, washingtonOutputFormat);
        base.OnActionExecuting(context);
    }
}

Following is the output I would receive,

from endpoint 1: Washington Weather Station

<WeatherForecast xmlns:WS="http://www.wausweatherStation.com/xml/namespaces/type" xmlns:Metric="http://www.wausweatherstation.com/xml/namespaces/siunit/temperature">
  <Date>2020-06-02T13:50:52.5094204-04:00</Date>
  <Temperature>
    <Celsius>8</Celsius>
    <Farenheit>46</Farenheit>
  </Temperature>
  <Summary>Mild</Summary>
</WeatherForecast>

from endpoint 2: Quebec Canada Weather Station

<WeatherForecast xmlns:WS="http://www.qubeccanadaweatherstation.com/xml/namespaces/type" xmlns:Metric="http://www.qubeccanadaweatherstation.com/xml/namespaces/siunit/temperature">
  <Date>2020-06-02T13:56:09.5506734-04:00</Date>
  <Temperature>
    <Celsius>34</Celsius>
    <Farenheit>93</Farenheit>
  </Temperature>
  <Summary>Cool</Summary>
</WeatherForecast>

if you look closely the only difference between these two responses are the namespaces

Pros and Cons of this approach

Pros

  • Code is nice separated, i.e business logic is taken care of by the API, and formatting the output is achieved by the formatters.

Cons

  • We are directly manipulating the output formatters in the MVC pipeline.
  • But I believe this may interfere with other endpoints (Unable to test in my machine), I also noticed a null reference exception from this area after hosting in the cloud.

Code

The sample code is here Solution file OutputFormatters-Pipeline.sln