Streaming modified lines of a file from a Controller











up vote
3
down vote

favorite












I am writing a piece of software that aims at streaming the modified lines of an external file located at given uri (and retrieved as a QueryString parameter).



I am trying to minimize the impact of big files so that I can maximize the use of asynchronous calls (i.e. async / await). I am using AsyncEnumerable (https://github.com/Dasync/AsyncEnumerable) to have at the same time the feature of enumerable and and async / await, in order to free up threads and memory (I am not going to keep the whole file in memory before sending the modified version back).



I created an extension method that can fetch the lines of a remote file.



public static class StringExtensions
{
public static AsyncEnumerable<string> ReadLinesAsyncViaHttpClient(this string uri)
{
return new AsyncEnumerable<string>(async yield =>
{
using (var httpClient = new HttpClient())
{
using (var responseStream = await httpClient.GetStreamAsync(uri))
{
using (var streamReader = new StreamReader(responseStream))
{
while(true)
{
var line = await streamReader.ReadLineAsync();

if (line != null)
{
await yield.ReturnAsync(line);
}
else
{
return;
}
}
}
}
}
});
}
}


And used that extension method in a Controller action:



using System;
using System.Collections.Async;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace WebApplicationTest.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class LinesController : ControllerBase
{
private static readonly Random Random = new Random();

// GET api/lines
// POST api/lines
[HttpGet]
[HttpPost]
public async Task<IActionResult> GetAsync([FromQuery] string fileUri)
{
using (var streamWriter = new StreamWriter(HttpContext.Response.Body))
{
await fileUri.ReadLinesAsyncViaHttpClient().ForEachAsync(async line =>
{
// Some dumb process on each (maybe big line)
line += Random.Next(0, 100 + 1);
await streamWriter.WriteLineAsync(line);
});
}

return Ok();
}
}
}


I used the HttpContext.Response.Body stream instead of the PushStreamContent cause according to this answer: https://stackoverflow.com/a/48115434/4636721 (in .NET Core we don't need to use the PushStreamContent if no need of HttpMessage)



It seems to work I mean, I get a payload with the lines of the initial file passed in the query string and with some random numbers at the end of each line. It seems there is no increase in memory but I would like to have another pair of eyes to check what I have done.



[EDIT 2]
Turns out there is already an issue on the ASP.NET MVC Github about that: https://github.com/aspnet/Mvc/issues/6224










share|improve this question




















  • 4




    yield - this is a really, really, really! terrible variable name. I had to look ten times at your code to actually understand that it's not an enumerator :-o
    – t3chb0t
    Nov 5 at 19:24










  • @t3chb0t: this is due AsyncEnumerable github.com/Dasync/AsyncEnumerable it's a workaround for the actual keyword which cannot be used pre C# 8 (if Async Streams are accepted someday): github.com/Dasync/…
    – Ehouarn Perret
    Nov 5 at 19:33












  • Thanks for the links. This is an interesting idea but I'm still sceptical about their word choice. It somehow doesn't fit well.
    – t3chb0t
    Nov 5 at 19:57










  • @t3chb0t hm may worth replace it with "yielder".
    – Ehouarn Perret
    Nov 5 at 20:01










  • @t3chb0t, that's the point of the library - to make asynchronous enumeration as easy as the built-in synchronous counterpart. You don't need to spend a lot of time learning how use it due to high similarity in syntax. The Author.
    – Serge Semenov
    Nov 27 at 18:54















up vote
3
down vote

favorite












I am writing a piece of software that aims at streaming the modified lines of an external file located at given uri (and retrieved as a QueryString parameter).



