Access Cookies inside unit test in AspNet.TestHost.TestServer context on ASP.NET 5 / MVC 6 - testing

There is no easy way to get an access to a CookieContainer in response object running integration tests with AspNet.TestHost.TestServer. Cookies have to be set by the controller action. What is the best way to achieve that?
var client = TestServer.Create(app =>
{
app.UseMvc(routes =>
routes.MapRoute("default", "{controller}/{action}/{id?}"));
app.UseIdentity();
}).CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get, "account/login");
var response = await client.SendAsync(request);
// how to get an access to cookie container ?????????
// response.Cookies prop doesn't exist
Assert.NotEmpty(response.Cookies["auth"]);
Solution that I see is to extend instance of the TestServer, return instance of a class CustomClientHandler : ClientHandler and override the whole process of sending a request in that handler, but it needs literally to change all logic except relatively small code of the TestServer.
Any better suggestion how to implement an access to Cookies in a response?

As an addition to #Oleh's response, you can achieve the same without reflection on newer frameworks like .NET 4.6.1+ / .NET Core
public class TestHttpClientHandler : DelegatingHandler
{
[NotNull]
private readonly CookieContainer cookies = new CookieContainer();
public TestHttpClientHandler([NotNull] HttpMessageHandler innerHandler)
: base(innerHandler) { }
[NotNull, ItemNotNull]
protected override async Task<HttpResponseMessage> SendAsync([NotNull] HttpRequestMessage request, CancellationToken ct)
{
Uri requestUri = request.RequestUri;
request.Headers.Add(HeaderNames.Cookie, this.cookies.GetCookieHeader(requestUri));
HttpResponseMessage response = await base.SendAsync(request, ct);
if (response.Headers.TryGetValues(HeaderNames.SetCookie, out IEnumerable<string> setCookieHeaders))
{
foreach (SetCookieHeaderValue cookieHeader in SetCookieHeaderValue.ParseList(setCookieHeaders.ToList()))
{
Cookie cookie = new Cookie(cookieHeader.Name.Value, cookieHeader.Value.Value, cookieHeader.Path.Value);
if (cookieHeader.Expires.HasValue)
{
cookie.Expires = cookieHeader.Expires.Value.DateTime;
}
this.cookies.Add(requestUri, cookie);
}
}
return response;
}
}

I've implemented custom HttpMessageHandler that tracks cookies.
It uses reflection to invoke the actual handler and just reads/sets Cookie headers.
class TestMessageHandler : HttpMessageHandler
{
delegate Task<HttpResponseMessage> HandlerSendAsync(HttpRequestMessage message, CancellationToken token);
private readonly HandlerSendAsync nextDelegate;
private readonly CookieContainer cookies = new System.Net.CookieContainer();
public TestMessageHandler(HttpMessageHandler next)
{
if(next == null) throw new ArgumentNullException(nameof(next));
nextDelegate = (HandlerSendAsync)
next.GetType()
.GetTypeInfo()
.GetMethod("SendAsync", BindingFlags.NonPublic | BindingFlags.Instance)
.CreateDelegate(typeof(HandlerSendAsync), next);
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("Cookie", cookies.GetCookieHeader(request.RequestUri));
var resp = await nextDelegate(request, cancellationToken).ConfigureAwait(false);
if (resp.Headers.TryGetValues("Set-Cookie", out var newCookies))
{
foreach (var item in SetCookieHeaderValue.ParseList(newCookies.ToList()))
{
cookies.Add(request.RequestUri, new Cookie(item.Name, item.Value, item.Path));
}
}
return resp;
}
}
And then you create your HttpClient like this:
var httpClient = new HttpClient(
new TestMessageHandler(
server.CreateHandler()));
TestMessageHandler now takes care of tracking cookies.

For a dotnet core integration test approach like the one described in the docs here, you can get cookies with the following code:
public class CookieTests : IClassFixture<WebApplicationFactory<Startup>>
{
private readonly WebApplicationFactory<Startup> _factory;
public CookieTests(WebApplicationFactory<Startup> factory)
{
_factory = factory;
}
[Fact]
public async Task GetPage_ShouldSetCookie_CookieSet()
{
using (var client = _factory.CreateClient())
{
var response = await client.GetAsync("/cookie_setting_url");
response.EnsureSuccessStatusCode();
//or other assertions
Assert.True(response.Headers.TryGetValues(HeaderNames.SetCookie, out IEnumerable<string> cookies));
}
}
}

