Add Custom TypedClient with Refit - c#

I am having issue when adding a custom client with Refit. I dont want default httpClient. The reason to do is that I dont want my consumers to change httpclient configuration when they are consuming my refit implemented Client. Separation of concerns I guess.
I have a class CustomHttpClient which is inherited from httpClient.
var client = new CustomHttpClient();
serviceCollection.AddRefitClient<IMyAPi>().AddTypedClient(sp => client).ConfigureHttpClient((serviceProvider, httpClient) =>
{
var options = serviceProvider.GetRequiredService<IOptions<CustomClientOptions>>().Value;
httpClient.BaseAddress =
new Uri(
$"{options.ApiProtocol}://{options.ApiHost}/{options.ApiVersion}");
}).AddHttpMessageHandler<CustomHttpClientHandler>();
CustomHttpClient.cs
public class CustomHttpClient : HttpClient
{
public CustomHttpClient(HttpMessageHandler handler) : base(handler)
{
}
public CustomHttpClient()
{
}
}
I am getting exception:
System.InvalidOperationException: 'ValueFactory attempted to access
the Value property of this instance.'
AddTypedClient adds the client as Transient but I want it as Singleton. I am not sure how to tell Refit to use my CustomHttpClient & CustomHttpClientHandler which should be singleton?
Update:
I tried below code and its giving new exception.
serviceCollection.AddSingleton<CustomHttpClient>();
serviceCollection.AddSingleton<CustomHttpClientHandler>();
serviceCollection.AddHttpClient<CustomHttpClient>(c => c.BaseAddress = new Uri("http://dummy.com"));
serviceCollection.AddRefitClient<IMyAPi>().AddHttpMessageHandler<CustomHttpClientHandler>();
System.InvalidOperationException: 'A suitable constructor for type
'BBC.Studios.Rightsline.Client.RightslineHttpClient' could not be
located. Ensure the type is concrete and services are registered for
all parameters of a public constructor.'

Related

AddHttpClient<T> configureClient method is not executing

I thought I understood how AddHttpClient worked, but apparently, I do not. I've distilled this problem down to the very basics, and it still isn't functioning as I expect.
I have the following class and interface:
public interface ISomeService
{
Uri BaseAddress { get; }
}
public class SomeService : ISomeService
{
private readonly HttpClient _client;
public SomeService(HttpClient client)
{
_client = client;
}
public Uri BaseAddress => _client.BaseAddress;
}
Simply exposing the BaseAddress for the purposes of this example.
Now, I perform the following:
[Fact]
public void TestClient()
{
var services = new ServiceCollection();
services.AddHttpClient<SomeService>((serviceProvider, client) =>
{
client.BaseAddress = new Uri("http://fakehost");
});
services.AddSingleton<ISomeService, SomeService>();
var provider = services.BuildServiceProvider();
var svc = provider.GetRequiredService<ISomeService>();
svc.BaseAddress.Should().Be("http://fakehost");
}
and it fails, because the base address is null, instead of http://fakehost like I expect.
This is because somehow, SomeService gets an HttpClient created for it without going through my configure method (I added a breakpoint, it never got hit). But like magic I still an actual constructed instance of SomeService.
I've tried adding the HttpClient typed to the interface instead, no luck.
I found that if I GetRequiredService<SomeService>, instead of for ISomeService the code behaves as expected. But I don't want people injecting concrete SomeService. I want them to inject an ISomeService.
What am I missing? How can I inject an HttpClient already configured with a base address to a service that will be, itself, injected via DI (with other potential dependencies as well).
Background: I'm building a client library with ServiceCollectionExtensions so that consumers can just call services.AddSomeService() and it will make ISomeService available for injection.
EDIT:
I have since found (through experimentation) that .AddHttpClient<T> seems to add an HttpClient for T only when explicitly trying to resolve T, but also adds a generic HttpClient for any other class.
commenting out the AddHttpClient section of my test resulted in failed resolution but changing the to AddHttpClient<SomeOtherClass> allowed ISomeService to resolve (still with null BaseAddress though).
EDIT 2:
The example I posted originally said I was registering ISomeService as a singleton, which I was, but after inspection, I've realized I don't need it to be so switching to transient allows me to do
.AddHttpClient<ISomeService, SomeService>(...)
Internally build in DI works with ServiceDescriptors which represent implementation type-service type pairs. AddHttpClient<SomeService> registers SomeService as SomeService which has nothing to do with ISomeService from DI standpoint, just provide service type and implementation type:
services.AddHttpClient<ISomeService, SomeService>((serviceProvider, client) =>
{
client.BaseAddress = new Uri("http://fakehost");
});
Though it will register the service type as transient.
If you need to have it as singleton - you can try reabstracting (though it should be done with caution as mentioned in the comments):
services.AddHttpClient<SomeService>((serviceProvider, client) =>
{
client.BaseAddress = new Uri("http://fakehost");
});
services.AddSingleton<ISomeService>(sp => sp.GetRequiredService<SomeService>());