I am trying to minimize the impact of big files so that I can maximize the use of asynchronous calls (i.e. async / await). I am using AsyncEnumerable (https://github.com/Dasync/AsyncEnumerable) to have at the same time the feature of enumerable and and async / await, in order to free up threads and memory (I am not going to keep the whole file in memory before sending the modified version back).



I created an extension method that can fetch the lines of a remote file.



public static class StringExtensions
{
public static AsyncEnumerable<string> ReadLinesAsyncViaHttpClient(this string uri)
{
return new AsyncEnumerable<string>(async yield =>
{
using (var httpClient = new HttpClient())
{
using (var responseStream = await httpClient.GetStreamAsync(uri))
{
using (var streamReader = new StreamReader(responseStream))
{
while(true)
{
var line = await streamReader.ReadLineAsync();

if (line != null)
{
await yield.ReturnAsync(line);
}
else
{
return;
}
}
}
}
}
});
}
}


And used that extension method in a Controller action:



using System;
using System.Collections.Async;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace WebApplicationTest.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class LinesController : ControllerBase
{
private static readonly Random Random = new Random();

// GET api/lines
// POST api/lines
[HttpGet]
[HttpPost]
public async Task<IActionResult> GetAsync([FromQuery] string fileUri)
{
using (var streamWriter = new StreamWriter(HttpContext.Response.Body))
{
await fileUri.ReadLinesAsyncViaHttpClient().ForEachAsync(async line =>
{
// Some dumb process on each (maybe big line)
line += Random.Next(0, 100 + 1);
await streamWriter.WriteLineAsync(line);
});
}

return Ok();
}
}
}


I used the HttpContext.Response.Body stream instead of the PushStreamContent cause according to this answer: https://stackoverflow.com/a/48115434/4636721 (in .NET Core we don't need to use the PushStreamContent if no need of HttpMessage)



It seems to work I mean, I get a payload with the lines of the initial file passed in the query string and with some random numbers at the end of each line. It seems there is no increase in memory but I would like to have another pair of eyes to check what I have done.



[EDIT 2]
Turns out there is already an issue on the ASP.NET MVC Github about that: https://github.com/aspnet/Mvc/issues/6224










share|improve this question




















  • 4




    yield - this is a really, really, really! terrible variable name. I had to look ten times at your code to actually understand that it's not an enumerator :-o
    – t3chb0t
    Nov 5 at 19:24










  • @t3chb0t: this is due AsyncEnumerable github.com/Dasync/AsyncEnumerable it's a workaround for the actual keyword which cannot be used pre C# 8 (if Async Streams are accepted someday): github.com/Dasync/…
    – Ehouarn Perret
    Nov 5 at 19:33












  • Thanks for the links. This is an interesting idea but I'm still sceptical about their word choice. It somehow doesn't fit well.
    – t3chb0t
    Nov 5 at 19:57










  • @t3chb0t hm may worth replace it with "yielder".
    – Ehouarn Perret
    Nov 5 at 20:01










  • @t3chb0t, that's the point of the library - to make asynchronous enumeration as easy as the built-in synchronous counterpart. You don't need to spend a lot of time learning how use it due to high similarity in syntax. The Author.
    – Serge Semenov
    Nov 27 at 18:54













up vote
3
down vote

favorite









up vote
3
down vote

favorite











I am writing a piece of software that aims at streaming the modified lines of an external file located at given uri (and retrieved as a QueryString parameter).



I am trying to minimize the impact of big files so that I can maximize the use of asynchronous calls (i.e. async / await). I am using AsyncEnumerable (https://github.com/Dasync/AsyncEnumerable) to have at the same time the feature of enumerable and and async / await, in order to free up threads and memory (I am not going to keep the whole file in memory before sending the modified version back).



I created an extension method that can fetch the lines of a remote file.



public static class StringExtensions
{
public static AsyncEnumerable<string> ReadLinesAsyncViaHttpClient(this string uri)
{
return new AsyncEnumerable<string>(async yield =>
{
using (var httpClient = new HttpClient())
{
using (var responseStream = await httpClient.GetStreamAsync(uri))
{
using (var streamReader = new StreamReader(responseStream))
{
while(true)
{
var line = await streamReader.ReadLineAsync();

if (line != null)
{
await yield.ReturnAsync(line);
}
else
{
return;
}
}
}
}
}
});
}
}


And used that extension method in a Controller action:



using System;
using System.Collections.Async;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace WebApplicationTest.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class LinesController : ControllerBase
{
private static readonly Random Random = new Random();

// GET api/lines
// POST api/lines
[HttpGet]
[HttpPost]
public async Task<IActionResult> GetAsync([FromQuery] string fileUri)
{
using (var streamWriter = new StreamWriter(HttpContext.Response.Body))
{
await fileUri.ReadLinesAsyncViaHttpClient().ForEachAsync(async line =>
{
// Some dumb process on each (maybe big line)
line += Random.Next(0, 100 + 1);
await streamWriter.WriteLineAsync(line);
});
}

return Ok();
}
}
}


