Inspect DefaultHttpContext body in unit test situation - c#

I'm trying to use the DefaultHttpContext object to unit test my exception handling middleware.
My test method looks like this:
[Fact]
public async Task Invoke_ProductionNonSuredException_ReturnsProductionRequestError()
{
var logger = new Mock<ILogger<ExceptionHandlerMiddleware>>();
var middleWare = new ExceptionHandlerMiddleware(next: async (innerHttpContext) =>
{
await Task.Run(() =>
{
throw new Exception();
});
}, logger: logger.Object);
var mockEnv = new Mock<IHostingEnvironment>();
mockEnv.Setup(u => u.EnvironmentName).Returns("Production");
var context = new DefaultHttpContext();
await middleWare.Invoke(context, mockEnv.Object);
var reader = new StreamReader(context.Response.Body);
var streamText = reader.ReadToEnd();
//TODO: write assert that checks streamtext is the expected production return type and not the verbose development environment version.
}
In my middleware, I am writing to the context like this:
public static Task WriteResponse(HttpContext context, HttpStatusCode statusCode, object responseData, Formatting jsonFormatting)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)statusCode;
return context.Response.WriteAsync(JsonConvert.SerializeObject(responseData, jsonFormatting));
}
To give you more insight into the middleware approach I've taken, I'm taking the approach found in this answer here.
Works fine when the app is running it's normal pipeline. However, using the DefaultHttpContext method in the test, the response body always comes back empty, and ContentLength is null. Thus, my streamText variable in the test is an empty string.
Is it possible to inspect what the middleware is writing to the context in this situation? Is this the appropriate way, or is there a better way.

Consider setting the body yourself so that you have control of the stream
Comment provided by #AndrewStanton-Nurse
The Response.Body stream in DefaultHttpContext is Stream.Null, which is a stream that ignores all reads/writes. You need to set the Stream yourself before calling the method.
Further - This will now allow the body to be set, but in order to read it correctly we must set the pointer to the beginning as per this answer, before using the StreamReader.
//...code removed for brevity
var context = new DefaultHttpContext();
context.Response.Body = new MemoryStream();
await middleWare.Invoke(context, mockEnv.Object);
context.Response.Body.Seek(0, SeekOrigin.Begin);
var reader = new StreamReader(context.Response.Body);
var streamText = reader.ReadToEnd();
//...code removed for brevity

Related

HttpClient and HttpclientHandler Dispose or not dispose, that is the question

