Azure Function V3 configuration with DI - c#

I have a Azure Function with 2 triggers:
I’m registering IService in my Startup like so:
I need a different configuration in the Service class depending on which trigger that is calling DoWork()? How can I achieve this using DI?
public class Service : IService
{
public Service(/*Configuration to be injected depends on calling trigger */)
{ }
public void DoWork()
{ }
}
Configuration extract:

Thankyou user1672994. Posting your suggestion as an answer so that it will be helpful for other community members who face similar kind of issues.
Below is the example code to implement todo work items where this will be helpful in resolving your issue.
using AZV3CleanArchitecture.Models;
using AZV3CleanArchitecture.Options;
using AZV3CleanArchitecture.Providers;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
namespace AZV3CleanArchitecture.Services
{
public class ToDoItemsService : IToDoItemsService
{
private readonly HttpClient httpClient;
private readonly ToDoItemsServiceOptions toDoItemsServiceOptions;
private readonly ILogger<ToDoItemsService> logger;
public ToDoItemsService(HttpClient httpClient, IOptions<ToDoItemsServiceOptions> toDoItemsServiceOptions, ILogger<ToDoItemsService> logger)
{
this.httpClient = httpClient;
this.toDoItemsServiceOptions = toDoItemsServiceOptions.Value;
this.logger = logger;
}
public async Task<ToDoItem> GetToDoItem(int id)
{
logger.LogInformation($"Retrieving item: {{{Constants.TodoItemId}}}", id);
var getUrl = $"{this.toDoItemsServiceOptions.BaseUrl.TrimEnd('/')}/todos/{id}";
using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, getUrl))
{
using (var response = await this.httpClient.SendAsync(requestMessage))
{
string responseString = await response.Content.ReadAsStringAsync();
logger.LogWarning($"Retrieved item: {{{Constants.TodoItemId}}}. Logged as warning for demo.", id);
return JsonConvert.DeserializeObject<ToDoItem>(responseString);
}
}
}
public async Task<IEnumerable<ToDoItem>> GetAllToDoItems(int id)
{
logger.LogInformation($"Retrieving all todo items");
var getUrl = $"{this.toDoItemsServiceOptions.BaseUrl.TrimEnd('/')}/todos";
using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, getUrl))
{
using (var response = await this.httpClient.SendAsync(requestMessage))
{
string responseString = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<IEnumerable<ToDoItem>>(responseString);
}
}
}
public async Task<ToDoItem> CreateToDoItem(ToDoItem toDoItem)
{
// call service and return the output
return await Task.FromResult(new ToDoItem() { Id = 1, UserId = 1, Title = "Some Dummy Title", Completed = true });
}
public Task<ToDoItem> UpdateToDoItem(ToDoItem toDoItem)
{
throw new System.NotImplementedException();
}
}
}
for further information check the ToDoItemServices link.

Related

Http response cannot be deserialized when it's created from test case

