Send file from local filesystem with mocked HttpClient - c#

Currently, I am retrieving a file by making a HTTP call with a HttpClient.
mRetriever = new MyRetriever(new HttpClient());
result = mRetriever.MyRetriever("https://some.url/myFile.js");
I would like to mock this call. After looking here, I added this to my code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace MyTestModule
{
public class FakeResponseHandler : DelegatingHandler
{
private readonly Dictionary<Uri, HttpResponseMessage> _FakeResponses = new Dictionary<Uri, HttpResponseMessage>();
public void AddFakeResponse(Uri uri, HttpResponseMessage responseMessage)
{
_FakeResponses.Add(uri, responseMessage);
}
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
if (_FakeResponses.ContainsKey(request.RequestUri))
{
return _FakeResponses[request.RequestUri];
}
else
{
return new HttpResponseMessage(HttpStatusCode.NotFound) { RequestMessage = request };
}
}
}
}
However, I'm not sure how to move on from here:
I added this code where myLocalFile is the file I would like to return as a response.
FakeResponseHandler fakeResponseHandler = new FakeResponseHandler();
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
response.AddHeader("content-disposition", "attachment;filename=" + "./myLocalFile");
fakeResponseHandler.AddFakeResponse(new Uri("https://some.url/myFile.js"), response);
Mock<HttpClient> mockHttpClient = new Mock<HttpClient>();
HttpClient httpClient = new HttpClient(fakeResponseHandler);
However, I don't know:
1.How to reference a file from the local file system in the code.
2.How to add that file to HttpResponseMessage.
The current way I am doing it:
response.AddHeader("content-disposition", "attachment;filename=" + "./myLocalFile");
throws this error:
'HttpResponseMessage' does not contain a definition for 'AddHeader'
and no extension method 'AddHeader' accepting a first argument of type
'HttpResponseMessage' could be found(are you missing a using directive
or an assembly reference?)

Related

C# can't access an in-memory database created for integration tests

