How to stream data directly from the database through a 3 tier architecture
The problem: generating a CSV file that is too large be stored in memory in a 3 tier architecture, without the complexity of saving the file to storage.
The solution: I've managed to write something that will stream directly from the database into the user's browser, through two WebApi controllers (simulating two tiers in this case)
A request is made to the front end WebApi, which makes a HttpClient call to the back end WebApi:
[HttpGet]
[Route("getFrontEndWebApi")]
public async Task<HttpResponseMessage> GetFrontEndWebApi()
{
var httpClient = new HttpClient();
var dataApiStream = await httpClient.GetStreamAsync("getBackEndWebApi");
var response = Request.CreateResponse();
response.Content = new PushStreamContent(async (stream, content, context) =>
{
await dataApiStream.CopyToAsync(stream);
dataApiStream.Close();
stream.Close();
});
response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv");
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = $"CrossingTheStreams.csv"
};
return response;
}
The back end WebApi compiles the CSV by iterating over an IEnumerable and writes it out to a stream:
[HttpGet]
[Route("getBackEndWebApi")]
public async Task<HttpResponseMessage> GetBackEndWebApi()
{
var results = ReadFromTable();
var response = Request.CreateResponse();
response.Content = new PushStreamContent(async (stream, content, context) =>
{
using (var writer = new StreamWriter(stream))
{
foreach (var result in results)
{
await writer.WriteLineAsync($"{result.Id},{result.SomeBullshit}");
}
}
});
return response;
}
Some junk code simulates a forward only DataReader connection to a database etc.
private IEnumerable<FishyFish> ReadFromTable()
{
var table = new DataTable();
table.Columns.Add("Id", typeof(int));
table.Columns.Add("SomethingElse", typeof(string));
for (int i = 0; i < 1000000; i++)
{
table.Rows.Add(new object { i, Guid.NewGuid().ToString() });
}
using (var reader = table.CreateDataReader())
{
do
{
if (reader.HasRows)
{
while (reader.Read())
{
yield return new FishyFish()
{
Id = reader.GetInt32(0),
SomeValue = reader.GetString(1)
};
}
}
} while (reader.NextResult());
}
}
public class FishyFish
{
public int Id { get; set; }
public string SomeValue { get; set; }
}
When you hit a breakpoint on yield return you can see that results have made it to the browser before the foreach has iterated over every value, thus streaming straight to the browser without compiling the file in memory.
Can this be done more sensibly? Are there any obvious flaws in this code?
c# asp.net-web-api
add a comment |
The problem: generating a CSV file that is too large be stored in memory in a 3 tier architecture, without the complexity of saving the file to storage.
The solution: I've managed to write something that will stream directly from the database into the user's browser, through two WebApi controllers (simulating two tiers in this case)
A request is made to the front end WebApi, which makes a HttpClient call to the back end WebApi:
[HttpGet]
[Route("getFrontEndWebApi")]
public async Task<HttpResponseMessage> GetFrontEndWebApi()
{
var httpClient = new HttpClient();
var dataApiStream = await httpClient.GetStreamAsync("getBackEndWebApi");
var response = Request.CreateResponse();
response.Content = new PushStreamContent(async (stream, content, context) =>
{
await dataApiStream.CopyToAsync(stream);
dataApiStream.Close();
stream.Close();
});
response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv");
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = $"CrossingTheStreams.csv"
};
return response;
}
The back end WebApi compiles the CSV by iterating over an IEnumerable and writes it out to a stream:
[HttpGet]
[Route("getBackEndWebApi")]
public async Task<HttpResponseMessage> GetBackEndWebApi()
{
var results = ReadFromTable();
var response = Request.CreateResponse();
response.Content = new PushStreamContent(async (stream, content, context) =>
{
using (var writer = new StreamWriter(stream))
{
foreach (var result in results)
{
await writer.WriteLineAsync($"{result.Id},{result.SomeBullshit}");
}
}
});
return response;
}
Some junk code simulates a forward only DataReader connection to a database etc.
private IEnumerable<FishyFish> ReadFromTable()
{
var table = new DataTable();
table.Columns.Add("Id", typeof(int));
table.Columns.Add("SomethingElse", typeof(string));
for (int i = 0; i < 1000000; i++)
{
table.Rows.Add(new object { i, Guid.NewGuid().ToString() });
}
using (var reader = table.CreateDataReader())
{
do
{
if (reader.HasRows)
{
while (reader.Read())
{
yield return new FishyFish()
{
Id = reader.GetInt32(0),
SomeValue = reader.GetString(1)
};
}
}
} while (reader.NextResult());
}
}
public class FishyFish
{
public int Id { get; set; }
public string SomeValue { get; set; }
}
When you hit a breakpoint on yield return you can see that results have made it to the browser before the foreach has iterated over every value, thus streaming straight to the browser without compiling the file in memory.
Can this be done more sensibly? Are there any obvious flaws in this code?
c# asp.net-web-api
Maybe checkout: stackoverflow.com/questions/23295119/…
– Re Captcha
Jun 6 '18 at 11:41
add a comment |
The problem: generating a CSV file that is too large be stored in memory in a 3 tier architecture, without the complexity of saving the file to storage.
The solution: I've managed to write something that will stream directly from the database into the user's browser, through two WebApi controllers (simulating two tiers in this case)
A request is made to the front end WebApi, which makes a HttpClient call to the back end WebApi:
[HttpGet]
[Route("getFrontEndWebApi")]
public async Task<HttpResponseMessage> GetFrontEndWebApi()
{
var httpClient = new HttpClient();
var dataApiStream = await httpClient.GetStreamAsync("getBackEndWebApi");
var response = Request.CreateResponse();
response.Content = new PushStreamContent(async (stream, content, context) =>
{
await dataApiStream.CopyToAsync(stream);
dataApiStream.Close();
stream.Close();
});
response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv");
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = $"CrossingTheStreams.csv"
};
return response;
}
The back end WebApi compiles the CSV by iterating over an IEnumerable and writes it out to a stream:
[HttpGet]
[Route("getBackEndWebApi")]
public async Task<HttpResponseMessage> GetBackEndWebApi()
{
var results = ReadFromTable();
var response = Request.CreateResponse();
response.Content = new PushStreamContent(async (stream, content, context) =>
{
using (var writer = new StreamWriter(stream))
{
foreach (var result in results)
{
await writer.WriteLineAsync($"{result.Id},{result.SomeBullshit}");
}
}
});
return response;
}
Some junk code simulates a forward only DataReader connection to a database etc.
private IEnumerable<FishyFish> ReadFromTable()
{
var table = new DataTable();
table.Columns.Add("Id", typeof(int));
table.Columns.Add("SomethingElse", typeof(string));
for (int i = 0; i < 1000000; i++)
{
table.Rows.Add(new object { i, Guid.NewGuid().ToString() });
}
using (var reader = table.CreateDataReader())
{
do
{
if (reader.HasRows)
{
while (reader.Read())
{
yield return new FishyFish()
{
Id = reader.GetInt32(0),
SomeValue = reader.GetString(1)
};
}
}
} while (reader.NextResult());
}
}
public class FishyFish
{
public int Id { get; set; }
public string SomeValue { get; set; }
}
When you hit a breakpoint on yield return you can see that results have made it to the browser before the foreach has iterated over every value, thus streaming straight to the browser without compiling the file in memory.
Can this be done more sensibly? Are there any obvious flaws in this code?
c# asp.net-web-api
The problem: generating a CSV file that is too large be stored in memory in a 3 tier architecture, without the complexity of saving the file to storage.
The solution: I've managed to write something that will stream directly from the database into the user's browser, through two WebApi controllers (simulating two tiers in this case)
A request is made to the front end WebApi, which makes a HttpClient call to the back end WebApi:
[HttpGet]
[Route("getFrontEndWebApi")]
public async Task<HttpResponseMessage> GetFrontEndWebApi()
{
var httpClient = new HttpClient();
var dataApiStream = await httpClient.GetStreamAsync("getBackEndWebApi");
var response = Request.CreateResponse();
response.Content = new PushStreamContent(async (stream, content, context) =>
{
await dataApiStream.CopyToAsync(stream);
dataApiStream.Close();
stream.Close();
});
response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv");
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = $"CrossingTheStreams.csv"
};
return response;
}
The back end WebApi compiles the CSV by iterating over an IEnumerable and writes it out to a stream:
[HttpGet]
[Route("getBackEndWebApi")]
public async Task<HttpResponseMessage> GetBackEndWebApi()
{
var results = ReadFromTable();
var response = Request.CreateResponse();
response.Content = new PushStreamContent(async (stream, content, context) =>
{
using (var writer = new StreamWriter(stream))
{
foreach (var result in results)
{
await writer.WriteLineAsync($"{result.Id},{result.SomeBullshit}");
}
}
});
return response;
}
Some junk code simulates a forward only DataReader connection to a database etc.
private IEnumerable<FishyFish> ReadFromTable()
{
var table = new DataTable();
table.Columns.Add("Id", typeof(int));
table.Columns.Add("SomethingElse", typeof(string));
for (int i = 0; i < 1000000; i++)
{
table.Rows.Add(new object { i, Guid.NewGuid().ToString() });
}
using (var reader = table.CreateDataReader())
{
do
{
if (reader.HasRows)
{
while (reader.Read())
{
yield return new FishyFish()
{
Id = reader.GetInt32(0),
SomeValue = reader.GetString(1)
};
}
}
} while (reader.NextResult());
}
}
public class FishyFish
{
public int Id { get; set; }
public string SomeValue { get; set; }
}
When you hit a breakpoint on yield return you can see that results have made it to the browser before the foreach has iterated over every value, thus streaming straight to the browser without compiling the file in memory.
Can this be done more sensibly? Are there any obvious flaws in this code?
c# asp.net-web-api
c# asp.net-web-api
edited Mar 8 '17 at 9:25
asked Mar 7 '17 at 17:00
James Law
210129
210129
Maybe checkout: stackoverflow.com/questions/23295119/…
– Re Captcha
Jun 6 '18 at 11:41
add a comment |
Maybe checkout: stackoverflow.com/questions/23295119/…
– Re Captcha
Jun 6 '18 at 11:41
Maybe checkout: stackoverflow.com/questions/23295119/…
– Re Captcha
Jun 6 '18 at 11:41
Maybe checkout: stackoverflow.com/questions/23295119/…
– Re Captcha
Jun 6 '18 at 11:41
add a comment |
1 Answer
1
active
oldest
votes
Backend API can send in chunk through streaming https://weblogs.asp.net/andresv/asynchronous-streaming-in-asp-net-webapi
New contributor
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f157178%2fhow-to-stream-data-directly-from-the-database-through-a-3-tier-architecture%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
Backend API can send in chunk through streaming https://weblogs.asp.net/andresv/asynchronous-streaming-in-asp-net-webapi
New contributor
add a comment |
Backend API can send in chunk through streaming https://weblogs.asp.net/andresv/asynchronous-streaming-in-asp-net-webapi
New contributor
add a comment |
Backend API can send in chunk through streaming https://weblogs.asp.net/andresv/asynchronous-streaming-in-asp-net-webapi
New contributor
Backend API can send in chunk through streaming https://weblogs.asp.net/andresv/asynchronous-streaming-in-asp-net-webapi
New contributor
New contributor
answered 2 mins ago
zubin joshi
1
1
New contributor
New contributor
add a comment |
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f157178%2fhow-to-stream-data-directly-from-the-database-through-a-3-tier-architecture%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Maybe checkout: stackoverflow.com/questions/23295119/…
– Re Captcha
Jun 6 '18 at 11:41