I used the HttpContext.Response.Body stream instead of the PushStreamContent cause according to this answer: https://stackoverflow.com/a/48115434/4636721 (in .NET Core we don't need to use the PushStreamContent if no need of HttpMessage)



It seems to work I mean, I get a payload with the lines of the initial file passed in the query string and with some random numbers at the end of each line. It seems there is no increase in memory but I would like to have another pair of eyes to check what I have done.



[EDIT 2]
Turns out there is already an issue on the ASP.NET MVC Github about that: https://github.com/aspnet/Mvc/issues/6224










share|improve this question















I am writing a piece of software that aims at streaming the modified lines of an external file located at given uri (and retrieved as a QueryString parameter).



I am trying to minimize the impact of big files so that I can maximize the use of asynchronous calls (i.e. async / await). I am using AsyncEnumerable (https://github.com/Dasync/AsyncEnumerable) to have at the same time the feature of enumerable and and async / await, in order to free up threads and memory (I am not going to keep the whole file in memory before sending the modified version back).



I created an extension method that can fetch the lines of a remote file.



public static class StringExtensions
{
public static AsyncEnumerable<string> ReadLinesAsyncViaHttpClient(this string uri)
{
return new AsyncEnumerable<string>(async yield =>
{
using (var httpClient = new HttpClient())
{
using (var responseStream = await httpClient.GetStreamAsync(uri))
{
using (var streamReader = new StreamReader(responseStream))
{
while(true)
{
var line = await streamReader.ReadLineAsync();

if (line != null)
{
await yield.ReturnAsync(line);
}
else
{
return;
}
}
}
}
}
});
}
}


And used that extension method in a Controller action:



using System;
using System.Collections.Async;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace WebApplicationTest.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class LinesController : ControllerBase
{
private static readonly Random Random = new Random();

// GET api/lines
// POST api/lines
[HttpGet]
[HttpPost]
public async Task<IActionResult> GetAsync([FromQuery] string fileUri)
{
using (var streamWriter = new StreamWriter(HttpContext.Response.Body))
{
await fileUri.ReadLinesAsyncViaHttpClient().ForEachAsync(async line =>
{
// Some dumb process on each (maybe big line)
line += Random.Next(0, 100 + 1);
await streamWriter.WriteLineAsync(line);
});
}

return Ok();
}
}
}


I used the HttpContext.Response.Body stream instead of the PushStreamContent cause according to this answer: https://stackoverflow.com/a/48115434/4636721 (in .NET Core we don't need to use the PushStreamContent if no need of HttpMessage)



It seems to work I mean, I get a payload with the lines of the initial file passed in the query string and with some random numbers at the end of each line. It seems there is no increase in memory but I would like to have another pair of eyes to check what I have done.



[EDIT 2]
Turns out there is already an issue on the ASP.NET MVC Github about that: https://github.com/aspnet/Mvc/issues/6224







c# beginner stream async-await asp.net-core-webapi






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 11 at 17:50

























asked Nov 5 at 19:08









Ehouarn Perret

20218




20218








  • 4




    yield - this is a really, really, really! terrible variable name. I had to look ten times at your code to actually understand that it's not an enumerator :-o
    – t3chb0t
    Nov 5 at 19:24










  • @t3chb0t: this is due AsyncEnumerable github.com/Dasync/AsyncEnumerable it's a workaround for the actual keyword which cannot be used pre C# 8 (if Async Streams are accepted someday): github.com/Dasync/…
    – Ehouarn Perret
    Nov 5 at 19:33












  • Thanks for the links. This is an interesting idea but I'm still sceptical about their word choice. It somehow doesn't fit well.
    – t3chb0t
    Nov 5 at 19:57










  • @t3chb0t hm may worth replace it with "yielder".
    – Ehouarn Perret
    Nov 5 at 20:01










  • @t3chb0t, that's the point of the library - to make asynchronous enumeration as easy as the built-in synchronous counterpart. You don't need to spend a lot of time learning how use it due to high similarity in syntax. The Author.
    – Serge Semenov
    Nov 27 at 18:54














  • 4




    yield - this is a really, really, really! terrible variable name. I had to look ten times at your code to actually understand that it's not an enumerator :-o
    – t3chb0t
    Nov 5 at 19:24










  • @t3chb0t: this is due AsyncEnumerable github.com/Dasync/AsyncEnumerable it's a workaround for the actual keyword which cannot be used pre C# 8 (if Async Streams are accepted someday): github.com/Dasync/…
    – Ehouarn Perret
    Nov 5 at 19:33












  • Thanks for the links. This is an interesting idea but I'm still sceptical about their word choice. It somehow doesn't fit well.
    – t3chb0t
    Nov 5 at 19:57










  • @t3chb0t hm may worth replace it with "yielder".
    – Ehouarn Perret
    Nov 5 at 20:01










  • @t3chb0t, that's the point of the library - to make asynchronous enumeration as easy as the built-in synchronous counterpart. You don't need to spend a lot of time learning how use it due to high similarity in syntax. The Author.
    – Serge Semenov
    Nov 27 at 18:54