Call API from AccountClaimsPrincipalFactory in Blazor WASM

I am using Blazor WASM with AzureB2C to call an API hosted in Azure Functions. I would like to call my API on a successful login to add/update user info into a database. I have been following this guide. When trying to inject my typed httpclient into the AccountClaimsPrincipalFactory I am met with a runtime error:
Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: ValueFactory attempted to access the Value property of this instance.
System.InvalidOperationException: ValueFactory attempted to access the Value property of this instance.
This shows in the browser, but the app compiles and runs just fine. The codes works great if I don't inject my PlatformServiceClient, but I need to make the API call to record the user. The following files are involved. I adjusted some things to simplify. This seems like the appropriate approach, but I have not seen examples where an api call was made in the claims factory.
CustomAccountFactory.cs
public class CustomAccountFactory: AccountClaimsPrincipalFactory<CustomUserAccount>
{
public IPlatformServiceClient client { get; set; }
public CustomAccountFactory(NavigationManager navigationManager,
IPlatformServiceClient platformServiceClient,
IAccessTokenProviderAccessor accessor) : base(accessor)
{
client = platformServiceClient;
}
public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
CustomUserAccount account, RemoteAuthenticationUserOptions options)
{
var initialUser = await base.CreateUserAsync(account, options);
if (initialUser.Identity.IsAuthenticated)
{
//call the API here
await client.RegisterUserAsync();
}
return initialUser;
}
}
Program.cs excerpt
builder.Services.AddScoped<CustomAuthorizationMessageHandler>();
builder.Services.AddHttpClient<IPlatformServiceClient, PlatformServiceClient>(
client => client.BaseAddress = new Uri(builder.Configuration["PlatformServiceUrl"]))
.AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
builder.Services.AddMsalAuthentication<RemoteAuthenticationState, CustomUserAccount>(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("openid");
options.ProviderOptions.DefaultAccessTokenScopes.Add("offline_access");
options.ProviderOptions.DefaultAccessTokenScopes.Add("access_as_user");
options.ProviderOptions.LoginMode = "redirect";
options.UserOptions.RoleClaim = "roles";
}).AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, CustomUserAccount, CustomAccountFactory>();
CustomAuthorizationMessageHandler.cs
public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
{
public CustomAuthorizationMessageHandler(IAccessTokenProvider provider,
NavigationManager navigationManager)
: base(provider, navigationManager)
{
ConfigureHandler(
authorizedUrls: new[] { "http://localhost:7071" },
scopes: new[] { "access_as_user" });
}
}
I solved this by creating a named instance of the client and passing an IHttpClientFactory into the CustomAccountFactory.
builder.Services.AddHttpClient<PlatformServiceClient>("PlatformServiceClient",
client => client.BaseAddress = new Uri(builder.Configuration["PlatformServiceUrl"]))
.AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
There I can create a client, but I have to setup my urls manually vs using the typed client where I have this work already done.
var client = factory.CreateClient("PlatformServiceClient");
var response = await client.GetAsync("/user/me");
I also registered the new client prior to calling AddMsalAuthenication
builder.Services.AddTransient(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("PlatformServiceClient"));
I did all of this following the code found here by Coding Flamingo. It is all working as expected.

Unable to resolve service for type 'System.Net.Http.HttpClient'