The proper way, using minimal code getting cookies
in Asp.Net Core Functional Tests is as follows, (I leave out init code for setting up WebApplicationFactory, which is known stuff)
The given examples above, require either reflection (Since I think MS made a design bug on not exposing the default handlers) or require cookie parsing, which is annoying in 2023.
private (HttpClient, CookieContainerHandler) GetHttpClient()
{
CookieContainerHandler cookieContainerHandler = new();
var client = _factory.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddAuthentication(defaultScheme: "YourSchema")
.AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
"TestAzure", options => { });
});
}).CreateDefaultClient(cookieContainerHandler);
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue(scheme: "YourSchema");
return (client, cookieContainerHandler);
}
[Fact]
public async Task MyUnitTest()
{
// Arrange
var (client, cookieHandler) = GetHttpClient();
// Act PUT/GET/POST etc
var response = await client.PutAsync("youruri", null);
var sessionCookie = cookieHandler.Container.GetAllCookies().FirstOrDefault(f => f.Name == "yourcookie"); // note this ignores cookie domain policy
}

Related

Why can't my xamarin app recieve data from my API call?

Im new to connecting an API to my xamarin app.
When I try to call the API visual studio & the app do not give a response.
Visual studio keeps running but nothing happens.
I've changed the firewall settings, and set my IP adres in all the desired places. Still not luck.
If I go to my API using swager or postman and I just the same Uri as I want to pass trough with my app I get the correct response.
What could be the reason for this?
my code:
Material service:
private readonly string _baseUri;
public APIMaterialService()
{
_baseUri = "https://192.168.1.9:5001/api";
}
public async Task<Material> GetById(Guid id)
{
return await WebApiClient
.GetApiResult<Material>($"{_baseUri}/Materials/{id}");
}
WebApiClient:
public class WebApiClient
{
private static HttpClientHandler ClientHandler()
{
var httpClientHandler = new HttpClientHandler();
#if DEBUG
//allow connecting to untrusted certificates when running a DEBUG assembly
httpClientHandler.ServerCertificateCustomValidationCallback =
(message, cert, chain, errors) => { return true; };
#endif
return httpClientHandler;
}
private static JsonMediaTypeFormatter GetJsonFormatter()
{
var formatter = new JsonMediaTypeFormatter();
formatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
return formatter;
}
public async static Task<T> GetApiResult<T>(string uri)
{
using (HttpClient httpClient = new HttpClient(ClientHandler()))
{
//Gets stuck finding the response
string response = await httpClient.GetStringAsync(uri);
return JsonConvert.DeserializeObject<T>(response, GetJsonFormatter().SerializerSettings);
}
}
I'll also add some images of the postman and swager response:
This is the code fo my controller.
return OK (material) shows me the data retrieved from the API
public async Task<IActionResult> GetMaterialByPartOfMaterialNumberOP(string partOfMaterialNumber)
{
var material = await _materialService.GetMaterialListbyPartOfMaterialNumber(partOfMaterialNumber);
return Ok(material);
}
The symptom you have (stuck on result from calling a method of HttpClient class) suggests a deadlock.
I believe the deadlock happens if you create multiple instances of HttpClient.
Doc HttpClient Class says:
// HttpClient is intended to be instantiated once per application, rather than per-use. See Remarks.
And shows this code:
static readonly HttpClient client = new HttpClient();
HOWEVER a deadlock would only happen the SECOND time your code does new HttpClient. And using ... new HttpClient should protect you, at least in simple situations.
Here are ways there might be TWO HttpClients active:
Is it possible that GetApiResult gets called A SECOND TIME, before the first one finishes?
Does your app do new HttpClient ELSEWHERE?
Here is what the technique might look like in your app:
public class WebApiClient
{
static HttpClient _client = new HttpClient(ClientHandler());
public async static Task<T> GetApiResult<T>(string uri)
{
string response = await _client.GetStringAsync(uri);
return JsonConvert.DeserializeObject<T>(response, GetJsonFormatter().SerializerSettings);
}
}

Is there a way to use any IDistributedCache as a ResponseCache in .net core?

I want to cache responses from APIs to DistributedSqlServerCache.
The default ResponseCaching only uses a memory cache. There is a constructor which allows to configure what cache to use, but it's internal.
I wrote a filter. If the response is not cached and the http response is OK and the ActionResult is an ObjectActionResult, it serializes the value as JSON and saves it to SQL cache.
If the response is cached, it deserializes it and sets the result as an OkObject result with the deserielized object.
It works ok, but it has some clumsy things (like, to use the attribute, you have to specify the type which will be de/serialized, with typeof()).
Is there a way to cache responses to a distributed sql cache, which doesn't involve me hacking together my own mostly working solution?
Another option would be to copy-pasta the netcore ResponseCacheMiddleWare, and modify it to use a diffirent cache. I could even make it a nuget package maybe.
Are there any other solutions out there?
Here's the filter I put together (simplified for display purposes)
namespace Api.Filters
{
/// <summary>
/// Caches the result of the action as data.
/// The action result must implement <see cref="ObjectResult"/>, and is only cached if the HTTP status code is OK.
/// </summary>
public class ResponseCache : IAsyncResourceFilter
{
public Type ActionType { get; set; }
public ExpirationType ExpirationType;
private readonly IDistributedCache cache;
public ResponseCache(IDistributedCache cache)
{
this.cache = cache;
}
public async Task OnResourceExecutionAsync(ResourceExecutingContext executingContext, ResourceExecutionDelegate next)
{
var key = getKey(executingContext);
var cachedValue = await cache.GetAsync(key);
if (cachedValue != null && executingContext.HttpContext.Request.Query["r"] == "cache")
{
await cache.RemoveAsync(key);
cachedValue = null;
}
if (cachedValue != null)
{
executingContext.Result = new OkObjectResult(await fromBytes(cachedValue));
return;
}
var executedContext = await next();
// Only cache a successful response.
if (executedContext.HttpContext.Response.StatusCode == StatusCodes.Status200OK && executedContext.Result is ObjectResult result)
{
await cache.SetAsync(key, await toBytes(result.Value), getExpiration());
}
}
private async Task<byte[]> toBytes(object value)
{
using var stream = new MemoryStream();
await JsonSerializer.SerializeAsync(stream, value, ActionType);
return stream.ToArray();
}
private async Task<object> fromBytes(byte[] bytes)
{
using var stream = new MemoryStream(bytes);
using var reader = new BinaryReader(stream, Encoding.Default, true);
return await JsonSerializer.DeserializeAsync(stream, ActionType);
}
}
public class ResponseCacheAttribute : Attribute, IFilterFactory
{
public bool IsReusable => true;
public ExpirationType ExpirationType;
public Type ActionType { get; set; }
public ResponseCacheAttribute(params string[] queryParameters)
{
this.queryParameters = queryParameters;
}
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
var cache = serviceProvider.GetService(typeof(IDistributedCache)) as IDistributedCache;
return new ResponseCache(cache)
{
ExpirationType = ExpirationType,
ActionType = ActionType
};
}
}
}
In the end I made a nuget package, sourced on github. See this issue for some more context as to why a new package was made.