I'm trying to run this test:
[TestFixture]
public class AccountManagementViewModelTests
{
[Test, BaseAutoData]
public void OnSync_Should_AddToDatabase_MissingLists(
[Frozen] Mock<IHttpClientService> httpClientService,
[Frozen] Mock<IDataStore<AppModel.List>> dataStore,
List<ApiModel.List> lists,
AccountManagementViewModel sut)
{
// Arrange
httpClientService
.Setup(x => x.GetAsync(It.IsAny<string>()))
.ReturnsAsync(new HttpResponseMessage()
{
Content = JsonContent.Create(lists)
});
// Act
sut.SyncCommand.Execute(null);
// Assert
dataStore.Verify(
d => d.AddItemAsync(
It.Is<AppModel.List>(
x => lists.Any(y => y.Guid == x.ListId))),
Times.Exactly(3));
}
}
Problem is, when I try to deserialize the response, I get 3 empty entities
var allBackedupListsForCurrentUserRequestTask = _httpClientService.GetAsync(string.Format(ListApiEndPoints.GetListsByOwnerEmail, ApplicationUser.Current.Email));
var allBackedupListsForCurrentUserRequest = await allBackedupListsForCurrentUserRequestTask;
var allBackedupListsForCurrentUser = await JsonSerializer.DeserializeAsync<List<ApiModel.List>>(await allBackedupListsForCurrentUserRequest.Content.ReadAsStreamAsync());
If I examine the content of allBackedupListsForCurrentUserRequest.Content I can see all the data there, but none are deserialized into the allBackedupListsForCurrentUser.
The code works fine with real HttpRequests, the issue rises only during unit tests.
What am I missing here?
Other classes involved:
HttpClientService
using ListApp.Services.Interfaces;
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace ListApp.Services
{
public class HttpClientService : IHttpClientService
{
private static HttpClient _httpClient = new HttpClient();
public async Task<HttpResponseMessage> GetAsync(string requestUri)
{
return await _httpClient.GetAsync(requestUri);
}
public async Task<HttpResponseMessage> PutAsync(string requestUri, HttpContent content)
{
return await _httpClient.PutAsync(requestUri, content);
}
public async Task<HttpResponseMessage> PostAsync(string requestUri, HttpContent content)
{
return await _httpClient.PostAsync(requestUri, content);
}
public void SetBaseAddress(Uri baseAddress)
{
_httpClient.BaseAddress = baseAddress;
}
}
}
BaseAutoDataAttribute
using AutoFixture;
using AutoFixture.AutoMoq;
using AutoFixture.NUnit3;
using System;
namespace ListApp.UnitTests.DataTtributes
{
[AttributeUsage(AttributeTargets.Method)]
internal class BaseAutoDataAttribute : AutoDataAttribute
{
public BaseAutoDataAttribute() : base(() => CreateFixture()) { }
private static IFixture CreateFixture()
{
var fixture = new Fixture();
fixture.Customize(new AutoMoqCustomization { ConfigureMembers = true, GenerateDelegates = true });
return fixture;
}
}
}
Turns out I'm changing the value of JsonSerializerOptions.PropertyNameCaseInsensitive to true when the app starts but not during the tests.
Setting it to true on the [OneTimeSetUp] did the trick.
((JsonSerializerOptions)typeof(JsonSerializerOptions)
.GetField("s_defaultOptions",
System.Reflection.BindingFlags.Static |
System.Reflection.BindingFlags.NonPublic).GetValue(null))
.PropertyNameCaseInsensitive = true;
Solution by andre-ss6.

Can not instantiate proxy of class: System.Net.HttpWebRequest. Could not find a parameterless constructor