I created a ViewComponent class which call a REST API using the HttpClient, this is the code:
public class ProductsViewComponent : ViewComponent
{
private readonly HttpClient _client;
public ProductsViewComponent(HttpClient client)
{
_client = client ?? throw new ArgumentNullException(nameof(client));
}
public async Task<IViewComponentResult> InvokeAsync(string date)
{
using(var response = await _client.GetAsync($"/product/get_products/{date}"))
{
response.EnsureSuccessStatusCode();
var products = await response.Content.ReadAsAsync<List<Products>>();
return View(products);
}
}
}
I get this error:
InvalidOperationException: Unable to resolve service for type 'System.Net.Http.HttpClient' while attempting to activate MyApp.ViewComponents.ProductsViewComponent'
I injected the HttpClient in the ConfigureService method available in Startup in this way:
services.AddHttpClient<FixturesViewComponent>(options =>
{
options.BaseAddress = new Uri("http://80.350.485.118/api/v2");
});
UPDATE:
I registered the ProductsViewComponent too, same error.
I had a similar problem - the problem was in double registration:
services.AddHttpClient<Service>();
services.AddSingleton<Service>(); // fixed by removing this line
Similar examples [just adding to clarify that it's not specific to AddSingleton, nor related to the order.]
services.AddScoped<IService, Service>(); // fixed by removing this line
services.AddHttpClient<IService, Service>();
TLDR;
ViewComponents do not support typed clients out of the box. To resolve this, add a call to AddViewComponentsAsServices() onto the end of the call to services.AddMvc(...).
After a pretty long chat that ran off the back of being able to reproduce your issue, we determined initially that the problem being observed is specific to ViewComponents. Even with a call to IServiceCollection.AddHttpClient<SomeViewComponent>(), passing an instance of HttpClient into SomeViewComponents constructor just refused to work.
However, sitting a new class (SomeService) between SomeComponent and HttpClient works as expected. This is what the docs refer to as a typed client. The code looks a bit like this:
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<SomeService>();
// ...
}
// SomeService.cs
public class SomeService
{
public SomeService(HttpClient httpClient)
{
// ...
}
}
// SomeViewComponent.cs
public class SomeViewComponent
{
public SomeViewComponent(SomeService someService)
{
// ...
}
}
As I've already stated, this approach works - the ASP.NET Core DI system is very happy to create the instance of SomeService and its typed HttpClient instance.
To restate the original problem, take the following example code:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<SomeViewComponent>();
// ...
}
public class SomeViewComponent
{
public SomeViewComponent(HttpClient httpClient)
{
// ...
}
}
In this case, the ASP.NET Core DI system refuses to create an instance of SomeViewComponent due to not being able to resolve HttpClient. It turns out that this is not specific just to ViewComponents: it also applies to Controllers and TagHelpers (thanks to Chris Pratt for confirming for TagHelpers).
Interestingly, the following also works:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<SomeViewComponent>();
// ...
}
public class SomeViewComponent
{
public SomeViewComponent(IHttpClientFactory httpClientFactory)
{
var httpClient = httpClientFactory.CreateClient("SomeViewComponent")
// ...
}
}
In this example, we're taking advantage of the fact that the call to AddHttpClient<SomeViewComponent> registered a named client for us.
In order to be able to inject HttpClient directly into a ViewComponent, we can add a call to AddViewComponentsAsServices when we register MVC with DI:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(...)
.AddViewComponentsAsServices();
// ...
}
AddControllersAsServices and AddTagHelpersAsServices can also be called to add the same support for Controllers and TagHelpers respectively.
If we look at the docs more closely, it's clear that none of the examples there inject a HttpClient into Controllers et al - there's simply no mention of this approach at all.
Unfortunately, I don't know enough about the ASP.NET Core DI system in order to be able to explain exactly why this works the way it does: The information I've provided above simply explains the what along with a solution. Chris Pratt has opened an issue in Github for the docs to be updated to expand upon this.
I was getting a similar error in my Azure Function Version 2. As per this document, we should be able to add the IHttpClientFactory as a dependency. After adding this DI in my Azure Function, I was getting the error mentioned below.
Microsoft.Extensions.DependencyInjection.Abstractions: Unable to
resolve service for type 'System.Net.Http.IHttpClientFactory' while
attempting to activate
'OServiceBus.Adapter.FetchDataFromSubscription1'
The issue was that I had not override the Configure function to add the HttpClient as a registered dependency. So I just created a class called Statup in the root directory of my Azure Function as follows.
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
[assembly: FunctionsStartup(typeof(ServiceBus.Adapter.Startup))]
namespace ServiceBus.Adapter {
public class Startup: FunctionsStartup {
public override void Configure(IFunctionsHostBuilder builder) {
builder.Services.AddHttpClient();
}
}
}
After adding this, my function started working properly. Hope it helps.
I had a similar error message trying to inject a wrapper for an external REST service to my controller as an interface. I needed to change the following in ConfigureServices:
services.AddHttpClient<IMyServiceWrapper>("MyServiceWrapper", client =>
{
client.BaseAddress = new Uri("http://some_service/api");
}
to
services.AddHttpClient<IMyServiceWrapper, MyServiceWrapper>("MyServiceWrapper", client =>
{
client.BaseAddress = new Uri("http://some_service/api");
}
in order to be able to use the interface in the constructor of my controller:
public MyController(IMyServiceWrapper myService)
{
_myService = myService;
}
Useful for testing myController using a mock service.
It seems that you've got two view components mixed up. You're registering the FixturesViewComponent as a "named HTTP client" yet you attempt to inject an HttpClient instance in the ProductsViewComponent.
Changing the HttpClient registration to ProductsViewComponent should help:
services.AddHttpClient<ProductsViewComponent>(options =>
{
options.BaseAddress = new Uri("http://80.350.485.118/api/v2");
});
Maybe it will help, but in my situation this worked:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IMyService,MyService>(); // my usual DI injection of a service that can be mocked
services.AddHttpClient<IMyService,MyService>(client => {
client.BaseAddress = new Uri("https://myservice.com/api");
}); // notice that I use IMyService for the reference of the registration AND implementation to where it will be injected.
}
public class MyService
{
public MyService(HttpClient client)
{
// client.BaseAddress is properly set here
}
}
public class MyController : Controller
{
public MyController(IMyService service) // used by the interface
{}
}
I've tried services.AddHttpClient<IMyService>() as well, which would not resolve due to lack of it's constructor.
Also tried services.AddHttpClient<MyService>() as above, but it would not resolve the configured instance, as described above.
So the important part is that class that is used to reference the resolved type needs to be used. So this also works:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<MyService>(); // registering the type itself, not via interface
services.AddHttpClient<MyService>(client => {
client.BaseAddress = new Uri("https://myservice.com/api");
}); // it's ok here, since it will be resolved by it's own type name
}
public class MyService
{
public MyService(HttpClient client)
{
// client.BaseAddress is properly set here
}
}
public class MyController : Controller
{
public MyController(MyService service) // used by the type directly
{}
}
It kind of makes sense, but documentation and examples could be better.