4




4




yield - this is a really, really, really! terrible variable name. I had to look ten times at your code to actually understand that it's not an enumerator :-o
– t3chb0t
Nov 5 at 19:24




yield - this is a really, really, really! terrible variable name. I had to look ten times at your code to actually understand that it's not an enumerator :-o
– t3chb0t
Nov 5 at 19:24












@t3chb0t: this is due AsyncEnumerable github.com/Dasync/AsyncEnumerable it's a workaround for the actual keyword which cannot be used pre C# 8 (if Async Streams are accepted someday): github.com/Dasync/…
– Ehouarn Perret
Nov 5 at 19:33






@t3chb0t: this is due AsyncEnumerable github.com/Dasync/AsyncEnumerable it's a workaround for the actual keyword which cannot be used pre C# 8 (if Async Streams are accepted someday): github.com/Dasync/…
– Ehouarn Perret
Nov 5 at 19:33














Thanks for the links. This is an interesting idea but I'm still sceptical about their word choice. It somehow doesn't fit well.
– t3chb0t
Nov 5 at 19:57




Thanks for the links. This is an interesting idea but I'm still sceptical about their word choice. It somehow doesn't fit well.
– t3chb0t
Nov 5 at 19:57












@t3chb0t hm may worth replace it with "yielder".
– Ehouarn Perret
Nov 5 at 20:01




@t3chb0t hm may worth replace it with "yielder".
– Ehouarn Perret
Nov 5 at 20:01












@t3chb0t, that's the point of the library - to make asynchronous enumeration as easy as the built-in synchronous counterpart. You don't need to spend a lot of time learning how use it due to high similarity in syntax. The Author.
– Serge Semenov
Nov 27 at 18:54




@t3chb0t, that's the point of the library - to make asynchronous enumeration as easy as the built-in synchronous counterpart. You don't need to spend a lot of time learning how use it due to high similarity in syntax. The Author.
– Serge Semenov
Nov 27 at 18:54










2 Answers
2






active

oldest

votes

















up vote
1
down vote



accepted










As the author of the AsyncEnumerable library, I confirm the correctness of the code.



P.S. we've been using this library for 3 years at the heart of a large distributed software at my current workplace.






share|improve this answer





















  • there are two things I am concerned with: 1] is whether the controller is going to buffer the data while they are being streamed to the Response.Body and increase memory use in the ASP.NET Core WebAPI application 2] how to properly handle errors, cause once you write to the stream you cannot modify the http status code in the response... I read in some places that you can add a middleware catching specific exceptions but it seems a bit over-convoluted
    – Ehouarn Perret
    Nov 28 at 0:35






  • 1




    @EhouarnPerret, the GetStreamAsync should guarantee the streaming and not loading the entire body, as 'under the hood' it uses the HttpCompletionOption.ResponseHeadersRead option. Regarding error handling, yes, it can be tricky. Essentially it's a typical problem of cascading failures of synchronous calls. On error, you can potentially re-issue the request and skip first X lines that were already returned. There are other options, but that's a bigger software architecture problem.
    – Serge Semenov
    Nov 28 at 4:30










  • thank you man! Really appreciated, I think I will keep using my middleware trick for error handling and update my post accordingly.
    – Ehouarn Perret
    Nov 28 at 21:51


















up vote
2
down vote













A small improvement you can do to avoid the pretty deep nesting is, stacking all usings without extra brackets.



