Using hangfire BackgroundJob.Schedule with httpclientfactory DI - c#

I have an api which is using the httpclientfactory to create different typed httpclients using services.addhttpclient<>(). I've started to integrate hangfire with my service due to long running jobs. Everything was working fine until I tried to use Hangfires schdule method "BackgroundJob.Schedule". It starts up, schedules the task but when it tries to execute the code I get:
"Unable to resolve service for type 'System.Net.Http.HttpClient' while attempting to activate 'BackupApi.BackupApiService'."
When trying to use Enqueue method it works without problem. Kinda lost atm, all help much appreciated. My guess is that somehow when the task has scheduled the dependencies is lost when hangfire later tries to use:
using BackupApi;
var backupApiService = Activate<BackupApiService>();
await backupApiService.AdhocBackup("BlurredServername");
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<IBackupApiService, BackupApiService>()
.ConfigurePrimaryHttpMessageHandler(handler =>
new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; }
});
services.AddHangfire(configuration => configuration
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseSqlServerStorage(Configuration.GetConnectionString("HangfireConnection"), new SqlServerStorageOptions
{
SchemaName = "BackupApi",
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
QueuePollInterval = TimeSpan.Zero,
UseRecommendedIsolationLevel = true,
UsePageLocksOnDequeue = true,
DisableGlobalLocks = true
}));
services.Configure<ConnectionInfo>(Configuration.GetSection("ConnectionStrings"));
services.AddHttpContextAccessor();
//services.AddTransient<IBackupApiService, BackupApiService>()
var section = Configuration.GetSection("ConnectionStrings");
services.AddHttpClient<DSDClient>()
.ConfigurePrimaryHttpMessageHandler(handler =>
new HttpClientHandler
{
Credentials = new NetworkCredential(
section["Username"],
section["Password"],
"blurredomain"),
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; }
});
services.AddHangfireServer();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
//.AddViewComponentsAsServices();
}
BackupApiService.cs
public class BackupApiService : IBackupApiService
{
public HttpClient _netclient { get; }
private static IOptions<ConnectionInfo> _config;
public DSDClient _dsdclient { get; }
private readonly IHttpContextAccessor _httpContextAccessor;
public BackupApiService(IOptions<ConnectionInfo> config, HttpClient netclient, DSDClient dsdclient, IHttpContextAccessor httpContextAccessor)
{
_config = config;
_httpContextAccessor = httpContextAccessor;
_dsdclient = dsdclient;
_netclient = netclient;
_netclient.Timeout = new TimeSpan(0, 2, 30);
_netclient.DefaultRequestHeaders.Clear();
_netclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", _config.Value.NetworkerConnectionString);
_netclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
public async Task<string> AdhocBackup(string ServerName)
{
....
}
BackupApiV1Controller
public class BackupApiV1Controller : ControllerBase
{
private readonly IBackupApiService _backupApiService;
public BackupApiV1Controller(IBackupApiService backupApiService)
{
_backupApiService = backupApiService;
}
[HttpPost]
[Route("StartDateBackup/")]
public IActionResult StartDateBackup([FromBody] ServerSchedule serverSchedule)
{
var resp = BackgroundJob.Schedule(() => _backupApiService.AdhocBackup(serverSchedule.Servername), serverSchedule.Date);
return Ok();
}
[HttpPost]
[Route("StartAdhocBackup/")]
public IActionResult StartAdhocBackup([FromBody] Server server)
{
var resp = BackgroundJob.Enqueue(() => _backupApiService.AdhocBackup(server.Servername));
return Ok(resp);
}

Related

AddHttpClient with HttpClientHandler, 401 unauthorized C#

I try to use service.AddHttpClient to create HttpClient and DI into my other services, but HttpClient need to be set credential informations.
Here is my step,
Startup.cs
services.AddHttpClient("ServiceOne", x => {})
.ConfigurePrimaryHttpMessageHandler(() =>
{ var handler = new HttpClientHandler(){
UseDefaultCredentials = true,
Credentials = new NetworkCredential("account", "password")
};
return handler;
});
2.ServiceOne.cs
public class ServiceOne : IService
{
private readonly IHttpClientFactory _clientFactory;
}
public ServiceOne (IHttpClientFactory clientFactory)
{
this._clientFactory = clientFactory;
}
public string SomeFunction ()
{
var request = new HttpRequestMessage(HttpMethod.Get,"http://uri");
var client = _clientFactory.CreateClient("ServiceOne");
var response = client.SendAsync(request).GetAwaiter().GetResult();
}
I received 401 Unauthorized, it seems the HttpClientHandler not be set correct.
Hope someone could help me this question, Thanks.

How to mock a client class executina a request in an integration test

In my microservice I have an endpoint AppointmentController which will get the appointments for a specified user. The controller calls the GetAppointmentsClient which retrieves the data from some other endpoint.
I am now creating IntegrationTests for AppointmentController but I'm having some issues mocking the GetAppointmentsClient. It is now actually executing a request which results in an exception.
The AppointmentControllerIT test class look like this:
public class MantelzorgerControllerIT : WebServerTest
{
private readonly CancellationToken _cancellationToken;
public MantelzorgerControllerIT ()
{
_cancellationToken = new CancellationTokenSource().Token;
}
[Fact]
public async Task Get_WhenAuthorized_ThenOk()
{
GetAppointmentsClientMock.Setup(mock =>
mock.Get(
It.IsAny<Guid>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<Appointment>());
var result = await Client.GetAsync(CreateMantelzorgerUrl(_spreekuurIdpId), _cancellationToken);
result.StatusCode.Should.Be(HttpStatusCode.OK);
}
}
public abstract class WebServerTest
{
internal readonly TestServer Server;
protected HttpClient Client { get; private set; }
protected Mock<IGetAppointmentsClient> GetAppointmentsClientMock { get; }
= new Mock<IGetAppointmentsClient>();
public WebServerTest()
{
Server = new TestServer(GetAppointmentsClientMock.Object);
Client = Server.CreateClient();
}
}
internal class TestServer : WebApplicationFactory<Program>
{
private readonly object[] _dependencyOverrides;
public TestServer(params object[] dependencyOverrides)
{
_dependencyOverrides = dependencyOverrides;
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseEnvironment("Development")
.ConfigureAppConfiguration((context, configBuilder) =>
{
// Placeholder: replace configuration for test configuration.
configBuilder.SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "appsettings.json"),
optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
})
.ConfigureTestServices(services =>
{
// Placeholder: replace services for test services.
services.AddAuthentication(TestAuthHandler.AuthenticationScheme)
.AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
TestAuthHandler.AuthenticationScheme, options => { });
AddAuthorizationForTest(services);
foreach (var dependency in _dependencyOverrides)
{
services.AddSingleton(dependency);
}
});
}
}
public class AppointmentController : Controller
{
private readonly IGetAppointmentsClient _appointmentsClient;
public AppointmentController(IGetAppointmentsClient appointmentsClient)
{
_appointmentsClient) = appointmentsClient;
}
public async Task<IActionResult> Get(Guid clientId, CancellationToken cancellationToken)
{
var appointments = await _appointmentsClient.Get(clientId, cancellationToken);
return Ok(appointments);
}
}
So I attempt to register GetAppointmentsClientMock through _dependencyOverrides and then mock the Get method but I'm probably doing something wrong.
What is the correct way to do this?
EDIT: Added the code of the AppointmentController;

Skip JWT Auth during Tests ASP.Net Core 3.1 Web Api

I a have a very simple app with one JWT authenticated controller:
[ApiController]
[Authorize]
[Route("[controller]")]
public class JwtController : ControllerBase
{
public JwtController() { }
[HttpGet]
public ActionResult Get() => Ok("Working!");
}
With the authentication configured as:
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false
};
});
During tests, i want the user to be "authenticated" all the time so that [Authorize] would be skipped.
[Fact]
public async Task JwtIsSkipped()
{
var response = (await _Client.GetAsync("/jwt")).EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
Assert.Equal("Working!", stringResponse);
}
Running the test like this will fail, so following this doc I added this simple auth handler:
public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public const string DefaultScheme = "Test";
public TestAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var claims = new[] { new Claim(ClaimTypes.Name, "Test user") };
var identity = new ClaimsIdentity(claims, DefaultScheme);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, DefaultScheme);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
}
So now my test class looks like this:
public class UnitTest : IClassFixture<WebApplicationFactory<Startup>>
{
private readonly WebApplicationFactory<Startup> _Factory;
private readonly HttpClient _Client;
public UnitTest(WebApplicationFactory<Startup> factory)
{
_Factory = factory;
_Client = _Factory.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddAuthentication(TestAuthHandler.DefaultScheme)
.AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
TestAuthHandler.DefaultScheme, options => { });
});
}).CreateClient();
_Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(TestAuthHandler.DefaultScheme);
}
[Fact]
public async Task JwtIsSkipped()
{
var response = (await _Client.GetAsync("/jwt")).EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
Assert.Equal("Working!", stringResponse);
}
}
And it still fails, I have no idea what I'm doing wrong.
I have had a similar situation previously with the Microsoft example and can promise you it can give headaches, it may work on specific Core versions, but I have given up. I have solved this way.
My goal was, is to Authorize the system while testing, instead of using AddAuthentication in our test we create a FakePolicyEvaluator class and add it as a singleton to our test.
So let's go to our FakePolicyEvaluator class:
public class FakePolicyEvaluator : IPolicyEvaluator
{
public virtual async Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context)
{
var principal = new ClaimsPrincipal();
principal.AddIdentity(new ClaimsIdentity(new[] {
new Claim("Permission", "CanViewPage"),
new Claim("Manager", "yes"),
new Claim(ClaimTypes.Role, "Administrator"),
new Claim(ClaimTypes.NameIdentifier, "John")
}, "FakeScheme"));
return await Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(principal,
new AuthenticationProperties(), "FakeScheme")));
}
public virtual async Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy,
AuthenticateResult authenticationResult, HttpContext context, object resource)
{
return await Task.FromResult(PolicyAuthorizationResult.Success());
}
}
Then in our ConfigureTestServices we added services.AddSingleton<IPolicyEvaluator, FakePolicyEvaluator>();
So in your test code like this:
private readonly HttpClient _client;
public UnitTest(WebApplicationFactory<Startup> factory)
{
_client = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddSingleton<IPolicyEvaluator, FakePolicyEvaluator>();
});
}).CreateClient();
}
[Fact]
public async Task JwtIsSkipped()
{
var response = (await _client.GetAsync("/jwt")).EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
Assert.Equal("Working!", stringResponse);
}
That is it. Now when you test, it will bypass authentication. I have tested it with the provided controller and it works.
It is also possible to place the fake inside the application startup, and it will be both testable for test and working under a development environment. Check the referenced article.
Disclaimer: I have written in more depth article about this on my personal website Reference where you can find and download a source code from GitHub.
You need to set DefaultAuthenticateScheme
builder.ConfigureTestServices(services =>
{
services.AddAuthentication(options =>
{
x.DefaultAuthenticateScheme = TestAuthHandler.DefaultScheme;
x.DefaultScheme = TestAuthHandler.DefaultScheme;
}).AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
TestAuthHandler.DefaultScheme, options => { });
});
its a small change to maysam fahmi answer that HttpContext.User also have values:
public class FakeUserPolicyEvaluator: IPolicyEvaluator
{
private ClaimsIdentity _claimsIdentity;
public virtual async Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context)
{
var testScheme = "FakeScheme";
var principal = new ClaimsPrincipal();
_claimsIdentity = new ClaimsIdentity(new[]
{
new Claim("sub", "a5"),
new Claim("client_id", "a6"),
new Claim(ClaimTypes.Role, BackmanConsts.Authorization.ClientPolicy),
}, testScheme);
principal.AddIdentity(_claimsIdentity);
return await Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(principal,
new AuthenticationProperties(), testScheme)));
}
public virtual async Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy,
AuthenticateResult authenticationResult, HttpContext context, object resource)
{
context.User = new ClaimsPrincipal(_claimsIdentity);
return await Task.FromResult(PolicyAuthorizationResult.Success());
}
}

