I'm going to make this post kind of a problem and solution post. In my pursuit to solve my problem, I stumbled upon three different solutions. I'm going to make each of these solutions a different post/ part. I will also discuss the pros and cons that I found with each of the solutions.

The Problem:

I have a web API with two endpoints, the endpoints serve data in XML format for two different vendors. The result of both the endpoints is the same object but there are some differences like namespaces. The problem is how to return the same object with only namespaces serialized differently? I didn't want to go with the obvious solution of serializing the object with the different namespaces and returning them. I wanted to explore other ways to tackle this. Why? because this will be fun ;)

Solution 1

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

Pros and Cons of the solution

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.
  • Unable to test in my machine, but I believe this may interfere with other endpoints, I also noticed a null reference exception from this area after hosting in the cloud.

Implementation

Step 1

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

The snippet of output formatter

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

    }
}

Step 2

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 Washington output formatter.

screen shot of action filter attribute
public class WashingtonOuputFilterAttribute : 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);
        }
    }

Output
Endpoint 1: Washington Weather Staion

<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>

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>

you can see that the only difference between the two output are the namespaces.

Solution 2 is here

Solution 3 is here

Code
The sample code for this is here
or https://github.com/BipinBlog/asp.net_response_manipulation
Solution : OutputFormatters-Pipeline.sln