I have a C# / ASP.NET Core MVC app for which I'm writing integration tests. I've been following https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-6.0 and I created a CustomWebApplicationFactory like this in the following file:
CustomWebApplicationFactory.cs
using System;
using System.Linq;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using amaranth.Data;
namespace amaranth.Tests
{
#region snippet1
public class CustomWebApplicationFactory<TStartup>
: WebApplicationFactory<TStartup> where TStartup: class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
var descriptor = services.SingleOrDefault(
d => d.ServiceType ==
typeof(DbContextOptions<ApplicationDbContext>));
services.Remove(descriptor);
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseInMemoryDatabase("InMemoryDbForTesting");
});
services.AddAntiforgery(t =>
{
t.Cookie.Name = AntiForgeryTokenExtractor.AntiForgeryCookieName;
t.FormFieldName = AntiForgeryTokenExtractor.AntiForgeryFieldName;
});
var sp = services.BuildServiceProvider();
using (var scope = sp.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var db = scopedServices.GetRequiredService<ApplicationDbContext>();
var logger = scopedServices
.GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();
db.Database.EnsureCreated();
}
});
}
}
#endregion
}
I also have the following helper files:
Helpers/HtmlHelpers.cs
Helpers/using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using AngleSharp;
using AngleSharp.Html.Dom;
using AngleSharp.Io;
namespace amaranth.Tests.Helpers
{
public class HtmlHelpers
{
public static async Task<IHtmlDocument> GetDocumentAsync(HttpResponseMessage response)
{
var content = await response.Content.ReadAsStringAsync();
var document = await BrowsingContext.New()
.OpenAsync(ResponseFactory, CancellationToken.None);
return (IHtmlDocument)document;
void ResponseFactory(VirtualResponse htmlResponse)
{
htmlResponse
.Address(response.RequestMessage.RequestUri)
.Status(response.StatusCode);
MapHeaders(response.Headers);
MapHeaders(response.Content.Headers);
htmlResponse.Content(content);
void MapHeaders(HttpHeaders headers)
{
foreach (var header in headers)
{
foreach (var value in header.Value)
{
htmlResponse.Header(header.Key, value);
}
}
}
}
}
}
}
Helpers/HttpClientExtensions.cs
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using AngleSharp.Html.Dom;
using Xunit;
namespace amaranth.Tests.Helpers
{
public static class HttpClientExtensions
{
public static Task<HttpResponseMessage> SendAsync(
this HttpClient client,
IHtmlFormElement form,
IHtmlElement submitButton)
{
return client.SendAsync(form, submitButton, new Dictionary<string, string>());
}
public static Task<HttpResponseMessage> SendAsync(
this HttpClient client,
IHtmlFormElement form,
IEnumerable<KeyValuePair<string, string>> formValues)
{
var submitElement = Assert.Single(form.QuerySelectorAll("[type=submit]"));
var submitButton = Assert.IsAssignableFrom<IHtmlElement>(submitElement);
return client.SendAsync(form, submitButton, formValues);
}
public static Task<HttpResponseMessage> SendAsync(
this HttpClient client,
IHtmlFormElement form,
IHtmlElement submitButton,
IEnumerable<KeyValuePair<string, string>> formValues)
{
foreach (var kvp in formValues)
{
var element = Assert.IsAssignableFrom<IHtmlInputElement>(form[kvp.Key]);
element.Value = kvp.Value;
}
var submit = form.GetSubmission(submitButton);
var target = (Uri)submit.Target;
if (submitButton.HasAttribute("formaction"))
{
var formaction = submitButton.GetAttribute("formaction");
target = new Uri(formaction, UriKind.Relative);
}
var submission = new HttpRequestMessage(new HttpMethod(submit.Method.ToString()), target)
{
Content = new StreamContent(submit.Body)
};
foreach (var header in submit.Headers)
{
submission.Headers.TryAddWithoutValidation(header.Key, header.Value);
submission.Content.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
return client.SendAsync(submission);
}
}
}
The app I'm testing is called amaranth and my testing project is called amaranth.Tests. In the amaranth controller AdminController.cs, I am trying to test the function CreateMasterWallet which looks like this:
[HttpPost]
public IActionResult CreateMasterWallet(bool isTestNet, string label)
{
_db.MasterWallets.Add(new MasterWallet
{
Label = label,
Address = BitcoinHelper.CreatePrivateKey(isTestNet),
IsTestNet = isTestNet
});
_db.SaveChanges();
return RedirectToAction(nameof(MasterWalletList));
}
I simply need to write a test to make sure that a master wallet is successfully created and added to my in memory database but I don't know how to access my in memory database for the test. This was my best attempt at writing a test that checks to see if a master wallet is being successfully added to the database:
[Fact]
public void CreateMasterWalletTest()
{
var _db = _factory.db;
_db.MasterWallets.Add(new MasterWallet
{
Label = label,
Address = BitcoinHelper.CreatePrivateKey(isTestNet),
IsTestNet = isTestNet
});
Assert.True(_db.Count() > 0);
}
It fails with this error:
/path/to/directory/amaranthPost052422Dir/amaranth.Tests/IntegrationTests/AdminControllerTests.cs(18,32): error CS1061: 'CustomWebApplicationFactory<Startup>' does not contain a definition for 'db' and no accessible extension method 'db' accepting a first argument of type 'CustomWebApplicationFactory<Startup>' could be found (are you missing a using directive or an assembly reference?) [/path/to/directory/amaranthPost052422Dir/amaranth.Tests/amaranth.Tests.csproj]
/path/to/directory/amaranthPost052422Dir/amaranth.Tests/IntegrationTests/AdminControllerTests.cs(19,39): error CS0246: The type or namespace name 'MasterWallet' could not be found (are you missing a using directive or an assembly reference?) [/path/to/directory/amaranthPost052422Dir/amaranth.Tests/amaranth.Tests.csproj]
/path/to/directory/amaranthPost052422Dir/amaranth.Tests/IntegrationTests/AdminControllerTests.cs(21,25): error CS0103: The name 'label' does not exist in the current context [/path/to/directory/amaranthPost052422Dir/amaranth.Tests/amaranth.Tests.csproj]
/path/to/directory/amaranthPost052422Dir/amaranth.Tests/IntegrationTests/AdminControllerTests.cs(22,27): error CS0103: The name 'BitcoinHelper' does not exist in the current context [/path/to/directory/amaranthPost052422Dir/amaranth.Tests/amaranth.Tests.csproj]
/path/to/directory/amaranthPost052422Dir/amaranth.Tests/IntegrationTests/AdminControllerTests.cs(22,58): error CS0103: The name 'isTestNet' does not exist in the current context [/path/to/directory/amaranthPost052422Dir/amaranth.Tests/amaranth.Tests.csproj]
/path/to/directory/amaranthPost052422Dir/amaranth.Tests/IntegrationTests/AdminControllerTests.cs(23,29): error CS0103: The name 'isTestNet' does not exist in the current context [/path/to/directory/amaranthPost052422Dir/amaranth.Tests/amaranth.Tests.csproj]
So how do I write a test to ensure that CreateMasterWallet is successfully creating a new MasterWallet and saving to the database?
To retrieve the database you've registered with the webhost builder, you can do something like this:
using (var scope = _factory.Host.Services.CreateScope())
{
_db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
}
The CustomWebApplicationFactory doesn't know that you call that ApplicationDbContext db, and doesn't have a field like that.
For the other build errors:
You probably also need to create some constants or other variables for label and isTestNet.
BitcoinHelper is probably in an unreferenced namespace, same with MasterWallet. You might check that you have the right references in your test project, and that you've got a using statement at the top of your test file referencing their namespaces. You can also use Visual Studio's right click menu to add the reference/using statement if it recognizes those types.

Azure Functions unit-testing errors (TimerTrigger. HttpFunction)

I am currently following this guide (https://learn.microsoft.com/en-us/azure/azure-functions/functions-test-a-function) for adding testing to my Azure Functions application.
Currently I have built out 8 Azure Functions which all work well, I have also added a Functions.Tests project and referenced the Azure Functions project within it.
Here is what the Functions.Tests currently look like.
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Xunit;
namespace Functions.Tests
{
public class FunctionsTests
{
private readonly ILogger logger = TestFactory.CreateLogger();
[Fact]
public async void Http_trigger_should_return_known_string()
{
var request = TestFactory.CreateHttpRequest("name", "Bill");
var response = (OkObjectResult)await HttpFunction.Run(request, logger);
Assert.Equal("Hello, Bill", response.Value);
}
[Theory]
[MemberData(nameof(TestFactory.Data), MemberType = typeof(TestFactory))]
public async void Http_trigger_should_return_known_string_from_member_data(string queryStringKey, string queryStringValue)
{
var request = TestFactory.CreateHttpRequest(queryStringKey, queryStringValue);
var response = (OkObjectResult)await HttpFunction.Run(request, logger);
Assert.Equal($"Hello, {queryStringValue}", response.Value);
}
[Fact]
public void Timer_should_log_message()
{
var logger = (ListLogger)TestFactory.CreateLogger(LoggerTypes.List);
TimerTrigger.Run(null, logger);
var msg = logger.Logs[0];
Assert.Contains("C# Timer trigger function executed at", msg);
}
}
}
However I am getting the following errors within FunctionsTests.cs
I have tried all the suggested fixes from Visual Studio and checked resources online but with no luck. Perhaps I am missing a reference? I'm not sure as I have followed the guide word for word.
Example Azure Function used:
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace Exemplar
{
public static class getCase
{
[FunctionName("getCase")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "v1/case/caseId")] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string name = req.Query["name"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
return name != null
? (ActionResult)new OkObjectResult($"Hello, {name}")
: new BadRequestObjectResult("Please pass a name on the query string or in the request body");
}
}
}
Assuming provided project that contains the function is referenced, the test simply needs to arrange and exercise the target function as shown in the linked example
[Fact]
public async Task getCase_should_return_known_string()
{
var request = TestFactory.CreateHttpRequest("name", "Bill");
var response = (OkObjectResult)await getCase.Run(request, logger);
Assert.Equal("Hello, Bill", response.Value);
}
[Theory]
[MemberData(nameof(TestFactory.Data), MemberType = typeof(TestFactory))]
public async Task getCase_should_return_known_string_from_member_data(string queryStringKey, string queryStringValue)
{
var request = TestFactory.CreateHttpRequest(queryStringKey, queryStringValue);
var response = (OkObjectResult)await getCase.Run(request, logger);
Assert.Equal($"Hello, {queryStringValue}", response.Value);
}
Also avoid using async void. Refactor those tests to use async Task instead.
Seems to me it's missing a reference to your azure functios project, make sure you've added the reference by:
right clicking your test project
select the menu Add
select Reference
select the projects tab (left menu), then mark the checkbox of your function project
click ok

Blazor Adding HttpClientHandler to add Jwt to HTTP Header on requests

I am using the Visual Studio 2019 and .Net Core 3.0.0-preview-7 with the standard Blazor Client, Server and Shared templates.
In the application our server side WebApi application will always require a JWT token to be present in the header for authorization.
From looking at the following
Make HTTP requests using IHttpClientFactory in ASP.NET Core
I created the following handler;
public class JwtTokenHeaderHandler : DelegatingHandler
{
private readonly ILocalStorageService _localStorage;
public JwtTokenHeaderHandler(ILocalStorageService localStorage)
{
_localStorage = localStorage;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (!request.Headers.Contains("bearer"))
{
var savedToken = await _localStorage.GetItemAsync<string>("authToken");
if (!string.IsNullOrWhiteSpace(savedToken))
{
request.Headers.Add("bearer", savedToken);
}
}
return await base.SendAsync(request, cancellationToken);
}
}
Where I use Blazored.LocalStorage to get the saved token from localstorage and add it to the header.
Now, at this point I am not sure what to do as if I add the following to the Blazor.Client Startup.cs;
services.AddTransient<JwtTokenHeaderHandler>();
services.AddHttpClient("JwtTokenHandler")
.AddHttpMessageHandler<JwtTokenHeaderHandler>();
I get the error message;
'IServiceCollection' does not contain a definition for 'AddHttpClient'
and no accessible extension method 'AddHttpClient' accepting a first
argument of type 'IServiceCollection' could be found (are you missing
a using directive or an assembly reference?)
Can anyone direct me to what I am doing wrong here?
I found a really good tutorial and sample demonstrating this (complete with roles/policy based claims):
https://chrissainty.com/securing-your-blazor-apps-authentication-with-clientside-blazor-using-webapi-aspnet-core-identity/
Here is an extract below, setting the default request headers on the default http client (through DI). All calls to your web api will then include the bearer token:
public class ApiAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly HttpClient _httpClient;
private readonly ILocalStorageService _localStorage;
public ApiAuthenticationStateProvider(HttpClient httpClient, ILocalStorageService localStorage)
{
_httpClient = httpClient;
_localStorage = localStorage;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var savedToken = await _localStorage.GetItemAsync<string>("authToken");
if (string.IsNullOrWhiteSpace(savedToken))
{
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
}
// ************** Set JWT header ****************
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", savedToken);
// *******************************************************
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(savedToken), "jwt")));
}
// ...
}
#Matthew Flynn, currently you can't use IHttpClientFactory on client-side Blazor.
And you don't have to derive from HttpMessageHandler (DelegatingHandler). It has already been done by Blazor. The following is n extension class to extend the functionality of the HttpClient service to enable the ability to add the Jwt token to the header of the request message...
ServiceExtensions.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
public static class ServiceExtensions
{
public static async Task<T> GetJsonAsync<T>(this HttpClient httpClient, string url, AuthenticationHeaderValue authorization)
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Authorization = authorization;
var response = await httpClient.SendAsync(request);
var responseBytes = await response.Content.ReadAsByteArrayAsync();
return JsonSerializer.Parse<T>(responseBytes, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
}
}
The following show how to call an endpoint on your Web Api, passing the Jwt token which is read from the localStorage. (incidentally, none of these versions is secured with data protection)
Index.razor
#page "/"
#inject ILocalStorageService localStorage
#inject HttpClient Http
<div class="mdc-card main-content-card">
<h1 class="#MdcTypography.H4">Hello, world!</h1>
Welcome to your new app.
</div>
// Razor content to display emloyees come here.....
#code {
Employee[] employees;
protected override async Task OnInitAsync()
{
var token = await localStorage.GetTokenAsync();
employees = await Http.GetJsonAsync<Employee[]>(
"api/employees",
new AuthenticationHeaderValue("Bearer", token));
}
}
Hope this works... If not, and you can't solve thr errors, come here and tell the community about it...
The following adds X-CSRF-TOKEN header to http requests:
public class CustomHttpMessageHandler : DelegatingHandler
{
private readonly IJSRuntime _js;
public CustomHttpMessageHandler(IJSRuntime js)
{
_js = js;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var afrt = await _js.InvokeAsync<string>("getCookie", ".AFRT");
request.Headers.Add("X-CSRF-TOKEN", afrt);
return await base.SendAsync(request, cancellationToken);
}
}
In Program.cs configure like below:
builder.Services.AddScoped<CustomHttpMessageHandler>();
builder.Services.AddHttpClient("ApiClient", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<CustomHttpMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("ApiClient"));
You need to install Microsoft.Extensions.Http package to your blazor webassembly client.
You need the NuGet package Microsoft.Extensions.Http which contains the AddHttpClient method. Install it with the following command: Install-Package Microsoft.Extensions.Http -Version 3.0.0-preview7.19362.4
As it seems, this NuGet package is automatically provided in server-side blazor but has to be installed seperately in client-side blazor.