I am looking to optimize my codeā€¦
I have number of API with early same structure for client and handler.
But I have some questions about disposing.
I have read the using statement automatically dispose resources (here HttpClient and HttpClientHandler).
Could I rewrite my code here:
public static async Task<IEnumerable<T>> deletePostsAsync<T>(IEnumerable<string> urls) where T : BaseReturnValues
{
var httpClientHandler = new HttpClientHandler
{
Proxy = new WebProxy(proxy, true),
UseProxy = IsProxySelected
};
using (var client = new HttpClient(httpClientHandler))
{
client.BaseAddress = new Uri(URI);
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add(Head.key, Head.apikey);
var tasks = urls.Select(async url => await DeleteAsync<T>(client, url).ConfigureAwait(false));
var result = await Task.WhenAll(tasks).ConfigureAwait(false);
return result!;
}
async Task<U> DeleteAsync<U>(HttpClient client, string url) where U : BaseReturnValues
{
var statusCode = -1;
var json = "_";
var isSuccess = false;
try
{
using (HttpResponseMessage response = await client.DeleteAsync(url).ConfigureAwait(false))
{
statusCode = (Int32)response.StatusCode;
json = await response.Content.ReadAsStringAsync();
isSuccess = response.IsSuccessStatusCode;
}
}
catch (Exception ex)
{
//something to catch
}
:
return record;
}
}
To this piece of code without problem? Disposing resource is always done?
public static async Task<IEnumerable<T>> deletePostsAsync<T>(IEnumerable<string> urls) where T : BaseReturnValues
{
using (var client = SetClientSettings())
{
var tasks = urls.Select(async url => await DeleteAsync<T>(client, url).ConfigureAwait(false));
var result = await Task.WhenAll(tasks).ConfigureAwait(false);
return result!;
}
async Task<U> DeleteAsync<U>(HttpClient client, string url) where U : BaseReturnValues
{
:
:
return record;
}
}
public static Httpclient SetClientSettings()
{
var httpClientHandler = new HttpClientHandler
{
Proxy = new WebProxy(proxy, true),
UseProxy = IsProxySelected
};
var client = new HttpClient(httpClientHandler);
client.BaseAddress = new Uri(URI);
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add(Head.key, Head.apikey);
return client;
}
So I have created a method SetClientSettings and this method create the client, the clienthandler, add some headers to client and return client.
so
var httpClientHandler = new HttpClientHandler
{
Proxy = new WebProxy(proxy, true),
UseProxy = IsProxySelected
};
using (var client = new HttpClient(httpClientHandler))
{
client.BaseAddress = new Uri(URI);
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add(Head.key, Head.apikey);
var tasks = urls.Select(async url => await DeleteAsync<T>(client, url).ConfigureAwait(false));
var result = await Task.WhenAll(tasks).ConfigureAwait(false);
return result!;
}
is really equivalent to??:
using (var client = SetClientSettings())
{
var tasks = urls.Select(async url => await DeleteAsync<T>(client, url).ConfigureAwait(false));
var result = await Task.WhenAll(tasks).ConfigureAwait(false);
return result!;
}
Your two pieces of code are not equivalent. There is a small difference, that with the first snippet you will dispose the client even when the following lines (e.g. client.BaseAddress = new Uri(URI); or client.DefaultRequestHeaders.Clear();) fail. Which is better. To achieve this with SetClientSettings you would need to wrap everything into try except and .Dispose() on exception.
I have read the using statement automatically dispose resources (here HttpClient and HttpClientHandler).
The using statement turns this:
using (var instance = something)
{
// body
}
into this
var instance = something;
try
{
// body
}
finally
{
if (instance != null)
{
instance.Dispose();
}
}
That's all it does. It is a syntactic sugar.
And so in your particular case the using statement will ensure that .Dispose() is called on HttpClient, regardless of whether exception is thrown or not.
Now, do we have to dispose HttpClient? Well, they say we have to, there's no reason not to believe it. In reality the HttpClient holds sockets under the hood, which have to be closed manually when done with. And so, yes, you should always dispose HttpClient when done with.
That being said, the best thing you can do is to have a singleton HttpClient for the duration of your app, and reuse it. You can tweak it to your needs (e.g. configure it to use pooled connections) for maximal efficiency. In such scenario you don't dispose it at all.
Note: you don't have to worry about disposing HttpClientHandler. By default HttpClient will dispose it when it is disposed itself. This behaviour can be modified by using different constructor.
Yes, the code you wrote is equivalent to each other. There's an option in HttpClient contructor to not to dispose message handler - by default, it's set to true. Anyway, as already suggested, you don't have to dispose HTTP client at all. There are reasons for that.
There's alot of nice articles about best practices of using HttpClient.
Try to search for IHttpClientFactory.

Polly: Retry request with StreamContent and MemoryStream - no content on second try