public static AsyncEnumerable<string> ReadLinesAsyncViaHttpClient(this string uri)
{
return new AsyncEnumerable<string>(async yield =>
{
using (var httpClient = new HttpClient())
using (var responseStream = await httpClient.GetStreamAsync(uri))
using (var streamReader = new StreamReader(responseStream))
{
while(true)
{
var line = await streamReader.ReadLineAsync();
if (line != null)
{
await yield.ReturnAsync(line);
}
else
{
return;
}
}
}
});
}


Apart from that I don't know if this the kind of thing that should be a extension method. This should rather be a class.



(I'm currently do not have my hands on a pc, so I cannot comment on the technical side of things >:( )






share|improve this answer





















  • Yeah I am aware of that, I wanted to keep the braces to see clearly what is nested. Curious about your thoughts on the technical side.
    – Ehouarn Perret
    Nov 6 at 19:31











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


}
});














draft saved

draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f207012%2fstreaming-modified-lines-of-a-file-from-a-controller%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown

























2 Answers
2






active

oldest

votes








2 Answers
2






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
1
down vote



accepted










As the author of the AsyncEnumerable library, I confirm the correctness of the code.



P.S. we've been using this library for 3 years at the heart of a large distributed software at my current workplace.






share|improve this answer





















  • there are two things I am concerned with: 1] is whether the controller is going to buffer the data while they are being streamed to the Response.Body and increase memory use in the ASP.NET Core WebAPI application 2] how to properly handle errors, cause once you write to the stream you cannot modify the http status code in the response... I read in some places that you can add a middleware catching specific exceptions but it seems a bit over-convoluted
    – Ehouarn Perret
    Nov 28 at 0:35






  • 1




    @EhouarnPerret, the GetStreamAsync should guarantee the streaming and not loading the entire body, as 'under the hood' it uses the HttpCompletionOption.ResponseHeadersRead option. Regarding error handling, yes, it can be tricky. Essentially it's a typical problem of cascading failures of synchronous calls. On error, you can potentially re-issue the request and skip first X lines that were already returned. There are other options, but that's a bigger software architecture problem.
    – Serge Semenov
    Nov 28 at 4:30










  • thank you man! Really appreciated, I think I will keep using my middleware trick for error handling and update my post accordingly.
    – Ehouarn Perret
    Nov 28 at 21:51















up vote
1
down vote



accepted










As the author of the AsyncEnumerable library, I confirm the correctness of the code.



P.S. we've been using this library for 3 years at the heart of a large distributed software at my current workplace.






share|improve this answer





















  • there are two things I am concerned with: 1] is whether the controller is going to buffer the data while they are being streamed to the Response.Body and increase memory use in the ASP.NET Core WebAPI application 2] how to properly handle errors, cause once you write to the stream you cannot modify the http status code in the response... I read in some places that you can add a middleware catching specific exceptions but it seems a bit over-convoluted
    – Ehouarn Perret
    Nov 28 at 0:35






  • 1




    @EhouarnPerret, the GetStreamAsync should guarantee the streaming and not loading the entire body, as 'under the hood' it uses the HttpCompletionOption.ResponseHeadersRead option. Regarding error handling, yes, it can be tricky. Essentially it's a typical problem of cascading failures of synchronous calls. On error, you can potentially re-issue the request and skip first X lines that were already returned. There are other options, but that's a bigger software architecture problem.
    – Serge Semenov
    Nov 28 at 4:30










  • thank you man! Really appreciated, I think I will keep using my middleware trick for error handling and update my post accordingly.
    – Ehouarn Perret
    Nov 28 at 21:51













up vote
1
down vote



accepted







up vote
1
down vote



accepted






As the author of the AsyncEnumerable library, I confirm the correctness of the code.



P.S. we've been using this library for 3 years at the heart of a large distributed software at my current workplace.






share|improve this answer












As the author of the AsyncEnumerable library, I confirm the correctness of the code.



P.S. we've been using this library for 3 years at the heart of a large distributed software at my current workplace.







share|improve this answer












share|improve this answer



share|improve this answer










answered Nov 27 at 19:03









Serge Semenov

1362