WebApi - Autofac cannot resolve parameter HttpRequestMessage

I have an issue with RegisterHttpRequestMessage not working for me and cannot figure out what I'm doing wrong. This is specifically when I try to manually resolve a service that accepts the HttpMessageRequest as a parameter.
I'm using modules to register components in my builder, and currently my module in the main web project looks like this:
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterHttpRequestMessage(GlobalConfiguration.Configuration);
builder.RegisterType<SourceSystemViewModel>().AsImplementedInterfaces().InstancePerRequest();
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.Where(t => t.Name.EndsWith("ViewModelValidator"))
.AsImplementedInterfaces()
.PropertiesAutowired();
// Etc etc etc
SourceSystemViewModel is currently quite simple and looks like this:
public interface ISourceSystemViewModel
{
SourceSystem Value { get; }
}
public class SourceSystemViewModel : ISourceSystemViewModel
{
public SourceSystemViewModel(HttpRequestMessage request)
{
Value = request.Headers.GetSourceSystem();
}
public SourceSystem Value { get; }
}
GetSourceSystem is just an extension method that pulls out the header value. I have tried both registering SourceSystemViewModel with and without InstancePerRequest but it doesn't make a difference. The moment autofac tries to resolve ISourceSystemViewModal (and ultimately HttpRequestMessage) it throws this:
An exception of type
'Autofac.Core.Registration.ComponentNotRegisteredException' occurred
in Autofac.dll but was not handled in user code
Additional information: The requested service 'System.Net.Http.HttpRequestMessage' has not been registered. To avoid
this exception, either register a component to provide the service,
check for service registration using IsRegistered(), or use the
ResolveOptional() method to resolve an optional dependency.
Using autofac 3.5 (webapi dll is quoted as autofac.webapi2 3.4).
Any ideas much appreciated!
Notes
I'll add any findings as I come across them...
Update 1
I took a look at how RegisterHttpRequestMessage works and it does indeed add a message handler called CurrentRequestHandler to HttpConfiguration. When my request comes in I can see that this message handler still exists. So the method seems to do what it's supposed to, it's just not resolving the request message for me...
Update 2
I have noticed that while in the context of a controller and therefore have access to the HttpRequestMessage I can resolve both objects. Like this:
ILifetimeScope requestLifetimeScope = Request.GetDependencyScope().GetRequestLifetimeScope();
var h = requestLifetimeScope.Resolve<HttpRequestMessage>();
var sourceSystemViewModel = requestLifetimeScope.Resolve<ISourceSystemViewModel>();
Update 3
It's important to note that I am manually trying to resolve a service that is expecting the HttpMessageRequest as an injected parameter. For example, this fails for me:
using (var httpRequestScope = IocProxy.Container.Context.BeginLifetimeScope("AutofacWebRequest"))
{
var sourceSystemViewModel = httpRequestScope.Resolve<ISourceSystemViewModel>();
}
I had the same problem and based on SO I was able to get it working with
public class NLoggerTraceWriterModule : Module
{
private HttpConfiguration _config;
public NLoggerTraceWriterModule(HttpConfiguration config)
{
this._config = config;
}
protected override void Load(ContainerBuilder builder)
{
builder.RegisterInstance(this._config).As<HttpConfiguration>();
builder.Register(c =>
c.Resolve<HttpConfiguration>()
.Services
.GetService(typeof(ITraceWriter)) as ITraceWriter)
.As<ITraceWriter>();
}
}
registering this module as
// Call RegisterHttpRequestMessage to add the feature.
builder.RegisterHttpRequestMessage(config);
builder.RegisterModule(new Helper.Logging.NLoggerTraceWriterModule(config));
Even though this wasn't the issue for OP, here's another solution since this is currently the only Google result for "The requested service System.Net.Http.HttpRequestMessage' has not been registered."
If you've created an HttpConfiguration instance, make sure that's what you're passing to the registration function.
In my case, this was resolved by changing this:
builder.RegisterHttpRequestMessage(GlobalConfiguration.Configuration);
To this:
builder.RegisterHttpRequestMessage(Startup.HttpConfiguration);

