In my .Net Core 3.0 app I want to use the Microsoft Graph Nuget library. I have created a connection class that authenticates my application using [MSAL][1] and then creates the connection and returns this. My idea was to inject this connection object in the constructor using Dependency Injection. However, since the method that creates the connection is async, I seem to have a problem how to use it in the constructor.
My Connect Class
public class AuthorizeGraphApi: IAuthorizeGraphApi
{
private readonly IConfiguration _config;
public AuthorizeGraphApi(IConfiguration config)
{
_config = config;
}
public async Task<GraphServiceClient> ConnectToAAD()
{
string accessToken = await GetAccessTokenFromAuthorityAsync();
var graphServiceClient = new GraphServiceClient(new DelegateAuthenticationProvider((requestMessage) => {
requestMessage
.Headers
.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
return Task.FromResult(0);
}));
return graphServiceClient;
}
private async Task<string> GetAccessTokenFromAuthorityAsync()
{
// clientid, authUri, etc removed for this example.
IConfidentialClientApplication _conn;
_conn = ConfidentialClientApplicationBuilder.Create(clientId)
.WithClientSecret(clientSecret)
.WithAuthority(new Uri(authUri))
.Build();
string[] scopes = new string[] { $"api://{clientId}/.default" };
AuthenticationResult result = null;
// AcquireTokenForClient only has async method.
result = await _conn.AcquireTokenForClient(scopes)
.ExecuteAsync();
return result.AccessToken;
}
}
My Graph Service to send requests
public class AzureIntuneService
{
private readonly IAuthorizeGraphApi _graphClient;
public AzureIntuneService(IAuthorizeGraphApi client)
{
//Gives: cannot implicitely convert to Threading.Tasks.Task.... error
_graphClient = client.ConnectToAAD();
}
public async Task<IList<string>> GetAADInformationAsync()
{
// then here, use the graphClient object for the request...
var payload = await _graphClient.Groups.Request().GetAsync();
return payload
}
}
I register the above classess in my startup as follows:
services.AddScoped<IAuthorizeGraphApi, AuthorizeGraphApi>();
The idea was that this way, I don't need to call the _graphClient in each method. How can I inject the connection object in a correct way? Or what are the best practices regarding this (injecting connection objects)?
One way would be to store a reference to the Task and make sure any public methods that use the connection are async:
public class AzureIntuneService
{
private readonly Task<GraphServiceClient> _graphClientTask;
public AzureIntuneService(IAuthorizeGraphApi client)
{
_graphClientTask = client.ConnectToAAD();
}
public async Task<IList<string>> GetAADInformationAsync()
{
var client = await _graphClientTask; // Get the client when connected
var payload = await client.Groups.Request().GetAsync();
return payload;
}
}
Constructors aren't async and should never be used to initialize anything async. The only way to workaround it is to do sync-over-async by doing a .Result which is always a problem.
In your case, the GraphServiceClient that takes in DelegateAuthenticationProvider, accepts an AuthenticateRequestAsyncDelegate. This allows you to have an async delegate to construct the client.
So now you can do
new DelegateAuthenticationProvider(async requestMessage =>
{
string accessToken = await GetAccessTokenFromAuthorityAsync();
//rest of code here
}
)
and this allows you to change your ConnectToAAD signature to just return a GraphServiceClient and not a Task<GraphServiceClient>.
When you need async data you have to look away from the regular constructor and create a factory method (private static function). Something like below:
public sealed class MyClass
{
private MyData asyncData;
private MyClass() { ... }
private async Task<MyClass> InitializeAsync()
{
asyncData = await GetDataAsync();
return this;
}
public static Task<MyClass> CreateAsync()
{
var ret = new MyClass();
return ret.InitializeAsync();
}
}
public static async Task UseMyClassAsync()
{
MyClass instance = await MyClass.CreateAsync();
...
}
More here: https://blog.stephencleary.com/2013/01/async-oop-2-constructors.html
Related
I've got some code that calls HttpClient's GetFromJsonAsync however I'm struggling to mock the method call and was wondering how can I do this?
C# code:
public class Client : IClient
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly HttpClient _httpClient;
public Client(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
_httpClient = _httpClientFactory.CreateClient();
}
public async Task<List<ApiResponse>> GetData()
{
try
{
return await _httpClient.GetFromJsonAsync<ApiResponse>("endpointUrl"); // How to mock?
}
catch (Exception e)
{
throw;
}
return null;
}
}
I've seen previous posts that suggest I should mock HttpMessageHandler but how do I mock the response back from the GetFromJsonAsync method call?
As per one of the suggested answers, I've done the following:
var httpClientMock = new Mock<HttpClient>();
httpClientMock.Setup(x => x.GetFromJsonAsync<ApiResponse>(It.IsAny<string>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new ApiResponse());
_httpClientFactoryMock = new Mock<IHttpClientFactory>();
_httpClientFactoryMock.Setup(x => x.CreateClient(It.IsAny<string>())).Returns(httpClientMock.Object);
However I receive the following error:
Message "Unsupported expression: x => x.GetFromJsonAsync<DataLookupResponse>(It.IsAny<string>(), It.IsAny<CancellationToken>())\nExtension methods (here: HttpClientJsonExtensions.GetFromJsonAsync) may not be used in setup / verification expressions."
If you create a mock of HttpClient you can then return this when calling _httpClientFactory.CreateClient();.
Something like this (haven't tested this code in my IDE so be aware of any typo's)
var httpClientMock = new Mock<HttpClient>();
httpClientMock.Setup(x => x.GetFromJsonAsync<ApiResponse>("endpointurl").Returns(...); httpClientFactoryMock.Setup(x => x.CreateClient()).Returns(httpClientMock.Object);
Recently I've been unit testing my HttpClients and I had to solve the same problem as you.
Modify where needed. I'm using the IConfiguration to retrieve some application settings. The code to mock this has also been included in the code you can find below.
The call in the test is a mocked call. You don't need an internet connection for this call to succeed. You can specify any endpoint and call it with any configured response.
This means can return anything you want in your mocked call and use fake endpoint in order to not expose any sensitive data in your code.
Install the following NuGet Packages in your test project in order for my solution to work:
<PackageReference Include="Moq" Version="4.18.1" />
<PackageReference Include="RichardSzalay.MockHttp" Version="6.0.0" />
My HttpClient:
public class MyHttpClient : IMyHttpClient
{
private readonly IConfiguration _configuration;
private readonly IHttpClientFactory _httpClientFactory;
public MyHttpClient(IConfiguration configuration, IHttpClientFactory httpClientFactory)
{
_configuration = configuration;
_httpClientFactory = httpClientFactory;
}
public async Task<SomeType> GetSomeInformationAsync()
{
var token = await FetchAccessToken();
var client = CreateHttpClient(token);
var endpoint = _configuration.GetValue<string>("Endpoints:SomeEndpoint");
var response = client.GetAsync(endpoint);
var content = await response.Result.Content.ReadAsStringAsync();
return content;
}
private HttpClient CreateHttpClient(string accessToken)
{
var client = _httpClientFactory.CreateClient(nameof(MyHttpClient));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
return client;
}
}
My TestClass:
public class MyHttpClientTests
{
private readonly MyHttpClient _sut;
private readonly Mock<IConfiguration> _configurationMock = new();
private readonly Mock<IHttpClientFactory> _httpClientFactoryMock = new();
private readonly MockHttpMessageHandler _httpMessageHandlerMock = new();
public MyHttpClientTests()
{
_sut = new MyHttpClient(_configurationMock.Object, _httpClientFactoryMock.Object);
}
[Fact]
public async void GetSomeInformationTest_ShouldReturnSomething()
{
// Since you are mocking you don't need the real endpoint.
var endpoint = "/someNotExistingEndpoint/";
var getSomeInformationValue = new Mock<IConfigurationSection>();
getSomeInformationValue.Setup(x => x.Value).Returns(endpoint);
// When I retrieve my configuration in my mocked HttpClient from 'Endpoints:SomeEndpoint' it will return the value '/someNotExistingEndpoint/'
_configurationMock.Setup(x => x.GetSection(It.Is<string>(x => x == "Endpoints:SomeEndpoint"))).Returns(getSomeInformationValue.Object);
// When the above endpoint is called I can respond with anything I want. In this case an StatusCode of OK and some JsonContent (application/json)).
_httpMessageHandlerMock.When(endpoint).Respond(HttpStatusCode.OK, JsonContent.Create(new { Message = "thisIsSomeJsonResponse" }));
_httpClientFactoryMock.Setup(x => x.CreateClient(nameof(MyHttpClient)))
.Returns(new HttpClient(_httpMessageHandlerMock)
{
BaseAddress = new Uri("someBaseAdress")
});
var result = await _sut.GetSomeInformationAsync();
// You can put your assertions here
}
}
I am trying to write a Blazor app that uses client secret credentials to get an access token for the API. I wanted to encapsulate it in such a way that it handles the token fetching and refreshing behind the scenes. To achieve this, I created the following inherited class which uses IdentityModel Nuget package:
public class MPSHttpClient : HttpClient
{
private readonly IConfiguration Configuration;
private readonly TokenProvider Tokens;
private readonly ILogger Logger;
public MPSHttpClient(IConfiguration configuration, TokenProvider tokens, ILogger logger)
{
Configuration = configuration;
Tokens = tokens;
Logger = logger;
}
public async Task<bool> RefreshTokens()
{
if (Tokens.RefreshToken == null)
return false;
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync(Configuration["Settings:Authority"]);
if (disco.IsError) throw new Exception(disco.Error);
var result = await client.RequestRefreshTokenAsync(new RefreshTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = Configuration["Settings:ClientID"],
RefreshToken = Tokens.RefreshToken
});
Logger.LogInformation("Refresh Token Result {0}", result.IsError);
if (result.IsError)
{
Logger.LogError("Error: {0)", result.ErrorDescription);
return false;
}
Tokens.RefreshToken = result.RefreshToken;
Tokens.AccessToken = result.AccessToken;
Logger.LogInformation("Access Token: {0}", result.AccessToken);
Logger.LogInformation("Refresh Token: {0}" , result.RefreshToken);
return true;
}
public async Task<bool> CheckTokens()
{
if (await RefreshTokens())
return true;
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync(Configuration["Settings:Authority"]);
if (disco.IsError) throw new Exception(disco.Error);
var result = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = Configuration["Settings:ClientID"],
ClientSecret = Configuration["Settings:ClientSecret"]
});
if (result.IsError)
{
//Log("Error: " + result.Error);
return false;
}
Tokens.AccessToken = result.AccessToken;
Tokens.RefreshToken = result.RefreshToken;
return true;
}
public new async Task<HttpResponseMessage> GetAsync(string requestUri)
{
DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", Tokens.AccessToken);
var response = await base.GetAsync(requestUri);
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
if (await CheckTokens())
{
DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", Tokens.AccessToken);
response = await base.GetAsync(requestUri);
}
}
return response;
}
}
The idea is to keep from having to write a bunch of redundant code to try the API, then request/refresh the token if you are unauthorized. I tried it at first using extension methods to HttpClient, but there was no good way to inject the Configuration into a static class.
So my Service code is written as this:
public interface IEngineListService
{
Task<IEnumerable<EngineList>> GetEngineList();
}
public class EngineListService : IEngineListService
{
private readonly MPSHttpClient _httpClient;
public EngineListService(MPSHttpClient httpClient)
{
_httpClient = httpClient;
}
async Task<IEnumerable<EngineList>> IEngineListService.GetEngineList()
{
return await JsonSerializer.DeserializeAsync<IEnumerable<EngineList>>
(await _httpClient.GetStreamAsync($"api/EngineLists"), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
}
}
Everything compiles great. In my Startup, I have the following code:
services.AddScoped<TokenProvider>();
services.AddHttpClient<IEngineListService, EngineListService>(client =>
{
client.BaseAddress = new Uri(Configuration["Settings:ApiAddress"]);
});
Just to be complete, Token Provider looks like this:
public class TokenProvider
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
}
When I run the App, it complains that it can't find a suitable constructor for EngineListService in the call to services.AddHttpClient. Is there a way to pass AddHttpClient an actual instance of the IEngineListService. Any other way I might be able to achieve this?
Thanks,
Jim
I think that EngineListService should not be registered as a HttpClient in services and instead you should register MPSHttpClient.
This follows the "Typed Client" example in the documentation and uses IHttpClientFactory behind the scenes.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests#typed-clients
When you use services.AddHttpClient the constructor needs a HttpClient parameter. That is how the HttpClientFactory initializes the HttpClient and then passes it into your service ready to go.
You can change your MPSHttpClient to not inherit HttpClient and instead add a HttpClient parameter to the constructor. You could also have it implement an interface like IMPSHttpClient
public class MPSHttpClient
{
public MPSHttpClient(HttpClient httpClient, IConfiguration configuration, TokenProvider tokens, ILogger logger)
{
HttpClient = httpClient;
Configuration = configuration;
Tokens = tokens;
Logger = logger;
}
}
You must remove these lines from MPSHttpClient and use the injected client.
// remove this
var client = new HttpClient();
In Startup add
services.AddHttpClient<MPSHttpClient>(client =>
{
// add any configuration
client.BaseAddress = new Uri(Configuration["Settings:ApiAddress"]);
});
Change EngineListService to a normal service registration as it is not a HttpClient
services.AddScoped<IEngineListService, EngineListService>()
Special thanks to #pinkfloydx33 for helping me solve this. This link that he shared https://blog.joaograssi.com/typed-httpclient-with-messagehandler-getting-accesstokens-from-identityserver/ was everything I needed. The trick was that there exists a class called DelegatingHandler that you can inherit and override the OnSendAsync method and do all of your token-checking there before sending it to the final HttpHandler. So my new MPSHttpClient class is as so:
public class MPSHttpClient : DelegatingHandler
{
private readonly IConfiguration Configuration;
private readonly TokenProvider Tokens;
private readonly ILogger<MPSHttpClient> Logger;
private readonly HttpClient client;
public MPSHttpClient(HttpClient httpClient, IConfiguration configuration, TokenProvider tokens, ILogger<MPSHttpClient> logger)
{
Configuration = configuration;
Tokens = tokens;
Logger = logger;
client = httpClient;
}
public async Task<bool> CheckTokens()
{
var disco = await client.GetDiscoveryDocumentAsync(Configuration["Settings:Authority"]);
if (disco.IsError) throw new Exception(disco.Error);
var result = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = Configuration["Settings:ClientID"],
ClientSecret = Configuration["Settings:ClientSecret"]
});
if (result.IsError)
{
//Log("Error: " + result.Error);
return false;
}
Tokens.AccessToken = result.AccessToken;
Tokens.RefreshToken = result.RefreshToken;
return true;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.SetBearerToken(Tokens.AccessToken);
var response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
if (await CheckTokens())
{
request.SetBearerToken(Tokens.AccessToken);
response = await base.SendAsync(request, cancellationToken);
}
}
return response;
}
}
The big changes here are the inheritance and I used DI to obtain the HttpClient much like #Rosco mentioned. I had tried to override OnGetAsync in my original version. When inheriting from DelegatingHandler, all you have to override is OnSendAsync. This will handle all of your get, put, post, and deletes from your HttpContext all in one method.
My EngineList Service is written as if there were no tokens to be considered, which was my original goal:
public interface IEngineListService
{
Task<IEnumerable<EngineList>> GetEngineList();
}
public class EngineListService : IEngineListService
{
private readonly HttpClient _httpClient;
public EngineListService(HttpClient httpClient)
{
_httpClient = httpClient;
}
async Task<IEnumerable<EngineList>> IEngineListService.GetEngineList()
{
return await JsonSerializer.DeserializeAsync<IEnumerable<EngineList>>
(await _httpClient.GetStreamAsync($"api/EngineLists"), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
}
}
The Token Provider stayed the same. I plan to add expirations and such to it, but it works as is:
public class TokenProvider
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
}
The ConfigureServices code changed just a bit:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<TokenProvider>();
services.AddTransient<MPSHttpClient>();
services.AddHttpClient<IEngineListService, EngineListService>(client =>
{
client.BaseAddress = new Uri(Configuration["Settings:ApiAddress"]);
}).AddHttpMessageHandler<MPSHttpClient>();
...
}
You instantiate MPSHttpClient as Transient, then reference it with the AddHttpMessageHandler call attached to the AddHttpClient call. I know this is different than how others implement HttpClients, but I learned this method of creating client services from a Pluralsight video and have been using it for everything. I create a separate Service for each entity in the database. If say I wanted to do tires, I would add the following to ConfigureServices:
services.AddHttpClient<ITireListService, TireListService>(client =>
{
client.BaseAddress = new Uri(Configuration["Settings:ApiAddress"]);
}).AddHttpMessageHandler<MPSHttpClient>();
It will use the same DelegatingHandler so I can just keep adding services for each entity type while no longer worrying about tokens. Thanks to everyone that responded.
Thanks,
Jim
I'm getting the following exception when trying to setup the moq for an IConfidentialClientApplication:
System.NotSupportedException : Unsupported expression: ... =>
....ExecuteAsync() Non-overridable members (here:
AbstractAcquireTokenParameterBuilder.ExecuteAsync)
may not be used in setup / verification expressions.
private Mock<IConfidentialClientApplication> _appMock = new Mock<IConfidentialClientApplication>();
[Fact]
public async Task GetAccessTokenResultAsync_WithGoodSetup_ReturnsToken()
{
// Leverages MSAL AuthenticationResult constructor meant for mocks in test
var authentication = CreateAuthenticationResult();
// EXCEPTION THROWN HERE
_appMock.Setup(_ => _.AcquireTokenForClient(It.IsAny<string[]>()).ExecuteAsync())
.ReturnsAsync(authentication);
... rest of test ...
}
An AcquireTokenForClientParameterBuilder is returned by _.AcquireTokenForClient; "a builder enabling you to add optional parameters before executing the token request". This is a sealed class, so I can't easily mock this tricky object.
For those curious, CreateAuthenticationResult() is a method that invokes a signature from Microsoft.Identity.Client.AuthenticationResult that was specifically added in by Microsoft for stubbing an AuthenticationResult, as it cannot be mocked since it too is a sealed class.
https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/682
Seeing that AcquireTokenForClientParameterBuilder is provided through an external library, you obviously aren't able to modify it to be more testable. Given that, I'd suggest abstracting that code behind your own interface (kind of applying the Adapter pattern for testing purposes).
Take the following service/test as an example of how you'd currently be using the IConfidentialClientApplication and trying to mock it (which results in the same error you're seeing):
public class MyService
{
private readonly IConfidentialClientApplication _confidentialClientApplication;
public MyService(IConfidentialClientApplication confidentialClientApplication)
{
_confidentialClientApplication = confidentialClientApplication;
}
public async Task<string> GetAccessToken(IEnumerable<string> scopes)
{
AcquireTokenForClientParameterBuilder tokenBuilder = _confidentialClientApplication.AcquireTokenForClient(scopes);
AuthenticationResult token = await tokenBuilder.ExecuteAsync();
return token.AccessToken;
}
}
public class UnitTest1
{
[Fact]
public async Task Test1()
{
Mock<IConfidentialClientApplication> _appMock = new Mock<IConfidentialClientApplication>();
AuthenticationResult authentication = CreateAuthenticationResult("myToken");
_appMock
.Setup(_ => _.AcquireTokenForClient(It.IsAny<string[]>()).ExecuteAsync())
.ReturnsAsync(authentication);
var myService = new MyService(_appMock.Object);
string accessToken = await myService.GetAccessToken(new string[] { });
Assert.Equal("myToken", accessToken);
}
private AuthenticationResult CreateAuthenticationResult(string accessToken) =>
new AuthenticationResult(accessToken, true, null, DateTimeOffset.Now, DateTimeOffset.Now, string.Empty, null, null, null, Guid.Empty);
}
By introducing a separate interface your code can the simply depend on that, putting you in control of how it will be used/tested:
public interface IIdentityClientAdapter
{
Task<string> GetAccessToken(IEnumerable<string> scopes);
}
public class IdentityClientAdapter : IIdentityClientAdapter
{
private readonly IConfidentialClientApplication _confidentialClientApplication;
public IdentityClientAdapter(IConfidentialClientApplication confidentialClientApplication)
{
_confidentialClientApplication = confidentialClientApplication;
}
public async Task<string> GetAccessToken(IEnumerable<string> scopes)
{
AcquireTokenForClientParameterBuilder tokenBuilder = _confidentialClientApplication.AcquireTokenForClient(scopes);
AuthenticationResult token = await tokenBuilder.ExecuteAsync();
return token.AccessToken;
}
}
public class MyService
{
private readonly IIdentityClientAdapter _identityClientAdapter;
public MyService(IIdentityClientAdapter identityClientAdapter)
{
_identityClientAdapter = identityClientAdapter;
}
public async Task<string> GetAccessToken(IEnumerable<string> scopes)
{
return await _identityClientAdapter.GetAccessToken(scopes);
}
}
public class UnitTest1
{
[Fact]
public async Task Test1()
{
Mock<IIdentityClientAdapter> _appMock = new Mock<IIdentityClientAdapter>();
_appMock
.Setup(_ => _.GetAccessToken(It.IsAny<string[]>()))
.ReturnsAsync("myToken");
var myService = new MyService(_appMock.Object);
string accessToken = await myService.GetAccessToken(new string[] { });
Assert.Equal("myToken", accessToken);
}
}
This example is obviously trivialized, but should still apply. The interface would just need to be fit to your needs.
I am having issues with testing Login Controller using IdentityServer4. It throws the following error:
{System.Net.Http.WinHttpException (0x80072EFD): A connection with the server could not be established
I am trying to generate the access Token using ResourceOwnerPassword, for which I have implemented IResourceOwnerPasswordValidator. I get the error in UserAccessToken.cs class when I call the RequestResourcePasswordAsync.
I am pretty sure it is because of the handler. Because if I use a handler in my test class and call the TokenClient with that handler I do get access Token but then I cannot test my Login Controller.
LoginController.cs
[HttpPost]
public async Task<IActionResult> Login([FromBody]LoginViewModel user)
{
var accessToken = await UserAccessToken.GenerateTokenAsync(user.Username, user.Password);
var loginToken = JsonConvert.DeserializeObject(accessToken);
return Ok(loginToken);
}
UserAccessToken.cs
public async Task<string> GenerateTokenAsync(string username, string password)
{
var tokenUrl = "http://localhost:5000/connect/token";
var tokenClient = new TokenClient(tokenUrl,"ClientId","ClientPassword");
var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync(username, password, SecurityConfig.PublicApiResourceId);
if (tokenResponse.IsError)
{
throw new AuthenticationFailedException(tokenResponse.Error);
}
return tokenResponse.Json.ToString();
}
TestClass.cs
[Fact]
public async Task Login()
{
var client = _identityServer.CreateClient();
var data = new StringContent(JsonConvert.SerializeObject(new LoginViewModel { Username = "1206", Password = "5m{F?Hk92/Qj}n7Lp6" }), Encoding.UTF8, "application/json");
var dd = await client.PostAsync("http://localhost:5000/login", data);
var ss = dd;
}
IdentityServerSetup.cs //Integration Test Setup
public class IdentityServerSetup
{
private TestServer _identityServer;
private const string TokenEndpoint = "http://localhost:5000/connect/token";
public HttpMessageHandler _handler;
//IF I use this code I do get a AccessToken
public async Task<string> GetAccessTokenForUser(string userName, string password, string clientId, string clientSecret, string apiName = "integrapay.api.public")
{
var client = new TokenClient(TokenEndpoint, clientId, clientSecret, innerHttpMessageHandler: _handler);
var response = await client.RequestResourceOwnerPasswordAsync(userName, password, apiName);
return response.AccessToken;
}
}
Well, you have already answered the question yourself: The problem is with the HttpHandler the TokenClient uses. It should use the one provided by the TestServer to successfully communicate with it instead of doing actual requests to localhost.
Right now, UserAccessToken requires a TokenClient. This is a dependency of your class, so you should refactor the code to pass in a TokenClient instead of generating it yourself. This pattern is called Dependency Injection and is ideal for cases like yours, where you might have different requirements in your tests than in your production setup.
You could make the code look like this:
UserAccessToken.cs
public class UserAccessToken
{
private readonly TokenClient _tokenClient;
public UserAccessToken(TokenClient tokenClient)
{
_tokenClient = tokenClient;
}
public async Task<string> GenerateTokenAsync(string username, string password)
{
var tokenUrl = "http://localhost:5000/connect/token";
var tokenResponse = await _tokenClient.RequestResourceOwnerPasswordAsync(username, password, SecurityConfig.PublicApiResourceId);
if (tokenResponse.IsError)
{
throw new AuthenticationFailedException(tokenResponse.Error);
}
return tokenResponse.Json.ToString();
}
}
TestHelpers.cs
public static class TestHelpers
{
private static TestServer _testServer;
private static readonly object _initializationLock = new object();
public static TestServer GetTestServer()
{
if (_testServer == null)
{
InitializeTestServer();
}
return _testServer;
}
private static void InitializeTestServer()
{
lock (_initializationLock)
{
if (_testServer != null)
{
return;
}
var webHostBuilder = new WebHostBuilder()
.UseStartup<IntegrationTestsStartup>();
var testServer = new TestServer(webHostBuilder);
var initializationTask = InitializeDatabase(testServer);
initializationTask.ConfigureAwait(false);
initializationTask.Wait();
testServer.BaseAddress = new Uri("http://localhost");
_testServer = testServer;
}
}
}
IntegrationTestsStartup.cs
public class IntegrationTestsStartup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<TokenClient>(() =>
{
var handler = TestUtilities.GetTestServer().CreateHandler();
var client = new TokenClient(TokenEndpoint, clientId, clientSecret, innerHttpMessageHandler: handler);
return client;
};
services.AddTransient<UserAccessToken>();
}
}
LoginController.cs
public class LoginController : Controller
{
private readonly UserAccessToken _userAccessToken;
public LoginController(UserAccessToken userAccessToken)
{
_userAccessToken = userAccessToken;
}
[HttpPost]
public async Task<IActionResult> Login([FromBody]LoginViewModel user)
{
var accessToken = await _userAccessToken .GenerateTokenAsync(user.Username, user.Password);
var loginToken = JsonConvert.DeserializeObject(accessToken);
return Ok(loginToken);
}
}
Here's one of my GitHub projects that makes use of the TestServer class and shows how I'm using it. It's not using IdentityServer4, though.
I have X controllers that use a API site (WebApi). I have created an ApiHelper class. Which I use in these controllers. Now my question is this. Can I make this ApiHelper a static class? I think I can because the httpClient is instanced. Or do I overlook something, and does it need to be an instanced ApiHelper. (the use of static still confuses me sometimes). Example code below.
public class HomeController : Controller
{
public async Task<string> VersionDemo()
{
var response = await ApiHelper.Call("/api/config/version");
var data = response.Content.ReadAsStringAsync();
var res = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(data.Result);
return res;
}
}
public class ConfigController : Controller
{
private async Task<List<ConfigSetting>> GetGeneralConfigurationDemo()
{
var generalConfiguration = new List<ConfigSetting>();
var response = await ApiHelper.Call("api/configuration/GetGeneralConfiguration");
var data = response.Content.ReadAsStringAsync();
generalConfiguration = JsonConvert.DeserializeObject<List<ConfigSetting>>(data.Result);
return generalConfiguration;
}
}
public static class ApiHelper
{
public static async Task<HttpResponseMessage> Call(string url)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
var baseAdress = System.Configuration.ConfigurationManager.AppSettings["ApiBaseAddress"];
string apiUrl = baseAdress + url;
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri(apiUrl);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.GetAsync(apiUrl);
return response;
}
}
}
Make base controller and hide http client as protected thing.
public abstract class BaseController : Controller
{
protected ApiHelper Api { get; set; }
}
Then derive your controllers from BaseController
public class ConfigController : BaseController {}
public class HomeController : BaseController {}
Note : try not to use static classes cause they make your heap littered. They are allocated in "high-frequency" heap, which is never garbage collected.
There would be no problem to leave your class static as the HttpClient stays on the method scope and thus each call to your static method will use a different HttpClient. It would not be safe if you used a static member (field or property) as it would be shared by all the callers and you would need to synchronize the access (for a multi thread usage).
After reading (httpClient your are doing it wrong , singleton pattern) and subsequently testing. I ended up using the following code. Main goal is one httpClient application wide and avoid socket exhaustion.
In my controllers where I'm in need of a httpClient I use the HttpClientSingleton.Instance see below.
And here is a BaseController you can inherit from in your controllers that are going to use your API.
public class BaseController : Controller
{
public readonly string ApiBaseAdress = System.Configuration.ConfigurationManager.AppSettings["ApiBaseAddress"];
public BaseController()
{
//Set as needed Servicepoint settings
//string SecurityProtocolTypeFromConfig = System.Configuration.ConfigurationManager.AppSettings["SecurityProtocolType"];
//SecurityProtocolType fromConfig;
//Enum.TryParse(SecurityProtocolTypeFromConfig, out fromConfig);
//ServicePointManager.SecurityProtocol = fromConfig;
//possible ServicePoint setting needed in some cases.
//ServicePointManager.Expect100Continue = false;
//ServicePointManager.MaxServicePointIdleTime = 2000;
//ServicePointManager.SetTcpKeepAlive(false, 1, 1);
}
}
And here is the HttpClientSingleton class:
public sealed class HttpClientSingleton
{
private static readonly Lazy<HttpClient> lazy = new Lazy<HttpClient>(() => new HttpClient());
public static HttpClient Instance { get { return lazy.Value; } }
private HttpClientSingleton()
{
}
}
So putting it together. Here is an example of getting some loginfo from the API.
public class MyLogController : BaseController
{
[HttpPost]
public async Task<JsonResult> log(string requestId)
{
var url = ApiBaseAdress + string.Format("/api/runs/log/{0}", requestId);
List<Log> logs = new List<Log>();
var response = await HttpClientSingleton.Instance.GetAsync(url);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadAsStringAsync();
logs = JsonConvert.DeserializeObject<List<Log>>(result);
return Json(logs);
}
}
You can write a static helper class. If the name is ApiHelper, then add a Microsoft.AspNet.WebApi.Client reference. When your app is initialized, call the class's InitializeClient() method, and you can call the GetAsync() method if you need. The code is below:
public static class ApiHelper
{
public static HttpClient ApiClient { get; set; }
public static void InitializeClient()
{
ApiClient = new HttpClient();
ApiClient.DefaultRequestHeaders.Accept.Clear();
ApiClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
public static async Task<T> GetAsync<T>(string url)
{
using (HttpResponseMessage response = await ApiHelper.ApiClient.GetAsync(url))
{
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsAsync<T>();
return result;
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
}
}