Authenticating with Clockify API?

I'm working on a new application where I need to use Clockify's API. When I make my test application for a proof of concept, I notice that I'm getting a 401 error as a response to using one of their base functions, get clients by work space. Am I missing something with the authentication? Is there a setting I need to allow on my profile? The error I'm getting is: System.Net.WebException: 'The remote server returned an error: (401) Unauthorized.' Thanks.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
namespace Api
{
public class ApiHelper
{
public static HttpClient ApiClient { get; set; } = new HttpClient();
public static void InitializeClient(string username, string password)
{
ApiClient = new HttpClient();
ApiClient.BaseAddress = new Uri("https://api.clockify.me/api/");
ApiClient.DefaultRequestHeaders.Accept.Clear();
ApiClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
public static void GetClientsFromWorkspace(string workspace)
{
ApiClient.DefaultRequestHeaders.Add("X-Api-Key", "*********");
var httpWebRequest = (HttpWebRequest)WebRequest.Create("https://api.clockify.me/api/workspaces/" + workspace + "/clients");
httpWebRequest.ContentType = "text/json";
httpWebRequest.Method = "GET";
var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
}
}
}
You’re setting the api key header on the ApiClient but then making your request with a newly createdHttpWebRequest which doesn’t then have the required api key header.
You should either make your request using the ApiClient or add the X-Api-Key header to theHttpWebRequest as follows:
httpWebRequest.Headers.Add(“X-Api-Key”, “********”)

Net Core Error The name 'Ok' does not exist in the current context

I am receiving the following error: The name 'Ok' does not exist in the current context.
How would I resolve this issue in my Controller API? Return Ok is already embedded in the controller.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using System.Net.Http;
using Newtonsoft.Json;
using WeatherTest.Models;
namespace WeatherChecker.Controllers
{
public class WeatherData
{
[HttpGet("[action]/{city}")]
public async Task<IActionResult> City(string city)
{
using (var client = new HttpClient())
{
try
{
client.BaseAddress = new Uri("http://api.openweathermap.org");
var response = await client.GetAsync($"/data/2.5/weather?q={city}&appid=YOUR_API_KEY_HERE&units=metric");
response.EnsureSuccessStatusCode();
var stringResult = await response.Content.ReadAsStringAsync();
var rawWeather = JsonConvert.DeserializeObject<OpenWeatherResponse>(stringResult);
// Error Here: ** The name 'Ok' does not exist in the current context **
return Ok(new
{
Temp = rawWeather.Main.Temp,
Summary = string.Join(",", rawWeather.Weather.Select(x => x.Main)),
City = rawWeather.Name
});
}
catch (HttpRequestException httpRequestException)
{
// Error Here: The name 'BadRequest' does not exist in the current context
return BadRequest($"Error getting weather from OpenWeather: {httpRequestException.Message}");
}
}
}
}
}
With Attribute routing feature, aspnet support POCO controller. It allow to use any class as controller. But you will we lose all utilities and helpers provided by framework base classes.
The class Controller inherite from ControllerBase and add view support. In your case, ControllerBase is enough.
public class WeatherData : ControllerBase // <-
{
// ...
}

Categories

Resources