1362












  • there are two things I am concerned with: 1] is whether the controller is going to buffer the data while they are being streamed to the Response.Body and increase memory use in the ASP.NET Core WebAPI application 2] how to properly handle errors, cause once you write to the stream you cannot modify the http status code in the response... I read in some places that you can add a middleware catching specific exceptions but it seems a bit over-convoluted
    – Ehouarn Perret
    Nov 28 at 0:35






  • 1




    @EhouarnPerret, the GetStreamAsync should guarantee the streaming and not loading the entire body, as 'under the hood' it uses the HttpCompletionOption.ResponseHeadersRead option. Regarding error handling, yes, it can be tricky. Essentially it's a typical problem of cascading failures of synchronous calls. On error, you can potentially re-issue the request and skip first X lines that were already returned. There are other options, but that's a bigger software architecture problem.
    – Serge Semenov
    Nov 28 at 4:30










  • thank you man! Really appreciated, I think I will keep using my middleware trick for error handling and update my post accordingly.
    – Ehouarn Perret
    Nov 28 at 21:51


















  • there are two things I am concerned with: 1] is whether the controller is going to buffer the data while they are being streamed to the Response.Body and increase memory use in the ASP.NET Core WebAPI application 2] how to properly handle errors, cause once you write to the stream you cannot modify the http status code in the response... I read in some places that you can add a middleware catching specific exceptions but it seems a bit over-convoluted
    – Ehouarn Perret
    Nov 28 at 0:35






  • 1




    @EhouarnPerret, the GetStreamAsync should guarantee the streaming and not loading the entire body, as 'under the hood' it uses the HttpCompletionOption.ResponseHeadersRead option. Regarding error handling, yes, it can be tricky. Essentially it's a typical problem of cascading failures of synchronous calls. On error, you can potentially re-issue the request and skip first X lines that were already returned. There are other options, but that's a bigger software architecture problem.
    – Serge Semenov
    Nov 28 at 4:30










  • thank you man! Really appreciated, I think I will keep using my middleware trick for error handling and update my post accordingly.
    – Ehouarn Perret
    Nov 28 at 21:51
















there are two things I am concerned with: 1] is whether the controller is going to buffer the data while they are being streamed to the Response.Body and increase memory use in the ASP.NET Core WebAPI application 2] how to properly handle errors, cause once you write to the stream you cannot modify the http status code in the response... I read in some places that you can add a middleware catching specific exceptions but it seems a bit over-convoluted
– Ehouarn Perret
Nov 28 at 0:35




there are two things I am concerned with: 1] is whether the controller is going to buffer the data while they are being streamed to the Response.Body and increase memory use in the ASP.NET Core WebAPI application 2] how to properly handle errors, cause once you write to the stream you cannot modify the http status code in the response... I read in some places that you can add a middleware catching specific exceptions but it seems a bit over-convoluted
– Ehouarn Perret
Nov 28 at 0:35




1




1




@EhouarnPerret, the GetStreamAsync should guarantee the streaming and not loading the entire body, as 'under the hood' it uses the HttpCompletionOption.ResponseHeadersRead option. Regarding error handling, yes, it can be tricky. Essentially it's a typical problem of cascading failures of synchronous calls. On error, you can potentially re-issue the request and skip first X lines that were already returned. There are other options, but that's a bigger software architecture problem.
– Serge Semenov
Nov 28 at 4:30




@EhouarnPerret, the GetStreamAsync should guarantee the streaming and not loading the entire body, as 'under the hood' it uses the HttpCompletionOption.ResponseHeadersRead option. Regarding error handling, yes, it can be tricky. Essentially it's a typical problem of cascading failures of synchronous calls. On error, you can potentially re-issue the request and skip first X lines that were already returned. There are other options, but that's a bigger software architecture problem.
– Serge Semenov
Nov 28 at 4:30












thank you man! Really appreciated, I think I will keep using my middleware trick for error handling and update my post accordingly.
– Ehouarn Perret
Nov 28 at 21:51




thank you man! Really appreciated, I think I will keep using my middleware trick for error handling and update my post accordingly.
– Ehouarn Perret
Nov 28 at 21:51












up vote
2
down vote













A small improvement you can do to avoid the pretty deep nesting is, stacking all usings without extra brackets.



public static AsyncEnumerable<string> ReadLinesAsyncViaHttpClient(this string uri)
{
return new AsyncEnumerable<string>(async yield =>
{
using (var httpClient = new HttpClient())
using (var responseStream = await httpClient.GetStreamAsync(uri))
using (var streamReader = new StreamReader(responseStream))
{
while(true)
{
var line = await streamReader.ReadLineAsync();
if (line != null)
{
await yield.ReturnAsync(line);
}
else
{
return;
}
}
}
});
}


