I am trying to UnitTest my MVC Controller method, which internally makes call to an WebAPI(using HttpClient). I'm not able to figure out how can I fake the httpclient call, as it should not go for actual request. Below is my source code and unit test case. Test case fails, as the call goes for actual HttpRequest (An error occurred while sending the request. A connection with the server could not be established)
Base MVC Controller
public class BaseController : Controller
{
public virtual async Task<T> PostRequestAsync<T>(string endpoint, Object obj) where T : class
{
var address = "http://localhost:5001/api/Login";
StringContent json = new StringContent(JsonConvert.SerializeObject(obj), Encoding.UTF8, "");
using (var client = new HttpClient())
{
try
{
var response = await client.PostAsync(address, json); // Test case fails here
if (response.IsSuccessStatusCode)
{
string data = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(data);
}
return default(T);
}
catch (WebException)
{
throw;
}
}
}
}
Derived class Controller
public class AccountController : BaseController
{
public AccountController() : base()
{
}
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
if (ModelState.IsValid)
{
var result = await PostRequestAsync<ResultObject>(Constants.UserLoginAPI, model); // this is call for basecontroller method which actually has HttpClient call.
var output = JsonConvert.DeserializeObject<UserObject>(result.Object.ToString());
if (result.Succeeded && !string.IsNullOrEmpty(output.Email))
{
var userRoleInfo = await GetRequestAsync<List<UserRoleObject>>(string.Format(Constants.GetUserRoleInfoAPI, output.Email));
if (userRoleInfo != null)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, output.Email),
new Claim("Username", output.UserName)
};
var claimsIdentity = new ClaimsIdentity(
claims, CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), new AuthenticationProperties { IsPersistent = model.RememberMe });
}
return View(new LoginViewModel());
}
}
return View(model);
}
}
TestMethod
[Fact]
public async Task LoginTest_Post_UserHasInValidCredentials()
{
// Arrange
var mockModel = new LoginViewModel { };
mockModel.Password = "TestPassword";
mockModel.Email = "test#test.com";
mockModel.RememberMe = false;
var commonResult = new CommonResult { Object = null, Succeeded = false, StatusCode = Common.Enums.ResponseStatusCodeEnum.Success };
var email = string.Empty;
var mockHttp = new MockHttpMessageHandler();
var mockBase = new Mock<BaseController>() { CallBase=true};
mockHttp.When("http://127.0.0.1:5001/*").Respond("application/json", "{'name' : 'Test McGee'}"); // Respond with JSON - using RichardSzalay.MockHttp;
//// Inject the handler or client into your application code
StringContent jsonInput = new StringContent(JsonConvert.SerializeObject(mockModel), Encoding.UTF8, "application/json");
var client = new HttpClient(mockHttp);
var response = await client.PostAsync("http://127.0.0.1:5001" + Constants.UserLoginAPI, jsonInput);
var json = await response.Content.ReadAsStringAsync();
mockBase.Setup(test => test.PostRequestAsync<CommonResult>(Constants.UserLoginAPI, mockModel)).Returns(Task.FromResult(CommonResult()));
var result = await accountController.Login(mockModel); // test case fails, as the call goes for actual HttpRequest (An error occurred while sending the request. A connection with the server could not be established)
//var viewResult = Assert.IsType<ViewResult>(result);
Assert.NotNull(commonResult);
Assert.False(commonResult.Succeeded);
Assert.Empty(email);
//Assert.NotNull(model.Email);
}
Tight coupling to HttpClient in the base controller makes it difficult to test derived classes in isolation. Review and refactor that code to follow DI.
No need to have a base controller and it is usually not advised.
Extract PostRequestAsync out into its own service abstraction and implementation.
public interface IWebService {
Task<T> PostRequestAsync<T>(string endpoint, Object obj) where T : class;
Task<T> GetRequestAsync<T>(string endpoint) where T : class;
}
public class WebService : IWebService {
static HttpClient client = new HttpClient();
public virtual async Task<T> PostRequestAsync<T>(string requestUri, Object obj) where T : class {
var content = new StringContent(JsonConvert.SerializeObject(obj), Encoding.UTF8, "");
try {
var response = await client.PostAsync(requestUri, content); // Test case fails here
if (response.IsSuccessStatusCode) {
string data = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(data);
}
return default(T);
} catch (WebException) {
throw;
}
}
public async Task<T> GetRequestAsync<T>(string requestUri) where T : class {
try {
var response = await client.GetAsync(requestUri);
if (response.IsSuccessStatusCode) {
string data = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(data);
}
return default(T);
} catch (WebException) {
throw;
}
}
}
Refactor derived controllers to depend on the service abstraction
public class AccountController : Controller {
private readonly IWebService webService;
public AccountController(IWebService webService) {
this.webService = webService;
}
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null) {
if (ModelState.IsValid) {
var result = await webService.PostRequestAsync<ResultObject>(Constants.UserLoginAPI, model);
if (result.Succeeded) {
var output = JsonConvert.DeserializeObject<UserObject>(result.Object.ToString());
if (output != null && !string.IsNullOrEmpty(output.Email)) {
var userRoleInfo = await webService.GetRequestAsync<List<UserRoleObject>>(string.Format(Constants.GetUserRoleInfoAPI, output.Email));
if (userRoleInfo != null) {
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, output.Email),
new Claim("Username", output.UserName)
};
var claimsIdentity = new ClaimsIdentity(
claims, CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), new AuthenticationProperties { IsPersistent = model.RememberMe });
}
return View(new LoginViewModel());
}
}
}
return View(model);
}
}
This should now allow you to mock the dependency when testing in isolation without adverse side effects.
[Fact]
public async Task LoginTest_Post_UserHasInValidCredentials() {
// Arrange
var mockModel = new LoginViewModel { };
mockModel.Password = "TestPassword";
mockModel.Email = "test#test.com";
mockModel.RememberMe = false;
var commonResult = new CommonResult {
Object = null,
Succeeded = false,
StatusCode = Common.Enums.ResponseStatusCodeEnum.Success
};
var mockWebService = new Mock<IWebService>();
var accountController = new AccountController(mockWebService.Object) {
//HttpContext would also be needed
};
mockWebService
.Setup(test => test.PostRequestAsync<CommonResult>(Constants.UserLoginAPI, mockModel))
.ReturnsAsync(commonResult);
//
//Act
var result = await accountController.Login(mockModel);
//Assert
//...Make assertions here
}
I would inject an IHttpClient interface, and in release register a HttpClient wrapper that implements that interface.
Related
I am bit frustrated now what's wrong with my code, and I hope you guys can help me with it, so here are the things I have tried.
so I tried making the HttpClient static, and I tried using the IHttpClientFactory.CreateClient() and I even added this on my .csproj
<ServerGarbageCollection>false</ServerGarbageCollection>
Here is the sample code that I have been doing
public class TestController : BaseController
{
private static HttpClient _httpClient = new();
public TestController()
{
}
[HttpGet("bills")]
public async Task<IActionResult> GetBillsPresentment([FromQuery] GetBillPresentmentQuery query)
{
if (!query.AccountNumber.Contains("-"))
query.AccountNumber = FormatAccountNumber(query.AccountNumber);
var billDetails = await GetBillDetail(query.AccountNumber);
if (billDetails == null)
throw new ProviderProcessException(ProviderErrorCode.INVALID_ACCOUNT_NUMBER);
return Ok(new BillPresentmentVm
{
User = new CustomerDto
{
CustomerName = billDetails.Name
},
Billing = new BillingDto
{
AccountNumber = query.AccountNumber,
DueDate = DateTime.Parse(billDetails.LastReadDate).AddMonths(1),
Outstanding = !string.IsNullOrEmpty(billDetails.Arrears) ? decimal.Parse(billDetails.Arrears) : null
}
});
}
private async Task<ResponseModel> GetBillDetail(string accountNumber)
{
try
{
var payload = new { accno = accountNumber };
string json = JsonConvert.SerializeObject(payload);
var buffer = System.Text.Encoding.UTF8.GetBytes(json);
using var byteContent = new ByteArrayContent(buffer);
byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await _httpClient.PostAsync("https://test.com", byteContent);
if (!response.IsSuccessStatusCode)
throw new ProviderProcessException(ProviderErrorCode.BILLING_CYCLE_UNAVAILABLE);
var result = await response.Content.ReadAsStringAsync();
if (result == "Accno not found!") return null;
var data = JsonConvert.DeserializeObject<ResponseModel>(result);
return data;
}
catch (Exception)
{
throw new ProviderProcessException(ProviderErrorCode.BILLING_CYCLE_UNAVAILABLE);
}
}
private static string FormatAccountNumber(string accountNumber)
{
return string.Format("{0:#######-########}", Convert.ToInt64(accountNumber));
}
}
And here's the docker memory usage
The memory usage keeps increasing after a request. Can someone explains me why it is not decreasing?
Thank you very much in advance
I solve this issue using IHttpClientFactory instead of HttpClient.
https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
I use some thing like this and it works fine on large amount of requests per second and it use memory in normal way .
namespace BehsaLoyalty.ApiClient
{
public class ApiRepo : IApiRepo
{
private readonly IHttpClientFactory _HttpClientFactory;
public ApiRepo (IHttpClientFactory httpClientFactory)
{
_HttpClientFactory = httpClientFactory;
}
public async Task<ResponseModel> PostMyObject(Myobject model, CancellationToken cancellationToken)
{
HttpClient httpClient = _HttpClientFactory.CreateClient("ApiDestinationURI");
using HttpRequestMessage request = new(HttpMethod.Post, "/blah/blah");
request.Content = new StringContent(JsonSerializer.Serialize(model));
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
HttpResponseMessage response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead, cancellationToken);
response.EnsureSuccessStatusCode();
string createdContent = await response.Content.ReadAsStringAsync();
ResponseModel ResponseReturn = JsonSerializer.Deserialize<ResponseModel>(createdContent);
return ResponseReturn;
}
}
}
I am dealing with a piece of code that looks like this:
public class Uploader : IUploader
{
public Uploader()
{
// assign member variables to dependency injected interface implementations
}
public async Task<string> Upload(string url, string data)
{
HttpResponseMessage result;
try
{
var handler = new HttpClientHandler();
var client = new HttpClient(handler);
result = await client.PostAsync(url, new FormUrlEncodedContent(data));
if (result.StatusCode != HttpStatusCode.OK)
{
return "Some Error Message";
}
else
{
return null; // Success!
}
}
catch (Exception ex)
{
// do some fancy stuff here
}
}
}
I am trying to unit test the Upload function. In particular, I need to mock the HttpClient. After reading the other answers on here and these two articles, I know that one of the better ways to solve this is to mock the HttpMessageHandler instead and pass that to HttpClient and have it return whatever I want.
So, I started along that path by first passing in HttpClient in the constructor as a dependency:
public class Uploader : IUploader
{
private readonly HttpClient m_httpClient; // made this a member variable
public Uploader(HttpClient httpClient) // dependency inject this
{
m_httpClient = httpClient;
}
public async Task<string> Upload(string url, string data)
{
HttpResponseMessage result;
try
{
var handler = new HttpClientHandler();
result = await m_httpClient.PostAsync(url, new FormUrlEncodedContent(data));
if (result.StatusCode != HttpStatusCode.OK)
{
return "Some Error Message";
}
else
{
return null; // Success!
}
}
catch (Exception ex)
{
// do some fancy stuff here
}
}
}
and adding: services.AddSingleton<HttpClient>(); to the ConfigureServices method of Startup.cs.
But now I face a slight issue where the original code specifically creates a HttpClientHandler to pass in. How then do I refactor that to take in a mockable handler?
I find the simplest way is to continue using HttpClient, but pass in a mocking HttpClientHandler such as https://github.com/richardszalay/mockhttp
Code sample from the link above:
var mockHttp = new MockHttpMessageHandler();
mockHttp.When("http://localhost/api/user/*")
.Respond("application/json", "{'name' : 'Test McGee'}");
// Inject the handler or client into your application code
var client = mockHttp.ToHttpClient();
var response = await client.GetAsync("http://localhost/api/user/1234");
var json = await response.Content.ReadAsStringAsync();
Console.Write(json); // {'name' : 'Test McGee'}
The Dependency Injection framework built into .NET Core ignores internal constructors, so it will call the parameter-less constructor in this scenario.
public sealed class Uploader : IUploader
{
private readonly HttpClient m_httpClient;
public Uploader() : this(new HttpClientHandler())
{
}
internal Uploader(HttpClientHandler handler)
{
m_httpClient = new HttpClient(handler);
}
// regular methods
}
In your unit tests, you can use the constructor accepting the HttpClientHandler:
[Fact]
public async Task ShouldDoSomethingAsync()
{
var mockHttp = new MockHttpMessageHandler();
mockHttp.When("http://myserver.com/upload")
.Respond("application/json", "{'status' : 'Success'}");
var uploader = new Uploader(mockHttp);
var result = await uploader.UploadAsync();
Assert.Equal("Success", result.Status);
}
Normally I'm not a big fan of having an internal constructor to facilitate testing, however, I find this more obvious and self-contained than registering a shared HttpClient.
HttpClientFactory might be another good option, but I haven't played around with that too much, so I'll just give info on what I've found useful myself.
One way would be to abstract your HTTP functionality into a service i.e. HttpService which implements an interface of IHttpService:
IHttpService
public interface IHttpService
{
Task<HttpResponseMessage> Post(Uri url, string payload, Dictionary<string, string> headers = null);
}
HttpService
public class HttpService : IHttpService
{
private static HttpClient _httpClient;
private const string MimeTypeApplicationJson = "application/json";
public HttpService()
{
_httpClient = new HttpClient();
}
private static async Task<HttpResponseMessage> HttpSendAsync(HttpMethod method, Uri url, string payload,
Dictionary<string, string> headers = null)
{
var request = new HttpRequestMessage(method, url);
request.Headers.Add("Accept", MimeTypeApplicationJson);
if (headers != null)
{
foreach (var header in headers)
{
request.Headers.Add(header.Key, header.Value);
}
}
if (!string.IsNullOrWhiteSpace(payload))
request.Content = new StringContent(payload, Encoding.UTF8, MimeTypeApplicationJson);
return await _httpClient.SendAsync(request);
}
public async Task<HttpResponseMessage> Post(Uri url, string payload, Dictionary<string, string> headers = null)
{
return await HttpSendAsync(HttpMethod.Post, url, payload, headers);
}
}
Add to your services:
services.AddSingleton<IHttpService, HttpService>();
In your class you would then inject IHttpService as a dependency:
public class Uploader : IUploader
{
private readonly IHttpService _httpService; // made this a member variable
public Uploader(IHttpService httpService) // dependency inject this
{
_httpService = httpService;
}
public async Task<string> Upload(string url, string data)
{
HttpResponseMessage result;
try
{
result = await _httpService.PostAsync(new Uri(url), data);
if (result.StatusCode != HttpStatusCode.OK)
{
return "Some Error Message";
}
else
{
return null; // Success!
}
}
catch (Exception ex)
{
// do some fancy stuff here
}
}
}
You could then use Moq to mock HttpService in your unit test:
[TestClass]
public class UploaderTests
{
private Mock<IHttpService> _mockHttpService = new Mock<IHttpService>();
[TestMethod]
public async Task WhenStatusCodeIsNot200Ok_ThenErrorMessageReturned()
{
// arrange
var uploader = new Uploader(_mockHttpService.Object);
var url = "someurl.co.uk";
var data = "data";
// need to setup your mock to return the response you want to test
_mockHttpService
.Setup(s => s.PostAsync(url, data))
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.InternalServerError));
// act
var result = await uploader.Upload(new Uri(url), data);
// assert
Assert.AreEqual("Some Error Message", result);
}
[TestMethod]
public async Task WhenStatusCodeIs200Ok_ThenNullReturned()
{
// arrange
var uploader = new Uploader(_mockHttpService.Object);
var url = "someurl.co.uk";
var data = "data";
// need to setup your mock to return the response you want to test
_mockHttpService
.Setup(s => s.PostAsync(new Uri(url), data))
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK));
// act
var result = await uploader.Upload(url, data);
// assert
Assert.AreEqual(null, result);
}
}
I have used Microsoft Graph SDK in my project to call graph API, for this I need to use GraphServiceClient.
To use GraphServiceClient, i have to add some helper classes, in which SDKHelper is a static class which has GetAuthenticatedClient() method.
Since method under test is tightly coupled to SDKHelper which is static, so I have created a service class and injected the dependency.
Below is the controller and method,
public class MyController
{
private IMyServices _iMyServices { get; set; }
public UserController(IMyServices iMyServices)
{
_iMyServices = iMyServices;
}
public async Task<HttpResponseMessage> GetGroupMembers([FromUri]string groupID)
{
GraphServiceClient graphClient = _iMyServices.GetAuthenticatedClient();
IGroupMembersCollectionWithReferencesPage groupMembers = await _iMyServices.GetGroupMembersCollectionWithReferencePage(graphClient, groupID);
return this.Request.CreateResponse(HttpStatusCode.OK, groupMembers, "application/json");
}
}
Service Class,
public class MyServices : IMyServices
{
public GraphServiceClient GetAuthenticatedClient()
{
GraphServiceClient graphClient = new GraphServiceClient(
new DelegateAuthenticationProvider(
async (requestMessage) =>
{
string accessToken = await SampleAuthProvider.Instance.GetAccessTokenAsync();
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
requestMessage.Headers.Add("Prefer", "outlook.timezone=\"" + TimeZoneInfo.Local.Id + "\"");
}));
return graphClient;
}
public async Task<IGraphServiceGroupsCollectionPage> GetGraphServiceGroupCollectionPage(GraphServiceClient graphClient)
{
return await graphClient.Groups.Request().GetAsync();
}
}
I am having challenge in writing Unit Test Case for the above service class methods, Below is my Unit Test Code:
public async Task GetGroupMembersCollectionWithReferencePage_Success()
{
GraphServiceClient graphClient = GetAuthenticatedClient();
IGraphServiceGroupsCollectionPage groupMembers = await graphClient.Groups.Request().GetAsync();
Mock<IUserServices> mockIUserService = new Mock<IUserServices>();
IGraphServiceGroupsCollectionPage expectedResult = await mockIUserService.Object.GetGraphServiceGroupCollectionPage(graphClient);
Assert.AreEqual(expectedResult, groupMembers);
}
In Above Test case line number 4 throws an exception -
Message: The type initializer for 'Connect3W.UserMgt.Api.Helpers.SampleAuthProvider' threw an exception.
Inner Exception Message: Value cannot be null. Parameter name: format
Can anyone suggest me how to use MOQ to mock above code or any other method to complete test case for this ?
Do not mock what you do not own. GraphServiceClient should be treated as a 3rd party dependency and should be encapsulated behind abstractions you control
You attempted to do that but are still leaking implementation concerns.
The service can be simplified to
public interface IUserServices {
Task<IGroupMembersCollectionWithReferencesPage> GetGroupMembers(string groupID);
}
and the implementation
public class UserServices : IUserServices {
GraphServiceClient GetAuthenticatedClient() {
var graphClient = new GraphServiceClient(
new DelegateAuthenticationProvider(
async (requestMessage) =>
{
string accessToken = await SampleAuthProvider.Instance.GetAccessTokenAsync();
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
requestMessage.Headers.Add("Prefer", "outlook.timezone=\"" + TimeZoneInfo.Local.Id + "\"");
}));
return graphClient;
}
public Task<IGroupMembersCollectionWithReferencesPage> GetGroupMembers(string groupID) {
var graphClient = GetAuthenticatedClient();
return graphClient.Groups[groupID].Members.Request().GetAsync();
}
}
Which would result in the controller being simplified as well
public class UserController : ApiController {
private readonly IUserServices service;
public UserController(IUserServices myServices) {
this.service = myServices;
}
public async Task<IHttpActionResult> GetGroupMembers([FromUri]string groupID) {
IGroupMembersCollectionWithReferencesPage groupMembers = await service.GetGroupMembers(groupID);
return Ok(groupMembers);
}
}
Now for testing of the controller you can easily mock the abstractions to behave as expected in order to exercise the test to completion because the controller is completely decoupled from the GraphServiceClient 3rd party dependency and the controller can be tested in isolation.
[TestClass]
public class UserControllerShould {
[TestMethod]
public async Task GetGroupMembersCollectionWithReferencePage_Success() {
//Arrange
var groupId = "12345";
var expectedResult = Mock.Of<IGroupMembersCollectionWithReferencesPage>();
var mockService = new Mock<IUserServices>();
mockService
.Setup(_ => _.GetGroupMembers(groupId))
.ReturnsAsync(expectedResult);
var controller = new UserController(mockService.Object);
//Act
var result = await controller.GetGroupMembers(groupId) as System.Web.Http.Results.OkNegotiatedContentResult<IGroupMembersCollectionWithReferencesPage>;
//Assert
Assert.IsNotNull(result);
var actualResult = result.Content;
Assert.AreEqual(expectedResult, actualResult);
}
}
An alternative solution to #Nkosi. Using the constructor public GraphServiceClient(IAuthenticationProvider authenticationProvider, IHttpProvider httpProvider = null); we can Mock the requests actually made.
Complete example below.
Our GraphApiService uses IMemoryCache, to cache both AccessToken and Users from ADB2C, IHttpClientFactory for HTTP requests and Settings is from appsettings.json.
https://learn.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-5.0
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-5.0
public class GraphApiService
{
private readonly IHttpClientFactory _clientFactory;
private readonly IMemoryCache _memoryCache;
private readonly Settings _settings;
private readonly string _accessToken;
public GraphApiService(IHttpClientFactory clientFactory, IMemoryCache memoryCache, Settings settings)
{
_clientFactory = clientFactory;
_memoryCache = memoryCache;
_settings = settings;
string graphApiAccessTokenCacheEntry;
// Look for cache key.
if (!_memoryCache.TryGetValue(CacheKeys.GraphApiAccessToken, out graphApiAccessTokenCacheEntry))
{
// Key not in cache, so get data.
var adb2cTokenResponse = GetAccessTokenAsync().GetAwaiter().GetResult();
graphApiAccessTokenCacheEntry = adb2cTokenResponse.access_token;
// Set cache options.
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromSeconds(adb2cTokenResponse.expires_in));
// Save data in cache.
_memoryCache.Set(CacheKeys.GraphApiAccessToken, graphApiAccessTokenCacheEntry, cacheEntryOptions);
}
_accessToken = graphApiAccessTokenCacheEntry;
}
public async Task<List<Adb2cUser>> GetAllUsersAsync(bool refreshCache = false)
{
if (refreshCache)
{
_memoryCache.Remove(CacheKeys.Adb2cUsers);
}
return await _memoryCache.GetOrCreateAsync(CacheKeys.Adb2cUsers, async (entry) =>
{
entry.SetAbsoluteExpiration(TimeSpan.FromHours(1));
var authProvider = new AuthenticationProvider(_accessToken);
GraphServiceClient graphClient = new GraphServiceClient(authProvider, new HttpClientHttpProvider(_clientFactory.CreateClient()));
var users = await graphClient.Users
.Request()
.GetAsync();
return users.Select(user => new Adb2cUser()
{
Id = Guid.Parse(user.Id),
GivenName = user.GivenName,
FamilyName = user.Surname,
}).ToList();
});
}
private async Task<Adb2cTokenResponse> GetAccessTokenAsync()
{
var client = _clientFactory.CreateClient();
var kvpList = new List<KeyValuePair<string, string>>();
kvpList.Add(new KeyValuePair<string, string>("grant_type", "client_credentials"));
kvpList.Add(new KeyValuePair<string, string>("client_id", _settings.AzureAdB2C.ClientId));
kvpList.Add(new KeyValuePair<string, string>("scope", "https://graph.microsoft.com/.default"));
kvpList.Add(new KeyValuePair<string, string>("client_secret", _settings.AzureAdB2C.ClientSecret));
#pragma warning disable SecurityIntelliSenseCS // MS Security rules violation
var req = new HttpRequestMessage(HttpMethod.Post, $"https://login.microsoftonline.com/{_settings.AzureAdB2C.Domain}/oauth2/v2.0/token")
{ Content = new FormUrlEncodedContent(kvpList) };
#pragma warning restore SecurityIntelliSenseCS // MS Security rules violation
using var httpResponse = await client.SendAsync(req);
var response = await httpResponse.Content.ReadAsStringAsync();
httpResponse.EnsureSuccessStatusCode();
var adb2cTokenResponse = JsonSerializer.Deserialize<Adb2cTokenResponse>(response);
return adb2cTokenResponse;
}
}
public class AuthenticationProvider : IAuthenticationProvider
{
private readonly string _accessToken;
public AuthenticationProvider(string accessToken)
{
_accessToken = accessToken;
}
public Task AuthenticateRequestAsync(HttpRequestMessage request)
{
request.Headers.Add("Authorization", $"Bearer {_accessToken}");
return Task.CompletedTask;
}
}
public class HttpClientHttpProvider : IHttpProvider
{
private readonly HttpClient http;
public HttpClientHttpProvider(HttpClient http)
{
this.http = http;
}
public ISerializer Serializer { get; } = new Serializer();
public TimeSpan OverallTimeout { get; set; } = TimeSpan.FromSeconds(300);
public void Dispose()
{
}
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
{
return http.SendAsync(request);
}
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
HttpCompletionOption completionOption,
CancellationToken cancellationToken)
{
return http.SendAsync(request, completionOption, cancellationToken);
}
}
We then use the GraphApiService in various Controllers. Example from a simple CommentController below. CommentService not included but it is not needed for the example anyway.
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class CommentController : ControllerBase
{
private readonly CommentService _commentService;
private readonly GraphApiService _graphApiService;
public CommentController(CommentService commentService, GraphApiService graphApiService)
{
_commentService = commentService;
_graphApiService = graphApiService;
}
[HttpGet("{rootEntity}/{id}")]
public ActionResult<IEnumerable<CommentDto>> Get(RootEntity rootEntity, int id)
{
var comments = _commentService.Get(rootEntity, id);
var users = _graphApiService.GetAllUsersAsync().GetAwaiter().GetResult();
var commentDtos = new List<CommentDto>();
foreach (var comment in comments)
{
commentDtos.Add(CommonToDtoMapper.MapCommentToCommentDto(comment, users));
}
return Ok(commentDtos);
}
[HttpPost("{rootEntity}/{id}")]
public ActionResult Post(RootEntity rootEntity, int id, [FromBody] string message)
{
_commentService.Add(rootEntity, id, message);
_commentService.SaveChanges();
return Ok();
}
}
Since we use our own IAuthenticationProvider and IHttpProvider we can mock IHttpClientFactory based on what URI is called. Complete test example below, check mockMessageHandler.Protected() to see how the requests are mocked. To find the exact request made we look at the documentation. For example var users = await graphClient.Users.Request().GetAsync(); is equivalent to GET https://graph.microsoft.com/v1.0/users.
https://learn.microsoft.com/en-us/graph/api/user-list?view=graph-rest-1.0&tabs=http#request
public class CommentControllerTest : SeededDatabase
{
[Fact]
public void Get()
{
using (var context = new ApplicationDbContext(_dbContextOptions))
{
var controller = GeCommentController(context);
var result = controller.Get(RootEntity.Question, 1).Result;
var okResult = Assert.IsType<OkObjectResult>(result);
var returnValue = Assert.IsType<List<CommentDto>>(okResult.Value);
Assert.Equal(2, returnValue.Count());
}
}
[Theory]
[MemberData(nameof(PostData))]
public void Post(RootEntity rootEntity, int id, string message)
{
using (var context = new ApplicationDbContext(_dbContextOptions))
{
var controller = GeCommentController(context);
var result = controller.Post(rootEntity, id, message);
var okResult = Assert.IsType<OkResult>(result);
var comment = context.Comments.First(x => x.Text == message);
if(rootEntity == RootEntity.Question)
{
Assert.Equal(comment.QuestionComments.First().QuestionId, id);
}
}
}
public static IEnumerable<object[]> PostData()
{
return new List<object[]>
{
new object[]
{ RootEntity.Question, 1, "Test comment from PostData" }
};
}
private CommentController GeCommentController(ApplicationDbContext dbContext)
{
var userService = new Mock<IUserResolverService>();
userService.Setup(x => x.GetNameIdentifier()).Returns(DbContextSeed.CurrentUser);
var settings = new Settings();
var commentService = new CommentService(new ExtendedApplicationDbContext(dbContext, userService.Object));
var expectedContentGetAccessTokenAsync = #"{
""token_type"": ""Bearer"",
""expires_in"": 3599,
""ext_expires_in"": 3599,
""access_token"": ""123""
}";
var expectedContentGetAllUsersAsync = #"{
""#odata.context"": ""https://graph.microsoft.com/v1.0/$metadata#users"",
""value"": [
{
""businessPhones"": [],
""displayName"": ""Oscar"",
""givenName"": ""Oscar"",
""jobTitle"": null,
""mail"": null,
""mobilePhone"": null,
""officeLocation"": null,
""preferredLanguage"": null,
""surname"": ""Andersson"",
""userPrincipalName"": """ + DbContextSeed.DummyUserExternalId + #"#contoso.onmicrosoft.com"",
""id"":""" + DbContextSeed.DummyUserExternalId + #"""
}
]
}";
var mockFactory = new Mock<IHttpClientFactory>();
var mockMessageHandler = new Mock<HttpMessageHandler>();
mockMessageHandler.Protected()
#pragma warning disable SecurityIntelliSenseCS // MS Security rules violation
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(x => x.RequestUri.AbsoluteUri.Contains("https://login.microsoftonline.com/")), ItExpr.IsAny<CancellationToken>())
#pragma warning restore SecurityIntelliSenseCS // MS Security rules violation
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(expectedContentGetAccessTokenAsync)
});
mockMessageHandler.Protected()
#pragma warning disable SecurityIntelliSenseCS // MS Security rules violation
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(x => x.RequestUri.AbsoluteUri.Contains("https://graph.microsoft.com/")), ItExpr.IsAny<CancellationToken>())
#pragma warning restore SecurityIntelliSenseCS // MS Security rules violation
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(expectedContentGetAllUsersAsync)
});
var httpClient = new HttpClient(mockMessageHandler.Object);
mockFactory.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(httpClient);
var services = new ServiceCollection();
services.AddMemoryCache();
var serviceProvider = services.BuildServiceProvider();
var memoryCache = serviceProvider.GetService<IMemoryCache>();
var graphService = new GraphApiService(mockFactory.Object, memoryCache, settings);
var controller = new CommentController(commentService, graphService);
return controller;
}
}
I have a web api which I'm calling (this is working correctly)
I call this like so
public ActionResult Index()
{
var mod = Checksomething();
return View();
}
public async Task Checksomething()
{
try
{
var client = new HttpClient();
var content = new StringContent(JsonConvert.SerializeObject(new UserLogin { EmailAddress = "SomeEmail#Hotmail.com", Password = "bahblah" }));
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await client.PostAsync("http://localhost:28247/api/UserLoginApi2/CheckCredentials", content);
var value = await response.Content.ReadAsStringAsync();
// I need to return UserProfile
var data = JsonConvert.DeserializeObject<UserProfile[]>(value);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
My web api passes back a model called UserProfile, I'm having great difficulty trying to return data back to the Index controller, would someone please enlighten me.
You need to change your method signature to use the generic version of Task
public async Task<ActionResult> Index()
{
UserProfile[] profiles = await Checksomething();
if (profiles.Any())
{
var user = profiles.First();
string username = user.FirstName;
// do something w/ username
}
return View();
}
public async Task<UserProfile[]> Checksomething()
{
try
{
var client = new HttpClient();
var content = new StringContent(JsonConvert.SerializeObject(new UserLogin { EmailAddress = "SomeEmail#Hotmail.com", Password = "bahblah" }));
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await client.PostAsync("http://localhost:28247/api/UserLoginApi2/CheckCredentials", content);
var value = await response.Content.ReadAsStringAsync();
// I need to return UserProfile
return JsonConvert.DeserializeObject<UserProfile[]>(value);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
The returned Task will be unwrapped and your caller will be given the Result of the Task, which in this case will be UserProfile[]
I have WebApi controller with async method Post:
public class WebApiController : ApiController
{
public async Task<HttpResponseMessage> Post(HttpRequestMessage request)
{
try
{
string json = await request.Content.ReadAsStringAsync();
var model = JsonConvert.DeserializeObject<TModel>(json);
var newEntity = Mapper.Map<TEntity>(model);
var newEntityVersion = Mapper.Map<TEntityVersion>(model);
var result = await CurrentRepository.AddAsync(newEntity, newEntityVersion);
return CreateResponse(result, "DefaultApiPost");
}
catch (Exception e)
{
Logger.Error("Post in " + ControllerName, e);
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, e);
}
}
}
AddAsync method looks like :
public virtual async Task<TEntity> AddAsync(TEntity entity, TEntityVersion version)
{
await DatabaseContext.SaveChangesAsync();
return entity;
}
When I call Post method using HttpClient I can enter AddAsync method :
client.PostAsync(requestUri, content).Result.Content.ReadAsStringAsync().Result;
But when I call Post method using WebApi controller explicit I can not enter AddAsync method, just jump over it :/ :
var product = FakeProduct();
var newEntity = Mapper.Map<ProductEntity>(product);
var newEntityVersion = Mapper.Map<ProductVersionEntity>(product);
productRepository.AddAsync(newEntity, newEntityVersion).Returns(Task.FromResult<ProductEntity>(newEntity));
var content = JsonConvert.SerializeObject(Product);
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost:8888/api/Product/")
{
Content = new StringContent(content),
};
var webApiController = FakeWebApiControllerWithPostRoute();
var result = await webApiController.Post(request);
Assert.AreEqual(HttpStatusCode.Created, result.StatusCode);
Any idea why this method is not executed ?