Can I avoid using BuildServiceProvider in startup?

Can I avoid using BuildServiceProvider in ExpireToken function below? In other words...
Is there a way of avoiding BuildServiceProvider() in Startup.cs when using JWTBearer OnAuthenticationFailed event?
(Using Web API aspnet Core 3.0)
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
//some code removed..
//Authentication
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents()
{
OnAuthenticationFailed = context =>
{
if (context.Exception.Message.Contains("The token is expired"))
{
Microsoft.Extensions.Primitives.StringValues token = string.Empty;
var logger = new LoggerManager();
logger.LogError("Token expired"); //confirming that the token expired so authentication failed
var header = context.Request.Headers;
if (!header.TryGetValue("Authorization", out token))
logger.LogError("no token found");
else
token = token.ToString().Substring("Bearer ".Length).Trim();
ExpireToken(services, token);
}
return Task.CompletedTask;
}
};
});
}
public void ExpireToken(IServiceCollection services, string tokenId)
{
var sp = services.BuildServiceProvider();
var jwtManager = sp.GetService<IJWTAuthenticationManager>();
jwtManager.ExpireToken(tokenId);
}
jwtAuthentication Manager will publish this event so that subscribers are notified (c#:event + delegates)
public delegate void TokenExpiredEventHandler(object source, TokenExpiredEventArgs args);
JWTAuthenticationManager.cs
public class JWTAuthenticationManager : IJWTAuthenticationManager
{
private readonly string _tokenKey;
public event TokenExpiredEventHandler TokenExpired;
//some code removed for brevity
public void ExpireToken(string tokenId)
{
OnTokenExpired(tokenId); //notify all subscribers
}
protected virtual void OnTokenExpired(string token) {
TokenExpired?.Invoke(this, new TokenExpiredEventArgs(token));
}
}
public class TokenExpiredEventArgs : EventArgs
{
public string token;
public TokenExpiredEventArgs(string tokenId)
{
token = tokenId;
}
}
Program.cs
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
var jwtMgr = host.Services.GetRequiredService<IJWTAuthenticationManager>();
var cacheMgr = host.Services.GetRequiredService<ICacheManager>();
jwtMgr.TokenExpired += cacheMgr.OnTokenExpired;
host.Run();
}
Why not resolve services from context? Simply change your ExpireToken method to take an IServiceProvider:
public void ExpireToken(IServiceProvider services, string tokenId)
{
var jwtManager = services.GetService<IJWTAuthenticationManager>();
jwtManager.ExpireToken(tokenId);
}
And then pass context.HttpContext.RequestServices:
ExpireToken(context.HttpContext.RequestServices, token);