I'm using Polly in combination with Microsoft.Extensions.Http.Polly to handle communication with an external API which has rate-limiting (N requests / second).I'm also using .NET 6.
The policy itself works fine for most requests, however it doesn't work properly for sending (stream) data. The API Client requires the usage of MemoryStream. When the Polly policy handles the requests and retries it, the stream data is not sent.
I verified this behavior stems from .NET itself with this minimal example:
using var fileStream = File.OpenRead(#"C:\myfile.pdf");
using var memoryStream = new MemoryStream();
await fileStream.CopyToAsync(memoryStream);
var response = await httpClient.SendAsync(
new HttpRequestMessage
{
// The endpoint will fail the request on the first request
RequestUri = new Uri("https://localhost:7186/api/test"),
Content = new StreamContent(memoryStream),
Method = HttpMethod.Post
}
);
Inspecting the request I see that Request.ContentLength is the length of the file on the first try. On the second try it's 0.
However if I change the example to use the FileStream directly it works:
using var fileStream = File.OpenRead(#"C:\myfile.pdf");
var response = await httpClient.SendAsync(
new HttpRequestMessage
{
// The endpoint will fail the request on the first request
RequestUri = new Uri("https://localhost:7186/api/test"),
Content = new StreamContent(fileStream ),
Method = HttpMethod.Post
}
);
And this is my Polly policy that I add to the chain of AddHttpClient.
public static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
return Policy
.HandleResult<HttpResponseMessage>(response =>
{
return response.StatusCode == System.Net.HttpStatusCode.Forbidden;
})
.WaitAndRetryAsync(4, (retry) => TimeSpan.FromSeconds(1));
}
My question:
How do I properly retry requests where StreamContent with a stream of type MemoryStream is involved, similar to the behavior of FileStream?
Edit for clarification:
I'm using an external API Client library (Egnyte) which accepts an instance of HttpClient
public class EgnyteClient {
public EgnyteClient(string apiKey, string domain, HttpClient? httpClient = null){
...
}
}
I pass an instance which I injected via the HttpContextFactory pattern. This instance uses the retry policy from above.
This is my method for writing a file using EgnyteClient
public async Task UploadFile(string path, MemoryStream stream){
// _egnyteClient is assigned in the constructor
await _egnyteClient.Files.CreateOrUpdateFile(path, stream);
}
This method call works (doesn't throw an exception) even when the API sometimes returns a 403 statucode because the internal HttpClient uses the Polly retry policy. HOWEVER the data isn't always properly transferred since it just works if it was the first attempt.
The root cause of your problem could be the following: once you have sent out a request then the MemoryStream's Position is at the end of the stream. So, any further requests needs to rewind the stream to be able to copy it again into the StreamContent (memoryStream.Position = 0;).
Here is how you can do that with retry:
private StreamContent GetContent(MemoryStream ms)
{
ms.Position = 0;
return new StreamContent(ms);
}
var response = await httpClient.SendAsync(
new HttpRequestMessage
{
RequestUri = new Uri("https://localhost:7186/api/test"),
Content = GetContent(memoryStream),
Method = HttpMethod.Post
}
);
This ensures that the memoryStream has been rewinded for each each retry attempt.
UPDATE #1
After receiving some clarification and digging in the source code of the Egnyte I think I know understand the problem scope better.
A 3rd party library receives an HttpClient instance which is decorated with a retry policy (related source code)
A MemoryStream is passed to a library which is passed forward as a StreamContent as a part of an HttpRequestMessage (related source code)
HRM is passed directly to the HttpClient and the response is wrapped into a ServiceResponse (related source code)
Based on the source code you can receive one of the followings:
An HttpRequestException thrown by the HttpClient
An EgnyteApiException or QPSLimitExceededException or RateLimitExceededException thrown by the ExceptionHelper
An EgnyteApiException thrown by the SendRequestAsync if there was a problem related to the deserialization
A ServiceResponse from SendRequestAsync
As far as I can see you can access the StatusCode only if you receive an HttpRequestException or an EgnyteApiException.
Because you can't rewind the MemoryStream whenever an HttpClient performs a retry I would suggest to decorate the UploadFile with retry. Inside the method you can always set the stream parameter's Position to 0.
public async Task UploadFile(string path, MemoryStream stream){
stream.Position = 0;
await _egnyteClient.Files.CreateOrUpdateFile(path, stream);
}
So rather than decorating the entire HttpClient you should decorate your UploadFile method with retry. Because of this you need to alter the policy definition to something like this:
public static IAsyncPolicy GetRetryPolicy()
=> Policy
.Handle<EgnyteApiException>(ex => ex.StatusCode == HttpStatusCode.Forbidden)
.Or<HttpRequestException>(ex => ex.StatusCode == HttpStatusCode.Forbidden)
.WaitAndRetryAsync(4, _ => TimeSpan.FromSeconds(1));
Maybe the Or builder clause is not needed because I haven't seen any EnsureSuccessStatusCode call anywhere, but for safety I would build the policy like that.

Dispose HTTP response in .NET WebAPI

I have a .NET WebAPI application and this is one of my api:
public IHttpActionResult Get()
{
...building myResult here...
var content = ElasticSearch.Json.ToJson(myResult);
var response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(content, Encoding.UTF8, "application/json");
return ResponseMessage(response);
}
I get a CA2000 error from code analisys:
Error CA2000 In method 'GroupsController.Get(string, string, string,
bool, string)', call System.IDisposable.Dispose on object 'response'
before all references to it are out of
scope
So I modified the code like this:
var content = ElasticSearch.Json.ToJson(myResult);
using (var response = Request.CreateResponse(HttpStatusCode.OK))
{
response.Content = new StringContent(content, Encoding.UTF8, "application/json");
return ResponseMessage(response);
}
So far so good. No memory leaks and the code analyzer is happy again.
Unfortunately now one of my test is complaining that it cannot access anymore a disposed object. Here the test testing that api (just the final part):
// Assert
var httpResponseMessage = await result.ExecuteAsync(CancellationToken.None);
var resultJson = await httpResponseMessage.Content.ReadAsStringAsync();
Assert.AreEqual(expectedJson, resultJson);
The Assert() is complaining that it cannot access an already disposed object, that is the actual api result:
System.ObjectDisposedException : Cannot access a disposed object.
Object name: 'System.Net.Http.StringContent'. at
System.Net.Http.HttpContent.CheckDisposed() at
System.Net.Http.HttpContent.ReadAsStringAsync()
How can I fix that? Disposing the object seems reasonable but at the same time the test should be able to access it
You can use ApiController.OK
return Ok(myResult);
You should not use using (var response = Request.CreateResponse(HttpStatusCode.OK)) because ResponseMessageResult will hold reference to disposed HttpResponseMessage. That's the reason you get this error in assert.
To check, change your code to snippet below and add breakpoint on result. Check result.Response.disposed
using (var response = Request.CreateResponse(HttpStatusCode.OK))
{
response.Content = new StringContent(content, Encoding.UTF8, "application/json");
result = ResponseMessage(response);
}
// result.Response.disposed is true hence error in assert.
return result;

PushStreamContent Web API

I am trying to push my data from web API to client, so I use PushStreamContent in Web API.
My data are all student exist in database.
public HttpResponseMessage GetAll()
{
HttpResponseMessage response = new HttpResponseMessage();
var students = _symboleService.GetAll();
response.Content = new PushStreamContent((students, httpContent, context) =>
{
OnStreamAvailable(students, httpContent, context);
}, "text/plain");
return response;
}
private void OnStreamAvailable(Stream stream, HttpContent content,TransportContext context)
{
StreamWriter responseStreamWriter = new StreamWriter(stream);
clients.Add(responseStreamWriter);
}
But an error shows on "students" on this line:
response.Content = new PushStreamContent((students, httpContent, context) =>
A local or parameter named 'students' cannot be declared in this scope
because that name is used in an enclosing local scope to define a
local or parameter
Update My point here is to push the data to the client side,
After some modification, this my code but it shows an error on line,
var students = _symboleService.GetAll();
An object reference is required for the non-static field, method, or
property 'SymbolesController._symboleService'
private static readonly Lazy<Timer> _timer = new Lazy<Timer>(() => new Timer(TimerCallback, null, 0, 1000));
private static readonly ConcurrentDictionary<StreamWriter, StreamWriter> _outputs = new ConcurrentDictionary<StreamWriter, StreamWriter>();
public HttpResponseMessage GetAll()
{
HttpResponseMessage response = new HttpResponseMessage();
response.Content = new PushStreamContent((responseStream, httpContent, context) =>
{
StreamWriter responseStreamWriter = new StreamWriter(responseStream);
_outputs.TryAdd(responseStreamWriter, responseStreamWriter);
}, "text/plain");
Timer t = _timer.Value;
return response;
}
// Runs every second after the first request to this controller and
// writes to the response streams of all currently active requests
private static void TimerCallback(object state)
{
foreach (var kvp in _outputs.ToArray())
{
StreamWriter responseStreamWriter = kvp.Value;
try
{
var students = _symboleService.GetAll();
responseStreamWriter.Write(students);
responseStreamWriter.Flush();
}
catch { }
}
}
By the way, if I change studentsby DateTime.Now here, it works:
responseStreamWriter.Write(students); to
responseStreamWriter.Write(DateTime.Now);
You need to rename the students parameter used in PushStreamContent constructor.
For example, from:
response.Content = new PushStreamContent((students, httpContent, context) =>
{
OnStreamAvailable(students, httpContent, context);
}, "text/plain");
to:
response.Content = new PushStreamContent((stream, httpContent, context) =>
{
OnStreamAvailable(stream, httpContent, context);
}, "text/plain");
The reason why you cannot use students is because it's already declared as a variable in the previous line:
var students = _symboleService.GetAll();
There are a couple of issues here that are going to impede your progress.
To start with, the Action in your PushStreamContent constructor cannot reuse the variable name "students". It appears that you think you're passing the reference to the Action, but that's not how this works.
You could try this:
response.Content = new PushStreamContent(OnStreamAvailable, "text/plain");
But there remains another problem. The stream in the PushStreamContent constructor's action is an output stream.
public PushStreamContent(Action<Stream, HttpContent, TransportContext> onStreamAvailable);
You'll want to write to that stream from within your OnStreamAvailable handler.
Since it's not clear what the _symbolService.GetAll() method, I can only speculate that a Byte[] generated from the method should be written to your output stream within your action itself.
Assuming that your student data is not really a stream or inherently streamable, this may be a misuse of the PushStreamContent class.

.NET HttpClient hangs when using await keyword?

After considering this interesting answer HttpClient.GetAsync(...) never returns..., I still have a situation where my HttpClient is not returning when I use await (sample code below). In addition. I use this helper routine from both asp.net MVC5 Controllers (UI-driven) and the WebApi. What can I do to:
Use await instead of the (dreaded) .Result and still have this function return?
Reuse this same routine from both MVC Controllers and WebApi?
Apparently, I should replace the .Result with .ConfigureAwait(false) but this seems to contradict with the fact that "Task4" in the post cited above works fine with an await httpClient.GetAsync. Or do I need separate routines for Controller and WebApi cases?
public static async Task<IEnumerable<TcMarketUserFullV1>> TcSearchMultiUsersAsync(string elasticQuery)
{
if (string.IsNullOrEmpty(elasticQuery)) return null;
IEnumerable<TcMarketUserFullV1> res = null;
using (var hclient = new HttpClient())
{
hclient.BaseAddress = new Uri("https://addr.servicex.com");
hclient.DefaultRequestHeaders.Accept.Clear();
hclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
hclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer",
CloudConfigurationManager.GetSetting("jwt-bearer-token"));
// Why does this never return when await is used?
HttpResponseMessage response = hclient.GetAsync("api/v2/users?q=" + elasticQuery + "&search_engine=v2").Result;
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
res = JsonConvert.DeserializeObject<TcMarketUserFullV1[]>(content).AsEnumerable();
}
else{log.Warn("...");}
}
return res;
}
UPDATE: My call chain, which starts with a Telerik Kendo Mvc.Grid DataBinding call is as follows:
[HttpPost]
public async Task<ActionResult> TopLicenseGrid_Read([DataSourceRequest]DataSourceRequest request)
{
var res = await GetLicenseInfo();
return Json(res.ToDataSourceResult(request)); // Kendo.Mvc.Extensions.DataSourceRequest
}
Then:
private async Task<IEnumerable<CsoPortalLicenseInfoModel>> GetLicenseInfo()
{
...
// Never returns
var qry = #"app_metadata.tc_app_user.country:""DE""";
return await TcSearchMultiUsersAsync(qry);
}
Then, the routine shown in full above but now WITHOUT the .Result:
public static async Task<IEnumerable<TcMarketUserFullV1>> TcSearchMultiUsersAsync(string elasticQuery)
{
if (string.IsNullOrEmpty(elasticQuery)) return null;
IEnumerable<TcMarketUserFullV1> res = null;
using (var hclient = new HttpClient())
{
hclient.BaseAddress = new Uri("https://addr.servicex.com");
hclient.DefaultRequestHeaders.Accept.Clear();
hclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
hclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer",
CloudConfigurationManager.GetSetting("jwt-bearer-token"));
// This now returns fine
HttpResponseMessage response = hclient.GetAsync("api/v2/users?search_engine=v2&q=" + elasticQuery");
if (response.IsSuccessStatusCode)
{
// This returns my results fine too
var content = await response.Content.ReadAsStringAsync();
// The following line never returns results. When debugging, everything flows perfectly until I reach the following line, which never
// returns and the debugger returns me immediately to the top level HttpPost with a result of null.
res = JsonConvert.DeserializeObject<TcMarketUserFullV1[]>(content).AsEnumerable();
}
else{log.Warn("...");}
}
return res;
}
You need to await everything. It's not enough that you await on one of them, you need to await on all of them:
This:
HttpResponseMessage response = hclient.GetAsync(
"api/v2/users?q=" + elasticQuery + "&search_engine=v2").Result;
Should be:
HttpResponseMessage response = await hclient.GetAsync(
"api/v2/users?q=" + elasticQuery + "&search_engine=v2");
It is enough to have one blocking call to .Result in order to deadlock. You need "async all the way".

Categories

Resources