ClientBase and https connection. The provided URI scheme 'https' is invalid; expected 'http'. Parameter name: via

I'm trying to use the ClientBase class for communicating to a REST service. Below is my code:
class MyService: ClientBase<IMyService>, IMyService
{
public GridVisionService(Uri baseAddress)
: base(new WebHttpBinding(), new EndpointAddress(baseAddress))
{
this.Endpoint.Behaviors.Add(new WebHttpBehavior());
}
#region IMyServiceMembers
public void DoSomething()
{
using (new OperationContextScope(this.InnerChannel)) // Line that throws the exception
{
WebOperationContext.Current.OutgoingRequest.Headers.Add("details", "details");
} // End of OperationContextScope
this.Channel.DoSomething();
}
#endregion
}
I run my class like the following:
MyService myService = new MyService(new Uri("https://localhost:8080/MyService/"));
However, it throws the exception about expecting HTTP instead of HTTPS. On searching I've found multiple instances of the same error, however in all of the cases I have found the app.config is configuring the connection. In my case I am not. Does anyone know how I can make my service expect to use https?
When you create the WebHttpBinding, set its Security property to Transport.
System.ServiceModel.WebHttpBinding w = new System.ServiceModel.WebHttpBinding();
w.Security = new System.ServiceModel.WebHttpSecurity();
w.Security.Mode = System.ServiceModel.WebHttpSecurityMode.Transport;

Categories