Building an integration test for an AspNetCore API that uses IdentityServer 4 for Auth

I've built a simple AspNetCore 2.2 API that uses IdentityServer 4 to handle OAuth. It's working fine but I'd now like to add integration tests and recently discovered this. I used it to build some tests which all worked fine - as long as I didn't have the [Authorize] attribute on my controllers - but obviously that attribute needs to be there.
I came across this stackoverflow question and from the answers given there I tried to put a test together but I'm still getting an Unauthorized response when I try to run tests.
Please note: I really don't know what details I should be using when I'm creating the client.
What should the allowed scopes be? (Should they match the real
scopes)
Also when building the IdentityServerWebHostBuilder
What should I pass to .AddApiResources? (Maybe a dumb question but
does it matter)
If anyone can guide me it would be greatly appreciated.
Here is my test:
[Fact]
public async Task Attempt_To_Test_InMemory_IdentityServer()
{
// Create a client
var clientConfiguration = new ClientConfiguration("MyClient", "MySecret");
var client = new Client
{
ClientId = clientConfiguration.Id,
ClientSecrets = new List<Secret>
{
new Secret(clientConfiguration.Secret.Sha256())
},
AllowedScopes = new[] { "api1" },
AllowedGrantTypes = new[] { GrantType.ClientCredentials },
AccessTokenType = AccessTokenType.Jwt,
AllowOfflineAccess = true
};
var webHostBuilder = new IdentityServerWebHostBuilder()
.AddClients(client)
.AddApiResources(new ApiResource("api1", "api1name"))
.CreateWebHostBuilder();
var identityServerProxy = new IdentityServerProxy(webHostBuilder);
var tokenResponse = await identityServerProxy.GetClientAccessTokenAsync(clientConfiguration, "api1");
// *****
// Note: creating an IdentityServerProxy above in order to get an access token
// causes the next line to throw an exception stating: WebHostBuilder allows creation only of a single instance of WebHost
// *****
// Create an auth server from the IdentityServerWebHostBuilder
HttpMessageHandler handler;
try
{
var fakeAuthServer = new TestServer(webHostBuilder);
handler = fakeAuthServer.CreateHandler();
}
catch (Exception e)
{
throw;
}
// Create an auth server from the IdentityServerWebHostBuilder
HttpMessageHandler handler;
try
{
var fakeAuthServer = new TestServer(webHostBuilder);
handler = fakeAuthServer.CreateHandler();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
// Set the BackChannelHandler of the 'production' IdentityServer to use the
// handler form the fakeAuthServer
Startup.BackChannelHandler = handler;
// Create the apiServer
var apiServer = new TestServer(new WebHostBuilder().UseStartup<Startup>());
var apiClient = apiServer.CreateClient();
apiClient.SetBearerToken(tokenResponse.AccessToken);
var user = new User
{
Username = "simonlomax#ekm.com",
Password = "Password-123"
};
var req = new HttpRequestMessage(new HttpMethod("GET"), "/api/users/login")
{
Content = new StringContent(JsonConvert.SerializeObject(user), Encoding.UTF8, "application/json"),
};
// Act
var response = await apiClient.SendAsync(req);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
My Startup class:
public class Startup
{
public IConfiguration Configuration { get; }
public static HttpMessageHandler BackChannelHandler { get; set; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
ConfigureAuth(services);
services.AddTransient<IPassportService, PassportService>();
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
}
protected virtual void ConfigureAuth(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = Configuration.GetValue<string>("IdentityServerAuthority");
options.Audience = Configuration.GetValue<string>("IdentityServerAudience");
options.BackchannelHttpHandler = BackChannelHandler;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseAuthentication();
app.UseHttpsRedirection();
app.UseMvc();
app.UseExceptionMiddleware();
}
}
Edit:
The below suggestion was one problem. The original source-code failed due to an exception by trying to build WebHostBuilder twice. Secondly the configuration-file was only present in the API project, not in the test-project, thats why authority wasn't set as well.
Instead of doing this
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = Configuration.GetValue<string>("IdentityServerAuthority");
options.Audience = Configuration.GetValue<string>("IdentityServerAudience");
options.BackchannelHttpHandler = BackChannelHandler;
});
You have to do something like this:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = Configuration.GetValue<string>("IdentityServerAuthority");
options.JwtBackChannelHandler = BackChannelHandler;
});
You can find a sample here.
Hope that helps, worked for me!
A solution which doesn't affect production code:
public class TestApiWebApplicationFactory<TStartup>
: WebApplicationFactory<TStartup> where TStartup : class
{
private readonly HttpClient _identityServerClient;
public TestApiWebApplicationFactory(HttpClient identityServerClient)
{
_identityServerClient = identityServerClient;
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureServices(
s =>
{
s.AddSingleton<IConfigureOptions<JwtBearerOptions>>(services =>
{
return new TestJwtBearerOptions(_identityServerClient);
});
});
}
}
and its usage is:
_factory = new WebApplicationFactory<Startup>()
{
ClientOptions = {BaseAddress = new Uri("http://localhost:5000/")}
};
_apiFactory = new TestApiWebApplicationFactory<SampleApi.Startup>(_factory.CreateClient())
{
ClientOptions = {BaseAddress = new Uri("http://localhost:5001/")}
};
The TestJwtBearerOptions just proxies requests to identityServerClient. The implementation you can find here:
https://gist.github.com/ru-sh/048e155d73263912297f1de1539a2687
If you don't want to rely on a static variable to hold the HttpHandler, I've found the following to work. I think it's a lot cleaner.
First create an object that you can instantiate before your TestHost is created. This is because you won't have the HttpHandler until after the TestHost is created, so you need to use a wrapper.
public class TestHttpMessageHandler : DelegatingHandler
{
private ILogger _logger;
public TestHttpMessageHandler(ILogger logger)
{
_logger = logger;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
_logger.Information($"Sending HTTP message using TestHttpMessageHandler. Uri: '{request.RequestUri.ToString()}'");
if (WrappedMessageHandler == null) throw new Exception("You must set WrappedMessageHandler before TestHttpMessageHandler can be used.");
var method = typeof(HttpMessageHandler).GetMethod("SendAsync", BindingFlags.Instance | BindingFlags.NonPublic);
var result = method.Invoke(this.WrappedMessageHandler, new object[] { request, cancellationToken });
return await (Task<HttpResponseMessage>)result;
}
public HttpMessageHandler WrappedMessageHandler { get; set; }
}
Then
var testMessageHandler = new TestHttpMessageHandler(logger);
var webHostBuilder = new WebHostBuilder()
...
services.PostConfigureAll<JwtBearerOptions>(options =>
{
options.Audience = "http://localhost";
options.Authority = "http://localhost";
options.BackchannelHttpHandler = testMessageHandler;
});
...
var server = new TestServer(webHostBuilder);
var innerHttpMessageHandler = server.CreateHandler();
testMessageHandler.WrappedMessageHandler = innerHttpMessageHandler;

Categories

Resources