I am upgrading my C# function app from .net 3.1 to 6.0`.
When I run my test cases, I found that, 1 of my test case failed with the below error.
Castle.DynamicProxy.InvalidProxyConstructorArgumentsException : Can not instantiate proxy of class: System.Net.HttpWebRequest. Could not find a parameterless constructor.
Basically, I am trying to mock HttpWebRequest and below is my piece of code for that.
var httpWebRequest = new Mock<HttpWebRequest>();
It is working fine in .Net 3.1. I am using Moq version 4.16.1 in both the projects.
I spent a fair bit of time when .Net 6 was initially released getting my Unit Test suite established. Here's how I do it using the same Moq version 4.16.1:
The Unit Tests get a Moq HttpClientFactory from the BaseClass:
public class UnitTests : BaseUnitTest
{
[Fact]
public async Task Should_Return_GetSomethingAsync()
{
// Arrange
IHttpClientFactory httpClientFactory = base.GetHttpClientFactory(new Uri("ExternalWebsiteUrlToMockTheResponse"), new StringContent("A Mock Response JSON Object"));
YourService yourService = new YourService(httpClientFactory);
// Act
Something something = yourService.GetSomethingAsync().Result;
// Assert
Assert.IsType<Something>(Something);
//..
}
In a BaseUnitTest.cs Class:
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Moq;
using Moq.Protected;
public class BaseUnitTest
{
public IHttpClientFactory GetHttpClientFactory(Uri uri, StringContent content, HttpStatusCode statusCode = HttpStatusCode.OK)
{
Mock<HttpMessageHandler> httpMsgHandler = new Mock<HttpMessageHandler>();
httpMsgHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", new object[2]
{
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>()
}).ReturnsAsync(new HttpResponseMessage
{
StatusCode = statusCode,
Content = content
});
HttpClient client = new HttpClient(httpMsgHandler.Object);
client.BaseAddress = uri;
Mock<IHttpClientFactory> clientFactory = new Mock<IHttpClientFactory>();
clientFactory.Setup((IHttpClientFactory cf) => cf.CreateClient(It.IsAny<string>())).Returns(client);
return clientFactory.Object;
}
Your Service Class or Controller:
public class YourService : IYourService
{
private readonly IHttpClientFactory _clientFactory;
private readonly HttpClient _client;
public YourService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
_client = _clientFactory.CreateClient("YourAPI");
}
public async Task<Something> GetSomethingAsync()
{
using (var request = new HttpRequestMessage(HttpMethod.Post, _client.BaseAddress))
{
request.Content = new StringContent($#"{{""jsonrpc"":""2.0"",""method"":""Something"",""params"": [""{SomethingHash}""],""id"":1}}");
using (var response = await _client.SendAsync(request))
{
//System.Diagnostics.Debug.WriteLine(response?.Content.ReadAsStringAsync()?.Result);
if (response.IsSuccessStatusCode)
{
using (var responseStream = await response.Content.ReadAsStreamAsync())
{
var options = new JsonSerializerOptions { IncludeFields = true };
var something = await JsonSerializer.DeserializeAsync<Something>(responseStream, options);
// Check if the transactions from the address we're looking for...
if (something != null)
{
if (something.result?.from == address)
{
return something;
}
} } }
else {
string exceptionMsg = $"Message: {response.Content?.ReadAsStringAsync()?.Result}";
throw new YourGeneralException(response.StatusCode, exceptionMsg);
}
}
}
return null;
}
}
In your Program.cs
builder.Services.AddHttpClient("YourAPI", c =>
{
c.BaseAddress = new Uri("ExternalWebsiteUrlToMockTheResponse");
c.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
c.DefaultRequestHeaders.UserAgent.TryParseAdd("Your Agent");
});
You can expand the BaseUnitTest.ccs class to have chained tests as well.
Both HttpWebRequest constructors are obsolete and should not be used. You have to use the static function "Create" to create a new instance of the HttpWebRequest class:
HttpWebRequest myReq =
(HttpWebRequest)WebRequest.Create("http://www.contoso.com/");
To solve your issue, use the HttpClient class instead. This class has a parameterless constructor.

Mock IHttpContextAccessor in Integration Tests

I would like to test one method in controller which is retrieving value from IHttpContextAccessor.HttpContext.Connection.RemoteIpAddress.
Is it possible to customize WebApplicationFactory so one can set fixed ip address?
The workaround would be to wrap the expression above into service so it can be mocked.
Thanks
Test.cs
var ipAddress = IPAddress.Parse("192.168.1.123");
var factory = new WebApplicationFactory<Startup>();
// ?
using var client = factory.CreateClient();
var response = await client.GetAsync(new Uri("https://localhost/test"), UriKind.Relative));
var ipAddress = await response.Content.ReadAsStringAsync();
TestController.cs
private readonly IHttpContextAccessor _httpContextAccessor;
public TestController(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
[HttpGet("test")]
public async Task<IActionResult> GetTest()
{
var ipAddressObj = _httpContextAccessor.HttpContext?.Connection.RemoteIpAddress;
var ipAddress = ipAddressObj?.ToString();
return Ok(ipAddress);
}
Try this:
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Moq;
using System.Net;
using WebApplication.Api.Controllers;
using Xunit;
namespace WebApplication.Api.Tests
{
public class TestControllerTests
{
[Fact]
public async void IpAdressResult()
{
var ipAddress = "192.168.1.123";
var accessorMcok = new Mock<IHttpContextAccessor>();
accessorMcok.Setup(a => a.HttpContext.Connection.RemoteIpAddress).Returns(IPAddress.Parse(ipAddress));
var controller = new TestController(accessorMcok.Object);
var result = (OkObjectResult)(await controller.GetTest());
Assert.Equal(ipAddress, result.Value);
}
}
}

Best way to setup MSTest for REST service with cookie-authentication?

Background: I am using ASP.NET Core 3.1, and integration testing a REST service that requires cookie authentication.
Candidate solution below.
Note:
The reason I use a vanilla Host instead of TestServer is because of the cookie requirement. When using TestServer, it provides an HttpClient for you, but the client does not pass cookies back to the server.
I also attempted to use a custom HttpClient with TestServer. That consistently generated a System.Net.Sockets.SocketException (No connection could be made because the target machine actively refused it.)
using Microsoft.Extensions.Hosting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using WebApi; // Contains my Startup.cs
namespace WebApiTest
{
[TestClass]
public class UserTest
{
static IHost HttpHost;
[ClassInitialize]
public static async Task ClassStartup(TestContext context)
{
HttpHost = Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.Build();
await HttpHost.StartAsync();
}
[ClassCleanup]
public static async Task ClassCleanup()
{
await HttpHost.StopAsync();
}
public static HttpContent GetHttpContent(object content)
{
HttpContent httpContent = null;
if (content != null)
{
httpContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(content, content.GetType()));
httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
}
return httpContent;
}
public static HttpClient GetCookieHttpClient()
{
SocketsHttpHandler handler = new SocketsHttpHandler
{
AllowAutoRedirect = false,
CookieContainer = new CookieContainer(),
UseCookies = true
};
return new HttpClient(handler);
}
[TestMethod]
public async Task GetUserData_ReturnsSuccess()
{
using (HttpClient client = GetCookieHttpClient())
{
var credentials = new
{
Email = "test#test.com",
Password = "password123",
};
HttpResponseMessage response = await client.PostAsync("http://localhost:5000/api/auth/login", GetHttpContent(credentials));
response = await client.GetAsync(String.Format("http://localhost:5000/api/users/{0}", credentials.Email));
Assert.IsTrue(response.StatusCode == HttpStatusCode.OK);
}
}
}
}
HttpClient is a thin-client; it doesn't do anything unless you explicitly tell it to. In other words, it will never send the cookie for you; you must add a Cookie header to the request with the cookie value for each request. The test server "client" is just an HttpClient instance set up to proxy requests to the test server. You should use the test server, as prescribed, along with its client, and then add the Cookie header the requests you make with that.
Solutions based on Chris Pratt's suggestions
After some further digging, Microsoft provides a solution for this (WebApplicationFactory):
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using WebApi;
namespace WebApiTest
{
[TestClass]
public class Class2
{
static WebApplicationFactory<Startup> Factory;
static WebApplicationFactoryClientOptions ClientOptions;
[ClassInitialize]
public static async Task ClassStartup(TestContext context)
{
Factory = new WebApplicationFactory<Startup>();
ClientOptions = new WebApplicationFactoryClientOptions();
ClientOptions.AllowAutoRedirect = false;
ClientOptions.HandleCookies = true;
ClientOptions.BaseAddress = new Uri("http://localhost:5000");
}
public static HttpContent GetHttpContent(object content)
{
HttpContent httpContent = null;
if (content != null)
{
httpContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(content, content.GetType()));
httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
}
return httpContent;
}
[TestMethod]
public async Task GetUserData_ReturnsSuccess()
{
using (HttpClient client = Factory.CreateClient(ClientOptions))
{
var credentials = new
{
Email = "test#test.com",
Password = "password123",
};
HttpResponseMessage response = await client.PostAsync("http://localhost:5000/api/auth/login", GetHttpContent(credentials));
response = await client.GetAsync(String.Format("http://localhost:5000/api/users/{0}", credentials.Email));
Assert.IsTrue(response.StatusCode == HttpStatusCode.OK);
}
}
}
}
In case you want to stick with TestServer, here is a manual Cookie-passing implementation:
using Microsoft.AspNetCore.TestHost;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using WebApi;
namespace WebApiTest
{
public class CookieHttpClient : IDisposable
{
private static HttpContent GetHttpContent(object content)
{
HttpContent httpContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(content, content.GetType()));
httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
return httpContent;
}
private static IEnumerable<string> GetCookieStrings(CookieCollection collection)
{
List<string> output = new List<string>(collection.Count);
foreach (Cookie cookie in collection)
{
output.Add(cookie.Name + "=" + cookie.Value);
}
return output;
}
private HttpClient client;
private CookieContainer container;
public CookieHttpClient(HttpClient client)
{
this.client = client;
this.container = new CookieContainer();
}
public async Task<HttpResponseMessage> SendAsync(HttpMethod method, Uri uri)
{
return await this.SendAsync(method, uri, null);
}
public async Task<HttpResponseMessage> SendAsync(HttpMethod method, Uri uri, object data)
{
HttpRequestMessage request = new HttpRequestMessage(method, uri);
// Add data
if (data != null)
{
request.Content = GetHttpContent(data);
}
// Add cookies
CookieCollection collection = this.container.GetCookies(uri);
if (collection.Count > 0)
{
request.Headers.Add("Cookie", GetCookieStrings(collection));
}
HttpResponseMessage response = await this.client.SendAsync(request);
// Remember cookies before returning
if (response.Headers.Contains("Set-Cookie"))
{
foreach (string s in response.Headers.GetValues("Set-Cookie"))
{
this.container.SetCookies(uri, s);
}
}
return response;
}
public void Dispose()
{
this.client.Dispose();
}
}
[TestClass]
public class Class1
{
static TestServer TestServer;
[ClassInitialize]
public static async Task ClassStartup(TestContext context)
{
IWebHostBuilder builder = new WebHostBuilder()
.UseStartup<Startup>();
TestServer = new TestServer(builder);
}
[TestMethod]
public async Task GetUserData_ReturnsSuccess()
{
using (CookieHttpClient client = new CookieHttpClient(TestServer.CreateClient()))
{
var credentials = new
{
Email = "test#test.com",
Password = "password123",
};
HttpResponseMessage response = await client.SendAsync(HttpMethod.Post, new Uri("http://localhost:5000/api/auth/login"), credentials);
response = await client.SendAsync(HttpMethod.Get, new Uri("http://localhost:5000/api/users/" + credentials.Email));
Assert.IsTrue(response.StatusCode == HttpStatusCode.OK);
}
}
}
}

