I want to optimize response returning from ASP.NET Core application. In order to do that I'm serializing the object to JSON UTF-8 bytes and inserting it to SQL as varbinary(max). I initially though that I'll get a stream from the column value using ADO.NET and just return it from Controller. This worked perfectly fine at the beginning as I've simply returned the Stream from the controller and it worked:
public async Task<Stream> GetSingleObject()
{
Response.ContentType = "application/json; charset=UTF-8";
var streamWithUtf8JsonBytesFromDb = await _repository.GetSingleObject();
return streamWithUtf8JsonBytesFromDb;
}
Unfortunately I have to attach additional properties to the response object:
{
"metadata": null,
"data": (here I would like to copy bytes from SQL stream directly to the response without any transformation)
"links": [
"self": "some url"
]
}
How can I use System.Text.Json to achieve this result in performant and usable fashion?
I've came up with two approaches so far, but I think there has to be a better way:
- First solution:
//using Microsoft.AspNetCore.Http.Extensions;
//using Microsoft.AspNetCore.Http;
public async Task GetSingleObject()
{
Response.ContentType = "application/json; charset=UTF-8";
await Response.WriteAsync("{\"meta\":null,\"data\":", System.Text.Encoding.UTF8)
var streamWithUtf8JsonBytesFromDb = await _repository.GetSingleObject();
await streamWithUtf8JsonBytesFromDb.CopyToAsync(Response.Body)
await Response.WriteAsync($",\"links\":[{{\"self\":{Request.GetEncodedUrl()}}}]}}", System.Text.Encoding.UTF8);
}
Constructing JSON parts from string and concatenating it in the stream is obviously painful
- Second solution:
public class Meta
{ /* Not relevant */ }
public class ObjectResponse
{
public Meta? Meta { get; set; }
public JsonDocument Data { get; set; }
public Dictionary<string, string> Links { get; set; }
}
//using Microsoft.AspNetCore.Http.Extensions;
//using Microsoft.AspNetCore.Http;
public async Task<ObjectResponse> GetSingleObject()
{
var streamWithUtf8JsonBytesFromDb = await _repository.GetSingleObject();
return new ObjectResponse {
Meta = null,
Data = await JsonDocument.ParseAsync(streamWithUtf8JsonBytesFromDb),
Links = new Dictionary<string, string> {
{ "self", Request.GetEncodedUrl()}
};
}
}
This is better than the first approach, but it adds unnecessary parsing of the stream which I'd like to omit.
What I'd think is the best idea is to use the following:
public class Meta
{ /* Not relevant */ }
public class ObjectResponse
{
public Meta? Meta { get; set; }
public Stream Data { get; set; }
public Dictionary<string, string> Links { get; set; }
}
//using Microsoft.AspNetCore.Http.Extensions;
//using Microsoft.AspNetCore.Http;
public async Task<ObjectResponse> GetSingleObject()
{
return new ObjectResponse {
Meta = null,
Data = await _repository.GetSingleObject(),
Links = new Dictionary<string, string> {
{ "self", Request.GetEncodedUrl()}
};
}
}
But it doesn't work, because System.Text.Json tries to serialize this stream instead of copying it directly to the response body (which makes sense of course). Am I missing something? Is there a custom converter I could use to achieve the result I'd like?