How to include credentials when making CORS requests from Blazor?

On a blazor client application, what is the equivalent of jQuery ajax WithCredentials or JavaScript credentials: 'include'?
With Javascript I am able to say:
fetch('https://www.example.com/api/test', {
credentials: 'include'
});
which includes auth cookie while making request and server responds with 200. I am trying to write same with Blazor using HttpClient.
You can no longer set WebAssemblyHttpMessageHandler.DefaultCredentials = FetchCredentialsOption.Include; in your Startup file to achieve js`s credentials: 'include'
To achieve this in newer versions of blazor, you need to create a class that derives from DelegatingHandler, override SendAsync method and set BrowserRequestCredentials for request to BrowserRequestCredentials.Include
public class CookieHandler : DelegatingHandler
{
public CookieHandler()
{
InnerHandler = new HttpClientHandler();
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
return await base.SendAsync(request, cancellationToken);
}
}
After it pass your CookieHandler to HttpClient
builder.Services.AddScoped(sp => new HttpClient(new CookieHandler()) { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
In your Startup.Configure method you can set the WebAssemblyHttpMessageHandler.DefaultCredentials to the required value of the 'credentials' option on outbound HTTP requests like this:
public void Configure(IComponentsApplicationBuilder app)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("WEBASSEMBLY")))
{
WebAssemblyHttpMessageHandler.DefaultCredentials = FetchCredentialsOption.Include;
}
app.AddComponent<App>("app");
}
References:
https://github.com/aspnet/AspNetCore/blob/c39fbb8f12002f61df6c093b0c11e6bd585ee202/src/Components/Blazor/Blazor/src/Http/WebAssemblyHttpMessageHandler.cs
https://github.com/aspnet/AspNetCore/blob/5a70f5312fb57fc3788e5af56a99e7b43761e195/src/Components/Blazor/Blazor/src/Http/FetchCredentialsOption.cs
https://github.com/aspnet/AspNetCore/blob/d18a033b1ee6d923a72d440718c5d496b57c2ffc/src/Components/test/testassets/BasicTestApp/Startup.cs
Hope this helps...

Access IUrlHelper inside DelegatingHandler

Following a migration to ASP.Net core, the following handler does not work. I cannot see how to get access to IUrlHelper from the HttpRequestMessage as was previously possible, and can't find a package with relevant extension methods.
The handler is added using config.MessageHandlers.Add(new LinkDecoratorHandler());
Can anyone help?
public class LinkDecoratorHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage
request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken)
.ContinueWith(task =>
{
var response = task.Result;
if (!(response.Content is ObjectContent))
{
return response;
}
var entity = (response.Content as ObjectContent).Value as ILinkedEntity;
var enumeration = (response.Content as ObjectContent).Value as IEnumerable<ILinkedEntity>;
if (entity != null || enumeration != null)
{
//no longer available
var helper = request.GetUrlHelper();
//blah
}
return response;
});
}
}
Thanks in advance
If your LinkDecoratorHandler is instantiated via dependency injection then you could inject an instance of IActionContextAccessor to get the current ActionContext. From there, you can create your own UrlHelper instance.

ASP.Net core- getting routetemplate value from middleware

I have a middleware that is called for every request to my APIs. I want to log the route template along with the duration of the request from this middleware. How to get route template in my middleware code? Route template is something like "/products/{productId}".
Here is how I got it working. I get the route template inside my filter OnActionExecuting method and add it to HttpContext. Later I access this from HttpContext inside my middleware, since I can access HttpContext inside the middleware.
public class LogActionFilter : IActionFilter
{
public LogActionFilter()
{
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
public void OnActionExecuting(ActionExecutingContext context)
{
context.HttpContext.Items.Add("RouteTemplate", context.ActionDescriptor.AttributeRouteInfo.Template);
}
}
It's not easy to get the route data from a custom middleware because it is created by MVC middleware which generally happens to be the last middleware to be executed in the ASP.NET Core pipeline.
If you want to log the request and response in your middleware as below,
public async Task Invoke(HttpContext context)
{
var requestBodyStream = new MemoryStream();
var originalRequestBody = context.Request.Body;
await context.Request.Body.CopyToAsync(requestBodyStream);
requestBodyStream.Seek(0, SeekOrigin.Begin);
var url = UriHelper.GetDisplayUrl(context.Request);
var requestBodyText = new StreamReader(requestBodyStream).ReadToEnd();
_logger.Log(LogLevel.Information, 1, $"REQUEST METHOD: {context.Request.Method}, REQUEST BODY: {requestBodyText}, REQUEST URL: {url}", null, _defaultFormatter);
requestBodyStream.Seek(0, SeekOrigin.Begin);
context.Request.Body = requestBodyStream;
await next(context);
var bodyStream = context.Response.Body;
var responseBodyStream = new MemoryStream();
context.Response.Body = responseBodyStream;
await _next(context);
responseBodyStream.Seek(0, SeekOrigin.Begin);
var responseBody = new StreamReader(responseBodyStream).ReadToEnd();
_logger.Log(LogLevel.Information, 1, $"RESPONSE LOG: {responseBody}", null, _defaultFormatter);
responseBodyStream.Seek(0, SeekOrigin.Begin);
await responseBodyStream.CopyToAsync(bodyStream);
}
However, if you are really interested in route data, there is very nice SO answer to implement a get routes miidleware here
Other alternative approach would be to use Action Filtersfor request/response logging.
You can actually achieve this quite easily with ASP.NET Core 3.0+ by getting the ControllerActionDescriptor from the context in a middleware:
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
Endpoint endpointBeingHit = context.Features.Get<IEndpointFeature>()?.Endpoint;
ControllerActionDescriptor actionDescriptor = endpointBeingHit?.Metadata?.GetMetadata<ControllerActionDescriptor>();
this.logger.LogInformation(
"Matched route template '{template}'",
actionDescriptor?.AttributeRouteInfo.Template);
await next();
}
it's work for me:
(context.Features.Get<IEndpointFeature>()?.Endpoint as RouteEndpoint)?.RoutePattern.RawText;
Where context is HttpContext