I have the below Generic Class & Interface implementations
public interface IHttpClient<T> where T : class
{
public Task<List<T>> GetJsonAsync(string url);
}
public class HttpClient<T> : IHttpClient<T> where T:class
{
private readonly IHttpClientFactory _clientFactory;
public HttpClient(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<List<T>> GetJsonAsync(string url)
{
var request = new HttpRequestMessage(HttpMethod.Get,url);
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<List<T>>(result);
}
return null;
}
}
and this is how I try to register them in the startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped(typeof(IHttpClient<>), typeof(HttpClient<>));
}
My Controller:
private readonly IHttpClient<Feed> _feedClient;
public HomeController( IHttpClient<Feed> _client)
{
_feedClient = _client;
}
and this is the error I'm getting
InvalidOperationException: Unable to resolve service for type 'System.Net.Http.IHttpClientFactory' while attempting to activate...
what is it that I'm missing? any help is very appreciated..
You should register HttpClient in startup class like this
//register
services.AddHttpClient();
use
public YourController(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
var client = _httpClientFactory.CreateClient();
Another options
//register
services.AddHttpClient("YourClientName", c =>
{
c.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
c.BaseAddress = new Uri("https://yoururl");
});
use
public YourController(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
var client = _httpClientFactory.CreateClient("YourClientName");
Related
I'm trying to create a integration test using Test Serer.
But I don't know why, I'm always getting an error message [404 not found]. I'm trying the simplest route of "/".
If someone have some idea of how to fix it, I would be very grateful.
Here is the code I use in my Test Server. This is my base class:
namespace NSG.Integration.Helpers
{
public class UnitTestFixture
{
//
public SqliteConnection sqliteConnection;
public ApplicationDbContext db_context;
public UserManager<ApplicationUser> userManager;
public RoleManager<ApplicationRole> roleManager;
public IConfiguration configuration = null;
private IWebHostBuilder _builder;
private TestServer _server;
private HttpClient _client;
//
public TestServer server { get { return _server; } }
public HttpClient client { get { return _client; } }
//
public void Fixture_ControllerTestSetup()
{
string projectPath = #"C:\Dat\Nsg\L\Web\22\Net.Incident4\NSG.NetIncident4.Core";
IWebHostBuilder _builder = null;
_server = null;
_client = null;
_builder = new WebHostBuilder()
.UseContentRoot(projectPath)
.UseEnvironment("Development")
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.SetBasePath(projectPath);
config.AddJsonFile("appsettings.json");
}).UseStartup<TestStartup>();
_server = new TestServer(_builder);
_client = _server.CreateClient();
userManager = _server.Services.GetService<UserManager<ApplicationUser>>();
roleManager = _server.Services.GetService<RoleManager<ApplicationRole>>();
db_context = _server.Services.GetService<ApplicationDbContext>();
}
[TearDown]
public void Fixture_TearDown()
{
Console.WriteLine("Fixture_UnitTestSetup: Dispose ...");
if ( sqliteConnection != null )
{
sqliteConnection.Close();
sqliteConnection.Dispose();
sqliteConnection = null;
}
if (db_context != null)
{
db_context.Database.EnsureDeleted();
db_context.Dispose();
db_context = null;
}
if (userManager != null)
{
userManager.Dispose();
userManager = null;
}
if (roleManager != null)
{
roleManager.Dispose();
roleManager = null;
}
if (_client != null)
{
_client.Dispose();
_client = null;
}
if (_server != null)
{
_server.Dispose();
_server = null;
}
}
}
}
This is test startup class. I'm using in-memory SQLite as my DB:
namespace NSG.Integration.Helpers
{
public class TestStartup : NSG.NetIncident4.Core.Startup
{
//
public TestStartup(IConfiguration configuration) : base(configuration)
{
}
//
public override void ConfigureServices(IServiceCollection services)
{
base.ConfigureServices(services);
//
ApplicationDbContext db_context =
NSG_Helpers.GetSqliteMemoryDbContext(NSG_Helpers.GetSqliteMemoryConnection(), services);
UserManager<ApplicationUser> userManager =
services.BuildServiceProvider().GetService<UserManager<ApplicationUser>>();
RoleManager<ApplicationRole> roleManager =
services.BuildServiceProvider().GetService<RoleManager<ApplicationRole>>();
DatabaseSeeder _seeder =
new DatabaseSeeder(db_context, userManager, roleManager);
_seeder.Seed().Wait();
}
//
}
}
This is a proof of concept test of accessing the home page:
namespace NSG.NetIncident4.Core_Tests.Infrastructure
{
[TestFixture]
public class EmailConfirmation_UnitTests : UnitTestFixture
{
//
public IConfiguration Configuration { get; set; }
Mock<IEmailSender> _emailSender = null;
[SetUp]
public void MySetup()
{
Fixture_ControllerTestSetup();
_emailSender = new Mock<IEmailSender>();
}
[Test()]
public async Task Home_Page_Test()
{
var response = await client.GetAsync("/");
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
Assert.AreEqual("Net-Incident Web API Services", responseString);
}
//
}
}
I do not know if HttpClient is properly configured, so this is the values of client:
I have a custom HttpMessageHandler implementation:
public class MyHandler : DelegatingHandler
{
private readonly HttpClient _httpClient;
private readonly MyHandlerOptions _config;
public MyHandler(
HttpClient httpClient,
IOptions<MyHandlerOptions> options)
{
_httpClient = httpClient;
_config = options.Value;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Authorization = await GetAccessToken()
return await base.SendAsync(request, cancellationToken);
}
private async Task<string> GetAccessToken()
{
//some logic to get access token using _httpClient and _config
}
}
It requires confiuration object MyHandlerOptions. Its form is not so important here. It basically contains clientId, clientSecret, etc. that are needed for the handler to know how to get the access token.
I have a few services (typed http clients) that need to use MyHandler:
//registration of MyHandler itself
builder.Services.AddHttpClient<MyHandler>();
//configuration of MyHandler
builder.Services.AddOptions<MyHandlerOptions>()
.Configure<IConfiguration>((config, configuration) =>
{
configuration.GetSection("MyHandlerOptions").Bind(config);
});
//Services that need to use MyHandler:
services.AddHttpClient<Service1>()
.AddHttpMessageHandler<MyHandler>();
services.AddHttpClient<Service2>()
.AddHttpMessageHandler<MyHandler>();
services.AddHttpClient<Service3>()
.AddHttpMessageHandler<MyHandler>();
The problem is that the MyHandlerOptions instance that I registered is valid only when used with Service1. However, Service2 and Service3 require other configuration (different clientId, clientSecret, etc.). How can I achieve it?
The possible solution that comes to my mind:
Create a new service:
public class AccessTokenGetter
{
Task<string> GetAccessToken(AccessTokenConfig config)
{
//get the access token...
}
}
Create separate HttpMessageHandlers for each case where configuration is different:
public class MyHandler1 : DelegatingHandler
{
private readonly MyHandler1Options _config;
private readonly AccessTokenGetter _accessTokenGetter;
public MyHandler(AccessTokenGetter accessTokenGetter, IOptions<MyHandlerOptions1> options)
{
_accessTokenGetter = accessTokenGetter;
_config = options.Value;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
//somehow convert _config to AccessTokenConfig
request.Headers.Authorization = await _accessTokenGetter.GetAccessToken(_config)
return await base.SendAsync(request, cancellationToken);
}
}
public class MyHandler2 : DelegatingHandler
{
//same implementation as MyHandler1, just use MyHandler2Options instead
}
Register my services:
//configurations
builder.Services.AddOptions<MyHandler1Options>()
.Configure<IConfiguration>((config, configuration) =>
{
configuration.GetSection("MyHandler1Options").Bind(config);
});
builder.Services.AddOptions<MyHandler2Options>()
.Configure<IConfiguration>((config, configuration) =>
{
configuration.GetSection("MyHandler2Options").Bind(config);
});
//AccessTokenGetter
services.AddHttpClient<AccessTokenGetter>()
//Services that need to use MyHandlers:
services.AddHttpClient<Service1>()
.AddHttpMessageHandler<MyHandler1>();
services.AddHttpClient<Service2>()
.AddHttpMessageHandler<MyHandler2>();
services.AddHttpClient<Service3>()
.AddHttpMessageHandler<MyHandler2>();
Is there a better solution? I am not a great fan of my idea, it is not very flexible.
services.AddHttpClient<Service1>()
.AddHttpMessageHandler(sp =>
{
var handler = sp.GetRequiredService<MyHandler>();
handler.Foo = "Bar";
return handler;
});
services.AddHttpClient<Service2>()
.AddHttpMessageHandler(sp =>
{
var handler = sp.GetRequiredService<MyHandler>();
handler.Foo = "Baz";
return handler;
});
I'm using dependency injection with HttpClient and I'm trying to figure out how to set a baseurl, but can't seem to figure it out.
I'm doing it this way:
public static async Task<HttpResponseMessage> PostUser(User user) {
var services = new ServiceCollection();
services.UseServices();
var serviceProvider = services.BuildServiceProvider();
var service = serviceProvider.GetRequiredService<IUserService>();
return await service.PostUser(user);
}
class UserService : IUserService
{
private readonly HttpClient _httpClient;
public UserService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<HttpResponseMessage> PostUser(User user)
{
HttpResponseMessage response = await _httpClient.PostAsJsonAsync(BASEURL, user);
return response;
}
}
I register in this way:
public static class Bootstrapper
{
public static void UseServices(this IServiceCollection services)
{
services.AddHttpClient<IUserService, UserService>();
}
}
So I want to use the BASEURL in the above, but how can I pass it with the httpClient?
This should work:
public static class Bootstrapper
{
public static void UseServices(this IServiceCollection services)
{
services.AddHttpClient<IUserServicee, UserService>(
client => client.BaseAddress = new Uri("YOUR_BASE_ADDRESS"));
}
}
Method overload takes Action<HttpClient> as an argument so it's void and you can mutate your HttpClient instance in the way you want.
I'm facing a strange issue since I created a view component in my app.
All my integration tests using an HttpClient start failing with response code "Internal Server Error".
Here the test configuration :
public class BaseTest<TStartup>
: WebApplicationFactory<TStartup> where TStartup : class
{
private readonly bool _hasUser;
private readonly HttpClient _client;
protected BaseTest(bool hasUser = false)
{
_hasUser = hasUser;
_client = CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false,
});
}
protected async Task<HttpResponseMessage> GetPageByPath(string path)
{
return await _client.GetAsync(path);
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
if (_hasUser)
{
services.AddScoped<IAuthenticationService, AuthenticationServiceStub>();
services.AddSingleton<IStartupFilter, FakeUserFilter>();
services.AddMvc(options =>
{
options.Filters.Add(new AllowAnonymousFilter());
options.Filters.Add(new AuthenticatedAttribute());
});
}
});
builder.ConfigureServices(services =>
{
// Create a new service provider.
ServiceProvider serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
// Build the service provider.
var sp = services.BuildServiceProvider();
// Create a scope to obtain a reference to the database
// context (ApplicationDbContext).
using (var scope = sp.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var logger = scopedServices
.GetRequiredService<ILogger<BaseTest<TStartup>>>();
}
});
}
}
}
And a usage example :
public class BasicTest : BaseTest<Startup>
{
public BasicTest() : base()
{
}
[Theory]
[InlineData("/")]
[InlineData("/Index")]
[InlineData("/Users/SignOut")]
[Trait("Category", "Integration")]
public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
{
// Act
var response = await GetPageByPath(url);
// Assert
response.EnsureSuccessStatusCode(); // Status Code 200-299
Assert.Equal("text/html; charset=utf-8",
response.Content.Headers.ContentType.ToString());
}
}
If you need the component code let me know.
I already rollback code to check when this start happening, and it's start after the commit with the new Component called in several pages.
I want to access JwtHelper from ExceptionHelper. But problem is ExceptionHelper must be static. And so, we can't create constructor and not access jwtHelper Method. How can I achieve access jwHelper from ExcewptionHelper.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddMvc();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddDbContext<MyDbContext>();
services.AddTransient<IUnitOfWork, UnitOfWork>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseExceptionHandler(builder => builder.Run(async context =>
{
var error = context.Features.Get<IExceptionHandlerFeature>();
context.Response.AddApplicationError(error);
await context.Response.WriteAsync(error.Error.Message);
}));
app.UseHttpsRedirection();
app.UseMvc();
}
ExceptionHelper.cs
public static class ExceptionHelper
{
public static async Task AddApplicationError(this HttpResponse response)
{
Log log = new Log();
log.UserId = jwtHelper.GetValueFromToken(token, "UserId");??????
//in this line I can't access jwtHelper.
}
}
JwtHelper.cs
public class JwtHelper : IJwtHelper
{
private readonly IHttpContextAccessor httpContextAccessor;
public JwtHelper(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public string GetValueFromToken(string stream, string propertyName)
{
var jwt = httpContextAccessor.HttpContext.Request.Headers["Authorization"];
var handler = new JwtSecurityTokenHandler();
var tokens = handler.ReadToken(stream.Replace("Bearer ", "")) as JwtSecurityToken;
return tokens.Claims.FirstOrDefault(claim => claim.Type == propertyName).Value;
}
}
If I were you I would register JwtHelper with a Interface known as IJwtHelper.
It would look like this then
public class JwtHelper : IJwtHelper
{
private readonly IHttpContextAccessor httpContextAccessor;
public JwtHelper(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public string GetValueFromToken(string propertyName)
{
var jwt= httpContextAccessor.HttpContext.Request.Headers["Authorization"];
// I can't access httpContextAccessor in this line.
var handler = new JwtSecurityTokenHandler();
var tokens = handler.ReadToken(jwt) as JwtSecurityToken;
return tokens.Claims.FirstOrDefault(claim => claim.Type == propertyName).Value;
}
}
public interface IJwtHelper
{
string GetValueFromToken(string propertyName);
}
In my startup.cs class I would then do
services.AddSingleton<IJwtHelper, JwtHelper>();
And then when you want to access your helper I would inject IJwtHelper
private IJwtHelper _jwtHelper;
public SomeConstructerOnClass(IJwtHelper jwtHelper)
{
_jwtHelper = jwtHelper;
}
public void SomeMethod(string property) {
var token = _jwtHelper.GetValueFromToken(property);
//Do something with token
}
where _jwtHelper is field of type IJwtHelper.
You will then be able to use GetValueFromToken quite fine anywhere you inject IJwtHelper
UPDATE
Your problem is that ExceptionHandler is Static , implement an interface and add it to container
public class ExceptionHelper : IExceptionHelper
{
private IJwtHelper _jwtHelper;
public ExceptionHelper(IJwtHelper jwtHelper)
{
_jwtHelper = jwtHelper;
}
public async Task AddApplicationError(this HttpResponse response)
{
Log log = new Log();
log.UserId = _jwtHelper.GetValueFromToken(token, "UserId");??????
}
}
public interface IExceptionHelper
{
Task AddApplicationError( HttpResponse response);
}
Then
services.AddSingleton<IExceptionHelper, ExceptionHelper>();
Now You will be able to inject it into your Configure method like so
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IExceptionHelper exceptionHelper)
{
app.UseExceptionHandler(builder => builder.Run(async context =>
{
var error = context.Features.Get<IExceptionHandlerFeature>();
//Resolved and available!
exceptionHelper.AddApplicationError(error);
await context.Response.WriteAsync(error.Error.Message);
}));
app.UseHttpsRedirection();
app.UseMvc();
}
If you follow me advice above from my initial response and my update everything should be fine and registered nicely in your container :)
You'll have to instantiate the JwtHelper class in order to access the instance variable (httpContextAccessor) from another class. Static methods, like GetValueFromToken, cannot access instance variables.