How to add CORS to .net core 3.2

Since the new .net 3.2 preview no longer has a startup.cs with its ConfigureServices function, I am at a loss to figure out how to implement .AddCors. The old way of adding services essentially was to add, then use a service. It doesn't look like that's the way to do it anymore. What is the proper code to add CORS?
Program.cs
using System.Threading.Tasks;
using Microsoft.AspNetCore.Blazor.Hosting;
using Microsoft.Extensions.DependencyInjection;
using BlazorDemo.Shared;
namespace BlazorDemo
{
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.Services.AddSingleton<IDataLayer, DataLayer>();
builder.Services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder
.AllowAnyOrigin());
});
builder.RootComponents.Add<App>("app");
await builder.Build().RunAsync();
}
}
}
Data.cs
namespace BlazorDemo.Shared
{
public class Data
{
public Country[] data { get; set; }
}
}
DataLayer.cs
using System;
using System.Threading.Tasks;
using System.Net.Http;
using Microsoft.AspNetCore.Components;
namespace BlazorDemo.Shared
{
public interface IDataLayer
{
Task<Country[]> FetchCountries(string sortField, bool sortDesc);
Task<Country[]> FetchCountries();
}
public class DataLayer : IDataLayer
{
public DataLayer(HttpClient httpClient)
{
this.httpClient = httpClient;
}
HttpClient httpClient;
public async Task<Country[]> FetchCountries(string sortField, bool sortDesc)
{
var url = $"http://outlier.oliversturm.com:8080/countries?sort[0][selector]={sortField}&sort[0][desc]={sortDesc}&take=10";
var data = await httpClient.GetJsonAsync<Data>(url);
return data.data;
}
public async Task<Country[]> FetchCountries()
{
Country[] Countries;
try
{
var url = $"http://outlier.oliversturm.com:8080/countries";
var data = await httpClient.GetJsonAsync<Data>(url);
Countries = ((Data)data).data;
}
catch (Exception e)
{
Country c = new Country() { name = "DD", areaKM2 = 2, population = 50, _id = e.Message };
Countries = new Country[] { c };
}
return Countries;
}
}
}

Categories