.Net 5 Custom Output Formatter

Çağlar Can SARIKAYA
.Net Programming
Published in
4 min readJun 10, 2021

Hi, I want to say something about output formatter because if someone doesn't know the term of this maybe can find it by searching words.

If you want to download an excel from your backend. You can implement an excel file and you can send it to your front end.

We have another to do that, Your backend works like what kind of request type accepted, responding in the same way.

If you requested application/json it responds the same, If you request application/xlsx it will respond the same way.

I will share all class with libraries, For help to everyone

using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using MazakaCore.Core.ViewModels;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Net.Http.Headers;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace MazakaCore.Web.Framework.OutputFormatters
{
public class ExcelOutputFormatter : OutputFormatter //this one comes as default on .net
{
public ExcelOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/xlsx")); // this is your return type name
}
public bool CanWriteType(OutputFormatterCanWriteContext context)
{
return typeof(IEnumerable<object>).IsAssignableFrom(context.ObjectType);
}
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.ObjectType.Name.Contains("FilteredListDataViewModel"))
{
//I cant pass directly to the excel because in my case I created a ResponseHandler from ActionResult object. Herewith I manage easly returned data and errors
//dynamic usage is not commended but I found only option this.
//after that filterlist full with Data<T>,bool error,String ErrorMessage I have to use only data, because I dont want to others in excel
dynamic filteredList = context.Object;
var excelStream = CreateExcelFile(filteredList.Data as IEnumerable<object>);
var response = context.HttpContext.Response;
response.ContentLength = excelStream.Length;
return response.Body.WriteAsync(excelStream.ToArray()).AsTask();
}
else
{
return Task.CompletedTask;
}
}
public override void WriteResponseHeaders(OutputFormatterWriteContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var linkQuery = context.HttpContext.Request.Path.ToString().Split("/");var fileName = linkQuery[^1].Replace(@"""", "\"") + " - " + DateTime.Now.ToShortDateString();context.HttpContext.Response.Headers["Content-Disposition"] =new ContentDispositionHeaderValue("attachment")
{
FileName = fileName + ".xlsx"
}.ToString();
context.HttpContext.Response.ContentType = "application/xlsx";
}
private MemoryStream CreateExcelFile(IEnumerable<object> data)
{
var ms = new MemoryStream();
using (SpreadsheetDocument spreedDoc = SpreadsheetDocument.Create(ms, SpreadsheetDocumentType.Workbook))
{
//openxml stuff
var wbPart = spreedDoc.AddWorkbookPart();
wbPart.Workbook = new Workbook();
var worksheetPart = wbPart.AddNewPart<WorksheetPart>();
var sheetData = new SheetData();
worksheetPart.Worksheet = new Worksheet(sheetData);
wbPart.Workbook.AppendChild<Sheets>(new Sheets());
var sheet = new Sheet()
{
Id = wbPart.GetIdOfPart(worksheetPart),
SheetId = 1,
Name = "Sheet1"
};
var workingSheet = ((WorksheetPart)wbPart.GetPartById(sheet.Id)).Worksheet;
//get model properties
var props = new List<PropertyInfo>(data.First().GetType().GetProperties());
//header
var headerRow = new Row();
foreach (var prop in props)
{
headerRow.AppendChild(
GetCell(prop.Name)
);
}
sheetData.AppendChild(headerRow);
//body
foreach (var record in data)
{
var row = new Row();
foreach (var prop in props)
{
var propValue = prop.GetValue(record, null)?.ToString()??"";
row.AppendChild(
GetCell(propValue)
);
}
sheetData.AppendChild(row);
}
wbPart.Workbook.Sheets.AppendChild(sheet);
wbPart.Workbook.Save();
}
return ms;
}
private Cell GetCell(string text)
{
var cell = new Cell()
{
DataType = CellValues.InlineString
};
var inlineString = new InlineString();
inlineString.AppendChild(new Text(text));
cell.AppendChild(inlineString);
return cell;
}
}
}

this block is enough for creating a xlsx return type.

There is one more thing, You should add your return type to your configurations

services.AddMvc(options =>
{
options.OutputFormatters.Add(new ExcelOutputFormatter());
options.RespectBrowserAcceptHeader = true; // false by default
});

I researched the respect part :) for 3 hours:D If you don't add that line, you project work with always application/xlsx.

It means except xlsx requests will not work on your backend :)

Second Part(I decided to share on front end part because it took 5 hours from me, just a piece of code which I don't know)

The main problem on the frontend I tried my endpoint on swagger and postman I downloaded successfully from both of them, but on my front end this excel file was always broken, the problem I have to array buffer on HTTP request otherwise it will claim as a byte array.

I used the natural download method because I don't want to add libraries.

1- my first fail was in the header because every source I read, says content-type:”application/xlsx” but it will not work, it means this is your content type. We have to change accept type I found it very deeply resaerch.

2-second fail is responseType: “arraybuffer” just add this to your request, it will protect your byte array you can see the difference on chrome with this and without this, by the way, it will not work properly without this.

with arraybuffer
without arraybuffer
function DownloadReport(reportName) {
$http({
method: 'GET', url: DownloadReportEndPoint + reportName,
headers: { Accept : "application/xlsx" }, responseType: "arraybuffer"
}).then(function (result) {
let headers = result.headers();
let blob = new Blob([result.data], { type: headers['content-type'] });
let windowUrl = (window.URL || window.webkitURL);
let downloadUrl = windowUrl.createObjectURL(blob);
let anchor = document.createElement("a");
anchor.href = downloadUrl;
anchor.download = headers['content-disposition'].split("filename=")[1].split('.xlsx')[0] + '.xlsx';
document.body.appendChild(anchor);
anchor.click();
windowUrl.revokeObjectURL(blob);
})
}

--

--