Tuesday, April 19, 2016

WebApi - Creating custom MediaTypeFormatter for CSV format

In this example we are going to create a custom formatter to generate CSV format response using WebApi.

The technology we will be using:
  • Visual Studio 2015
  • ASP.NET WebApi 5.2.3
  • C#


What is the Media Type Formatter?

The Media Type Formatter is used during the serialization process, in other words it`s the type used to translate a .NET Common Language Runtime (CLR) type into a format that can be transmitted over HTTP. The default formats are either JSON or XML.


A media type formatter, which is an object of type MediaTypeFormatter, performs the serialization in the ASP.NET Web API pipeline.

Step #1 - Create the MVC Project

In this example we will need to create a simple WebApi Project, no authentication is required.



Step #2 - Create a Model Class

Now we need to create a model class to be serialized to the CSV format. This is shown in the following code.


    public class Employee
    {
        public int EmployeeId { get; set; }
        public String Name { get; set; }
        public DateTime AdmissionDate { get; set; }
    }

Step #3 - Create the CSV Formatter

Now let`s create a custom formatter to handle the CSV format.
To do it we will need to create a class and inherit from BufferedMediaTypeFormatter. This is shown in the following code.


public class EmployeeCsvFormatter : BufferedMediaTypeFormatter
{
      public EmployeeCsvFormatter()
      {
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/csv"));
            SupportedEncodings.Add(new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
            SupportedEncodings.Add(Encoding.GetEncoding("iso-8859-1"));
      }
      public EmployeeCsvFormatter(MediaTypeMapping mediaTypeMapping) : this()
      {
            MediaTypeMappings.Add(mediaTypeMapping);
      }
 
      public EmployeeCsvFormatter(IEnumerable<MediaTypeMapping> mediaTypeMappings) : this()
      {
            foreach (var mediaTypeMapping in mediaTypeMappings)
                MediaTypeMappings.Add(mediaTypeMapping);
 
      }
}
Above, no matter which constructor you use, we always add text/csv media type to be supported for this formatter. We also allow custom MediaTypeMappings to be injected.
The MediaTypeFormatter also supports multiple Encodings and in the example above we added two encodings to the Formatter.

Now let`s override the methods: MediaTypeFormatter.CanWriteType and BufferedMediaTypeFormatter.WriteToStream .

In the MediaTypeFormatter.CanWriteType method we need to check if the type is supported with this formatter in order to write it or not.

 public override bool CanWriteType(System.Type type)
        {
            if (type == typeof(Employee))
            {
                return true;
            }
            else
            {
                Type enumerableType = typeof(IEnumerable<Employee>);
                return enumerableType.IsAssignableFrom(type);
            }
        }



Now we need to implement the BufferedMediaTypeFormatter.WriteToStream method. This method will write your CSV content to the Stream. This is shown in the following code.



public override void WriteToStream(Type type, object value, Stream writeStream, HttpContent content)
        {
            Encoding effectiveEncoding = SelectCharacterEncoding(content.Headers);
            using (var writer = new StreamWriter(writeStream))
            {
                var employees = value as IEnumerable<Employee>;
                if (employees != null)
                {
                    foreach (var employee in employees)
                        WriteItem(employee, writer);
 
                }
                else
                {
                    var singleProduct = value as Employee;
                    if (singleProduct == null)
                        throw new InvalidOperationException("Cannot serialize type");
 
                    WriteItem(singleProduct, writer);
                }
            }
        }
        private void WriteItem(Employee employee, StreamWriter writer)
        {
            writer.WriteLine("{0};{1};{2}", Escape(employee.EmployeeId),
                Escape(employee.Name), employee.AdmissionDate.ToString("d"));
        }
 
        static char[] _specialChars = new char[] { ';', '\n', '\r', '"' };
        private string Escape(object o)
        {
            if (o == null)
            {
                return "";
            }
            string field = o.ToString();
            if (field.IndexOfAny(_specialChars) != -1)
            {
                // Delimit the entire field with quotes and replace embedded quotes with "".
                return String.Format("\"{0}\"", field.Replace("\"", "\"\""));
            }
            else return field;
        }

Above, we are selecting the desired Encoding and we also have created two new methods to help on the content writing. Source: http://www.asp.net/web-api/overview/formats-and-model-binding/media-formatters

If you want to download your CSV when you call the WebApi, you can override the method MediaTypeFormatter.SetDefaultContentHeaders. This is shown in the following code.

public override void SetDefaultContentHeaders(Type type, HttpContentHeaders headers, MediaTypeHeaderValue mediaType)
        {
            base.SetDefaultContentHeaders(type, headers, mediaType);
            headers.Add("Content-Disposition", "attachment; filename=Employees.csv");
            headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
        }


Step #4 - Register the formatter

Now that the formatter is created we need to register it. Go to your WebApiConfig class or to the Global.asax and add the following code.


config.Formatters.Add(new EmployeeCsvFormatter(new QueryStringMapping("format", "csv", "text/csv")));



The QueryStringMapping will map the query string "format=csv" to the EmployeeCsvFormatter.

Step #5 - Create the WebApi Controller

Now wee need to create the WebApi Controller with the action that will return the employees. This is shown in the following code.


    [RoutePrefix("api/Employee")]
    public class EmployeeController : ApiController
    {
        public Employee[] Get()
        {
            var result = new[]
                       {
                   new Employee {AdmissionDate = DateTime.Now.AddYears(-10), Name = "John;", EmployeeId = 1 },
                   new Employee {AdmissionDate = DateTime.Now.AddYears(-3), Name = "Mark", EmployeeId = 2 },
                   new Employee {AdmissionDate = DateTime.Now.AddYears(-5), Name = "Ann", EmployeeId = 3 }
               };
 
            return result;
        }
    }


Now we have everything ready, to test it execute the application and call the api ( /api/employee?format=csv ) to see the result. Enjoy!

2 comments:

  1. Really nice blog post.provided a helpful information.I hope that you will post more updates like this
    Dot NET Online Training

    ReplyDelete
  2. Many of the writing systems that are being sold are based on speed writing. But what is speed writing? How do you use it? What are the advantages and disadvantages? This article gives you an introduction to speed writing and discusses some of the good and bad. https://globalscience.ru/news/intznat/8890-kak-pravilno-podgotovitsya-k-ekzamenu.html

    ReplyDelete

Microservices – Creating Resilient Services

Dealing with unexpected failures is one of the hardest problems to solve, especially in a distributed system . A microservice needs to...