I have a method that take Stream parameter and pass it to server
public async Task<string> Execute(Stream archive)
{
archive.Seek(0, SeekOrigin.Begin);
using var content = new MultipartFormDataContent();
content.Add(new StreamContent(archive), "file1", "file1");
var result = "";
using (var response = await _client.PostAsync(_uri, content))
{
if (response.IsSuccessStatusCode)
{
var stringResult = await response.Content.ReadAsStringAsync();
result = stringResult;
}
}
// here archive is already disposed
return result;
}
Now I implement the retry policy of this method.
If outside code calling this method gets "" as result, then it tries to call this method againg.
But the archive is disposed to that moment.
I see that archive stream is disposed immediately after disposing of response.
Why?
What should I do if I need stream parameter outside after this method?
It's the StreamContent that will dispose the Stream, as states in it's source. And that will be disposed by the MultipartContent. And that will be disposed in PostAsync... all though the chain.
A solution is to subclass the Stream and remove the dispose method, like proposed here. but you'll have to make sure the original stream gets disposed yourself!.
Edit: update. Stream is an abstract class, so it would be easier if you know the specific stream type, e.g.
public sealed class NoDisposeMemoryStream : MemoryStream
{
protected override void Dispose(bool disposing) { }
}
Else you will need to write your own complete Stream wrapper, see bottom.
Another solution is to implement the retry mechanism in the innermost using block, resetting the archive seek origin every fail. That's likely safer.
public sealed class NoDisposeStream : Stream
{
private Stream _original;
public NoDisposeStream(Stream original) => _original = original
?? throw new ArgumentNullException(nameof(original));
public override bool CanRead => _original.CanRead;
public override bool CanSeek => _original.CanSeek;
public override bool CanWrite => _original.CanWrite;
public override long Length => _original.Length;
public override long Position { get => _original.Position; set { _original.Position = value; } }
public override void Flush() => _original.Flush();
public override int Read(byte[] buffer, int offset, int count) => _original.Read(buffer, offset, count);
public override long Seek(long offset, SeekOrigin origin) => _original.Seek(offset, origin);
public override void SetLength(long value) => _original.SetLength(value);
public override void Write(byte[] buffer, int offset, int count) => _original.Write(buffer, offset, count);
protected override void Dispose(bool disposing) { }
}
This happens because HttpClient PostAsync Disposes of Content that you pass.
https://github.com/microsoft/referencesource/blob/master/System/net/System/Net/Http/HttpClient.cs
The relevant code:
public Task<HttpResponseMessage> PostAsync(Uri requestUri, HttpContent content,
CancellationToken cancellationToken)
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, requestUri);
request.Content = content;
return SendAsync(request, cancellationToken);
}
And then SendAsync calls DisposeRequestContent which is implemented like:
private void DisposeRequestContent(HttpRequestMessage request)
{
Contract.Requires(request != null);
// When a request completes, HttpClient disposes the request content so the user doesn't have to. This also
// ensures that a HttpContent object is only sent once using HttpClient (similar to HttpRequestMessages
// that can also be sent only once).
HttpContent content = request.Content;
if (content != null)
{
content.Dispose();
}
}
The comments say why
Related
I am trying to change the JSON parser in my web API project.
I have followed the following tutorials:
https://learn.microsoft.com/en-us/aspnet/core/web-api/advanced/custom-formatters?view=aspnetcore-2.2
https://www.youtube.com/watch?v=tNzgXjqqIjI
https://weblog.west-wind.com/posts/2012/Mar/09/Using-an-alternate-JSON-Serializer-in-ASPNET-Web-API
I now have the following code:
public class MyJsonFormatter : MediaTypeFormatter
{
public MyJsonFormatter()
{
base.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
}
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken)
{
return null;
}
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
{
return null;
}
public override void SetDefaultContentHeaders(Type type, HttpContentHeaders headers, MediaTypeHeaderValue mediaType)
{
base.SetDefaultContentHeaders(type, headers, mediaType);
}
public override bool CanReadType(Type type)
{
return true;
}
public override bool CanWriteType(Type type)
{
return true;
}
public override MediaTypeFormatter GetPerRequestFormatterInstance(Type type, HttpRequestMessage request, MediaTypeHeaderValue mediaType)
{
return base.GetPerRequestFormatterInstance(type, request, mediaType);
}
public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
{
return null;
}
public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger, CancellationToken cancellationToken)
{
return null;
}
public override IRequiredMemberSelector RequiredMemberSelector { get => base.RequiredMemberSelector; set => base.RequiredMemberSelector = value; }
}
public static void Register(HttpConfiguration config)
{
///Other stuff...
GlobalConfiguration.Configuration.Formatters.Clear();
GlobalConfiguration.Configuration.Formatters.Insert(0, new MyJsonFormatter());
}
My issue is that whatever I do, JSON gets parsed and it seems to ignore my code - I can throw exceptions in the read or write methods and nothing will happen, break points do not get hit etc.
I know this formatter is being added as only the content types in my class are visible and if I set CanReadType to return false then nothing gets parsed.
My question is, how can I make the code execute my overrides?
Update how the formatter is registered
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
// Other stuff...
var jsonFormatter = new MyJsonFormatter();
config.Formatters.Clear();
config.Formatters.Insert(0, jsonFormatter);
//...
}
}
Making sure the suggested syntax is followed in Startup or where ever the application is started.
// configure Web Api
GlobalConfiguration.Configure(WebApiConfig.Register);
There is also the process of content negotiation as suggested in the following article
Supporting only JSON in ASP.NET Web API – the right way
Adapting it to your example, it would look like
public class JsonContentNegotiator : IContentNegotiator {
MediaTypeHeaderValue mediaType = MediaTypeHeaderValue.Parse("application/json;charset=utf-8");
private readonly MyJsonFormatter _jsonFormatter;
public JsonContentNegotiator(MyJsonFormatter formatter) {
_jsonFormatter = formatter;
}
public ContentNegotiationResult Negotiate(Type type, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters) {
var result = new ContentNegotiationResult(_jsonFormatter, mediaType);
return result;
}
}
And registered against your HttpConfiguration
var jsonFormatter = new MyJsonFormatter();
config.Formatters.Clear();
config.Formatters.Insert(0, jsonFormatter);
//update content negotiation
config.Services.Replace(typeof(IContentNegotiator), new JsonContentNegotiator(jsonFormatter));
Finally one piece of information to note is that the framework did tightly couple its JSON formatting to its own JsonMediaTypeFormatter
/// <summary>
/// Gets the <see cref="MediaTypeFormatter"/> to use for Json.
/// </summary>
public JsonMediaTypeFormatter JsonFormatter
{
get { return Items.OfType<JsonMediaTypeFormatter>().FirstOrDefault(); }
}
Reference source
So depending on how much of the pipeline actually depends on the existence of an instance of JsonMediaTypeFormatter, it would probably affect JSON related formatting.
If it is in fact a problem then my suggestion would be to derive from JsonMediaTypeFormatter and override its members as needed.
public class MyJsonFormatter : JsonMediaTypeFormatter {
//... removed for brevity
}
But that might bring with it, its own problems depending on what that base class is coupled to.
You need to register your formatter in the startup config.
I would like to know how many bytes were actually transmitted when using Post or PostAsync. I'm using code similar to the following. I could look at the bytes of the filePath, but in my real code, I'm doing some manipulation to the file stream between being read and sent. If you pull out the MyFilteredContent line, how would you do it?
async Task<bool> SendFile(string filePath)
{
using (HttpContent fileContent = new FileContent(filePath))
using (MyFilteredContent filteredContent = new MyFilteredContent(fileContent))
{
var t = await MyAppSettings.TargetUrl
.AllowAnyHttpStatus()
.PostAsync(filteredContent);
if (t.IsSuccessStatusCode)
{
return true;
}
throw new Exception("blah blah");
}
}
Here's a code sample of what I described in the comment - using DelegatingHandler, overriding SendAsync to get the bytes of the request being sent and then configuring FlurlHttp settings to use the handler:
public class HttpFactory : DefaultHttpClientFactory
{
public override HttpMessageHandler CreateMessageHandler()
{
return new CustomMessageHandler();
}
}
public class CustomMessageHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var content = await request.Content.ReadAsByteArrayAsync();
return await base.SendAsync(request, cancellationToken);
}
}
FlurlHttp.Configure(settings =>
{
settings.HttpClientFactory = new HttpFactory();
});
How do you read the content of an HttpResponse object in C# / ASP.net?
I need to be able to read the body content as a JSON object, modify it, and then write it back to the response output stream. I want to make sure I don't lose what's already in the stream, thus I need to read from it first.
How do I do this?
You can use a Delegating handler
public class ContentHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken).ContinueWith<HttpResponseMessage>((responseToCompleteTask) =>
{
HttpResponseMessage response = responseToCompleteTask.Result;
var YourContent = response.Content.ReadAsStreamAsync().Result;
response.Content = new CompressedContent(response.Content, acceptedEncoding);
return response;
},
TaskContinuationOptions.OnlyOnRanToCompletion);
}
}
Register your handler at WebApiConfig
GlobalConfiguration.Configuration.MessageHandlers.Add(new ContentHandler());
You can edit your reponse content by extending the HttpContent Class.
For example to compress content
public class CompressedContent : HttpContent
{
private HttpContent originalContent;
protected override bool TryComputeLength(out long length)
{
length = -1;
return false;
}
protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
Stream editedStream = null;
if (encodingType == "gzip")
{
editedStream = new GZipStream(stream, CompressionMode.Compress, leaveOpen: true);
}
else if (encodingType == "deflate")
{
editedStream = new DeflateStream(stream, CompressionMode.Compress, leaveOpen: true);
}
return originalContent.CopyToAsync(editedStream).ContinueWith(tsk =>
{
if (editedStream != null)
{
editedStream.Dispose();
}
});
}
}
This question has been asked before in a few forms but I cannot get any of the answers to work, I'm losing my hair and unsure if the problem is just that the solutions were from 2 years ago and things have changed.
How can I safely intercept the Response stream in a custom Owin Middleware - I based my code on this, it looks like it should work, but it doesn't
OWIN OnSendingHeaders Callback - Reading Response Body - seems to be a different OWIN version, because method signature doesn't work
What I want to do is write an OMC that can inspect the response stream from MVC.
What I did (amongst several other attempts), is to add an OMC that sets context.Response.Body to a MemoryStream, so I can rewind it and inspect what was written by downstream components:
public async Task Invoke(IDictionary<string, object> env)
{
IOwinContext context = new OwinContext(env);
// Buffer the response
var stream = context.Response.Body;
var buffer = new MemoryStream();
context.Response.Body = buffer;
.......
What I find is that the MemoryStream is always empty, unless I write to it from another OMC. So it seems that downstream OMCs are using my MemoryStream, but MVC responses are not, as if the OWIN pipeline completes before the request goes to MVC, but that's not right is it?
Complete code:
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
app.Use(new ResponseExaminerMiddleware());
// Specify the stage for the OMC
//app.UseStageMarker(PipelineStage.Authenticate);
}
}
public class ResponseExaminerMiddleware
{
private AppFunc next;
public void Initialize(AppFunc next)
{
this.next = next;
}
public async Task Invoke(IDictionary<string, object> env)
{
IOwinContext context = new OwinContext(env);
// Buffer the response
var stream = context.Response.Body;
var buffer = new MemoryStream();
context.Response.Body = buffer;
await this.next(env);
buffer.Seek(0, SeekOrigin.Begin);
var reader = new StreamReader(buffer);
string responseBody = await reader.ReadToEndAsync();
// Now, you can access response body.
System.Diagnostics.Debug.WriteLine(responseBody);
// You need to do this so that the response we buffered
// is flushed out to the client application.
buffer.Seek(0, SeekOrigin.Begin);
await buffer.CopyToAsync(stream);
}
}
For what it's worth I also tried a suggestion where the response.Body stream is set to a Stream subclass, just so I could monitor what is written to the stream and bizarrely the Stream.Write method is called, but with an empty byte array, never any actual content...
MVC does not pass its request through OWIN pipeline. To capture MVC response we need to make custom response filter that captures response data
/// <summary>
/// Stream capturing the data going to another stream
/// </summary>
internal class OutputCaptureStream : Stream
{
private Stream InnerStream;
public MemoryStream CapturedData { get; private set; }
public OutputCaptureStream(Stream inner)
{
InnerStream = inner;
CapturedData = new MemoryStream();
}
public override bool CanRead
{
get { return InnerStream.CanRead; }
}
public override bool CanSeek
{
get { return InnerStream.CanSeek; }
}
public override bool CanWrite
{
get { return InnerStream.CanWrite; }
}
public override void Flush()
{
InnerStream.Flush();
}
public override long Length
{
get { return InnerStream.Length; }
}
public override long Position
{
get { return InnerStream.Position; }
set { CapturedData.Position = InnerStream.Position = value; }
}
public override int Read(byte[] buffer, int offset, int count)
{
return InnerStream.Read(buffer, offset, count);
}
public override long Seek(long offset, SeekOrigin origin)
{
CapturedData.Seek(offset, origin);
return InnerStream.Seek(offset, origin);
}
public override void SetLength(long value)
{
CapturedData.SetLength(value);
InnerStream.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count)
{
CapturedData.Write(buffer, offset, count);
InnerStream.Write(buffer, offset, count);
}
}
And then we make a logging middleware that can log both kinds of responses properly
public class LoggerMiddleware : OwinMiddleware
{
public LoggerMiddleware(OwinMiddleware next): base(next)
{
}
public async override Task Invoke(IOwinContext context)
{
//to intercept MVC responses, because they don't go through OWIN
HttpResponse httpResponse = HttpContext.Current.Response;
OutputCaptureStream outputCapture = new OutputCaptureStream(httpResponse.Filter);
httpResponse.Filter = outputCapture;
IOwinResponse owinResponse = context.Response;
//buffer the response stream in order to intercept downstream writes
Stream owinResponseStream = owinResponse.Body;
owinResponse.Body = new MemoryStream();
await Next.Invoke(context);
if (outputCapture.CapturedData.Length == 0) {
//response is formed by OWIN
//make sure the response we buffered is flushed to the client
owinResponse.Body.Position = 0;
await owinResponse.Body.CopyToAsync(owinResponseStream);
} else {
//response by MVC
//write captured data to response body as if it was written by OWIN
outputCapture.CapturedData.Position = 0;
outputCapture.CapturedData.CopyTo(owinResponse.Body);
}
LogResponse(owinResponse);
}
}
Is it possible to return { } instead of null when webApi returns a null object?
This, to prevent my user from getting errors while parsing the response. And to make the response a valid Json Response?
I know that i could be setting it everywhere manually. That when null is the response, an empty Json object should be returned. But, is there a way to do it automaticly for every response?
If you are building a RESTful service, and have nothing to return from the resource, I believe that it would be more correct to return 404 (Not Found) than a 200 (OK) response with an empty body.
You can use a HttpMessageHandler to perform behaviour on all requests. The example below is one way to do it. Be warned though, I whipped this up very quickly and it probably has a bunch of edge case bugs, but it should give you the idea of how it can be done.
public class NullJsonHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
if (response.Content == null)
{
response.Content = new StringContent("{}");
} else if (response.Content is ObjectContent)
{
var objectContent = (ObjectContent) response.Content;
if (objectContent.Value == null)
{
response.Content = new StringContent("{}");
}
}
return response;
}
}
You can enable this handler by doing,
config.MessageHandlers.Add(new NullJsonHandler());
Thanks to Darrel Miller, I for now use this solution.
WebApi messes with StringContent "{}" again in some environment, so serialize through HttpContent.
/// <summary>
/// Sends HTTP content as JSON
/// </summary>
/// <remarks>Thanks to Darrel Miller</remarks>
/// <seealso cref="http://www.bizcoder.com/returning-raw-json-content-from-asp-net-web-api"/>
public class JsonContent : HttpContent
{
private readonly JToken jToken;
public JsonContent(String json) { jToken = JObject.Parse(json); }
public JsonContent(JToken value)
{
jToken = value;
Headers.ContentType = new MediaTypeHeaderValue("application/json");
}
protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
var jw = new JsonTextWriter(new StreamWriter(stream))
{
Formatting = Formatting.Indented
};
jToken.WriteTo(jw);
jw.Flush();
return Task.FromResult<object>(null);
}
protected override bool TryComputeLength(out long length)
{
length = -1;
return false;
}
}
Derived from OkResult to take advantage Ok() in ApiController
public class OkJsonPatchResult : OkResult
{
readonly MediaTypeWithQualityHeaderValue acceptJson = new MediaTypeWithQualityHeaderValue("application/json");
public OkJsonPatchResult(HttpRequestMessage request) : base(request) { }
public OkJsonPatchResult(ApiController controller) : base(controller) { }
public override Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var accept = Request.Headers.Accept;
var jsonFormat = accept.Any(h => h.Equals(acceptJson));
if (jsonFormat)
{
return Task.FromResult(ExecuteResult());
}
else
{
return base.ExecuteAsync(cancellationToken);
}
}
public HttpResponseMessage ExecuteResult()
{
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new JsonContent("{}"),
RequestMessage = Request
};
}
}
Override Ok() in ApiController
public class BaseApiController : ApiController
{
protected override OkResult Ok()
{
return new OkJsonPatchResult(this);
}
}
Maybe better solution is using Custom Message Handler.
A delegating handler can also skip the inner handler and directly
create the response.
Custom Message Handler:
public class NullJsonHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var updatedResponse = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = null
};
var response = await base.SendAsync(request, cancellationToken);
if (response.Content == null)
{
response.Content = new StringContent("{}");
}
else if (response.Content is ObjectContent)
{
var contents = await response.Content.ReadAsStringAsync();
if (contents.Contains("null"))
{
contents = contents.Replace("null", "{}");
}
updatedResponse.Content = new StringContent(contents,Encoding.UTF8,"application/json");
}
var tsc = new TaskCompletionSource<HttpResponseMessage>();
tsc.SetResult(updatedResponse);
return await tsc.Task;
}
}
Register the Handler:
In Global.asax file inside Application_Start() method register your Handler by adding below code.
GlobalConfiguration.Configuration.MessageHandlers.Add(new NullJsonHandler());
Now all the Asp.NET Web API Response which contains null will be replaced with empty Json body {}.
References:
- https://stackoverflow.com/a/22764608/2218697
- https://learn.microsoft.com/en-us/aspnet/web-api/overview/advanced/http-message-handlers