Apart from that I don't know if this the kind of thing that should be a extension method. This should rather be a class.



(I'm currently do not have my hands on a pc, so I cannot comment on the technical side of things >:( )






share|improve this answer





















  • Yeah I am aware of that, I wanted to keep the braces to see clearly what is nested. Curious about your thoughts on the technical side.
    – Ehouarn Perret
    Nov 6 at 19:31















up vote
2
down vote













A small improvement you can do to avoid the pretty deep nesting is, stacking all usings without extra brackets.



public static AsyncEnumerable<string> ReadLinesAsyncViaHttpClient(this string uri)
{
return new AsyncEnumerable<string>(async yield =>
{
using (var httpClient = new HttpClient())
using (var responseStream = await httpClient.GetStreamAsync(uri))
using (var streamReader = new StreamReader(responseStream))
{
while(true)
{
var line = await streamReader.ReadLineAsync();
if (line != null)
{
await yield.ReturnAsync(line);
}
else
{
return;
}
}
}
});
}


Apart from that I don't know if this the kind of thing that should be a extension method. This should rather be a class.



(I'm currently do not have my hands on a pc, so I cannot comment on the technical side of things >:( )






share|improve this answer





















  • Yeah I am aware of that, I wanted to keep the braces to see clearly what is nested. Curious about your thoughts on the technical side.
    – Ehouarn Perret
    Nov 6 at 19:31













up vote
2
down vote










up vote
2
down vote









A small improvement you can do to avoid the pretty deep nesting is, stacking all usings without extra brackets.



public static AsyncEnumerable<string> ReadLinesAsyncViaHttpClient(this string uri)
{
return new AsyncEnumerable<string>(async yield =>
{
using (var httpClient = new HttpClient())
using (var responseStream = await httpClient.GetStreamAsync(uri))
using (var streamReader = new StreamReader(responseStream))
{
while(true)
{
var line = await streamReader.ReadLineAsync();
if (line != null)
{
await yield.ReturnAsync(line);
}
else
{
return;
}
}
}
});
}


Apart from that I don't know if this the kind of thing that should be a extension method. This should rather be a class.



(I'm currently do not have my hands on a pc, so I cannot comment on the technical side of things >:( )






share|improve this answer












A small improvement you can do to avoid the pretty deep nesting is, stacking all usings without extra brackets.



public static AsyncEnumerable<string> ReadLinesAsyncViaHttpClient(this string uri)
{
return new AsyncEnumerable<string>(async yield =>
{
using (var httpClient = new HttpClient())
using (var responseStream = await httpClient.GetStreamAsync(uri))
using (var streamReader = new StreamReader(responseStream))
{
while(true)
{
var line = await streamReader.ReadLineAsync();
if (line != null)
{
await yield.ReturnAsync(line);
}
else
{
return;
}
}
}
});
}


Apart from that I don't know if this the kind of thing that should be a extension method. This should rather be a class.



(I'm currently do not have my hands on a pc, so I cannot comment on the technical side of things >:( )







share|improve this answer












share|improve this answer



share|improve this answer










answered Nov 6 at 7:44









Patrick Hollweck

18817




18817












  • Yeah I am aware of that, I wanted to keep the braces to see clearly what is nested. Curious about your thoughts on the technical side.
    – Ehouarn Perret
    Nov 6 at 19:31


















  • Yeah I am aware of that, I wanted to keep the braces to see clearly what is nested. Curious about your thoughts on the technical side.
    – Ehouarn Perret
    Nov 6 at 19:31
















Yeah I am aware of that, I wanted to keep the braces to see clearly what is nested. Curious about your thoughts on the technical side.
– Ehouarn Perret
Nov 6 at 19:31




Yeah I am aware of that, I wanted to keep the braces to see clearly what is nested. Curious about your thoughts on the technical side.
– Ehouarn Perret
Nov 6 at 19:31


















draft saved

draft discarded




















































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.




draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f207012%2fstreaming-modified-lines-of-a-file-from-a-controller%23new-answer', 'question_page');
}
);

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







Popular posts from this blog

Список кардиналов, возведённых папой римским Каликстом III

Deduzione

Mysql.sock missing - “Can't connect to local MySQL server through socket”