Xamarin Restart HttpClientFactory - c#

In my Xamarin app I get an exception on app startup on both Android and iOS.
"Cannot access a disposed object. Object name: 'MobileAuthenticatedStream'"
This seems to be happening after a background fetch has been run.
I am initializing my httpclientfactory like so
static void Init()
{
var host = new HostBuilder()
.ConfigureHostConfiguration(c =>
{
c.AddCommandLine(new string[] { $"ContentRoot={FileSystem.AppDataDirectory}" });
})
.ConfigureServices((c, x) =>
{
ConfigureServices(c, x);
})
.Build();
ServiceProvider = host.Services;
}
static void ConfigureServices(HostBuilderContext ctx, IServiceCollection services)
{
services.AddSingleton<Helpers.ClientService>();
services.AddHttpClient("webClient", client =>
{
client.DefaultRequestHeaders.Authorization = GenerateAuthHeader();
})
.AddPolicyHandler(GetRetryPolicy());
}
Then getting the client for use like
public class ClientService
{
IHttpClientFactory _httpClientFactory;
public ClientService(IHttpClientFactory factory)
{
_httpClientFactory = factory;
}
public HttpClient GetClient()
{
return _httpClientFactory.CreateClient(App.ClientName);
}
}
Helpers.ClientService service = App.ServiceProvider.GetService<Helpers.ClientService>();
HttpClient client = service.GetClient();
HttpResponseMessage response = await client.GetAsync(url);
On app start and resume I am re-running Init method.
Is this re-initializing the factory correctly, if not how should I implement this functionality

Related

Accessing external parameters/values within HttpClientFactory definition in ASP.NET core

I've an httpclient which uses Polly policy :
services.AddHttpClient("MyClient", (serviceProvider, client) =>
{
var X = External_PARAMETER;
client.BaseAddress = new Uri(settings.BaseUrl);
.....
})
.AddPolicyHandler(retryPolicy)
.AddPolicyHandler((provider, httpRequestMessage) => GetTokenPolicy(provider, httpRequestMessage));
When creating the clienthttp from this named client, I want to dynamically pass some paramters to it like:
var httpClient = httpClientFactory.CreateClient("MyClient", External_PARAMETER);
I know that one solution is actually to use a singleton service like this and pass values:
As far as I know you can't use a transient or scoped service inside AddHttpClient definition. If I use a non-sington one I get this error (Cannot resolve scoped service 'MyApp.IMyService' from root provider.))
services.AddHttpClient("MyClient", (serviceProvider, client) =>
{
var passedServer = serviceProvider.GetService<IMyService>();
var X = passedServer.GetMyParameter();
client.BaseAddress = new Uri(settings.BaseUrl);
.....
})
.AddPolicyHandler(retryPolicy)
.AddPolicyHandler((provider, httpRequestMessage) => GetTokenPolicy(provider, httpRequestMessage));
services.AddSingleton<IMyService, MyService>();
.
.
.
myServiceInstance.SetMyParameter("A_VALUE");
var httpClient = httpClientFactory.CreateClient("MyClient");
But how can I achieve this without using a singleton service like MyService?
There are cases where the HTTP request should create an HttpClient and having a sington service means all request will have access to this singleton service which is not ideal.
As far as I know, we could try to set the DelegatingHandler for httpclient and this DelegatingHandler could be registered as the Transient.
More details, you could refer to below example:
Handler:
public class TraceLogHandler : DelegatingHandler
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<TraceLogHandler> _logger;
private readonly bool _canLog;
private StringValues ipAddress;
public TraceLogHandler(IHttpContextAccessor httpContextAccessor, ILogger<TraceLogHandler> logger)
{
_httpContextAccessor = httpContextAccessor;
_logger = logger;
}
public TraceLogHandler(IHttpContextAccessor httpContextAccessor, ILogger<TraceLogHandler> logger, bool canLog)
{
_httpContextAccessor = httpContextAccessor;
_logger = logger;
_canLog = canLog;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// here you could modify the http request
int i = 0;
return new HttpResponseMessage();
//blah blah code for custom logging
}
}
Startup.cs:
services.AddTransient<TraceLogHandler>();
services.AddHttpClient<ICountryRepositoryClient, CountryRepositoryClientV2>()
.ConfigureHttpClient(c => c.BaseAddress = new Uri("https://localhost"))
.AddHttpMessageHandler((services) =>
{
return new TraceLogHandler(services.GetRequiredService<IHttpContextAccessor>(),
services.GetRequiredService<ILogger<TraceLogHandler>>());
});
What you could try is Options pattern in .NET.
Instead of creating one singleton service providing the value, it enables you to create and access a set of named singleton settings, so you could create one setting per named HTTP client:
class MyHttpClientOptions
{
public string BaseUri { get; set; }
}
services.Configure<MyHttpClientOptions>("MyClient", opt =>
{
opt.BaseUri = "https://url1";
});
services.Configure<MyHttpClientOptions>("MyClient2", opt =>
{
opt.BaseUri = "https://url2";
});
You can access the named settings when creating the HttpClient:
services.AddHttpClient("MyClient", (serviceProvider, client) =>
{
var options = serviceProvider
.GetRequiredService<IOptionsMonitor<MyHttpClientOptions>>()
.Get("MyClient");
client.BaseAddress = new Uri(options.BaseUri);
});
UPDATE
If some values really have to be fetched from the request's context, and can't be set to the client from where it gets created, you could add a custom builder which uses IHttpContextAccessor:
services.AddHttpClient("MyClient1");
services.AddOptions<HttpClientFactoryOptions>("MyClient1")
.Configure<IHttpContextAccessor>((opt, httpContextAccessor) =>
{
opt.HttpClientActions.Add(client =>
{
var httpContext = httpContextAccessor.HttpContext;
// If you have scoped service to create in the request context
httpContext.RequestServices.GetRequiredService<MyScopedService>();
// Shared key value pairs
httpContext.Items["some key"]
});
});

How to register typed httpClient service with autofac?

I'm creating MVC web application which calls an api using .net core 2.2 using separate HttpClients to call each controller (same api).
Ex:
For user controller actions : UserService (httpclient)
For post controller actions : PostService (httpclient)
In startup.cs I use DI as:
services.AddHttpClient<IUserService, UserService>();
services.AddHttpClient<IPostService, PostService>();
In my handler :
public class CommandHandler : IRequestHandler<Command, BaseResponse>
{
private readonly IUserService _userService;
public CommandHandler(IUserService userService)
{
_userService = userService;
}
public Task<BaseResponse> Handle(Command request, CancellationToken cancellationToken)
{
throw new System.NotImplementedException();
}
}
But when invoking command handler I get this error:
None of the constructors found with
'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type
'xxx.Application.Services.Users.UserService' can be invoked with
the available services and parameters: Cannot resolve parameter
'System.Net.Http.HttpClient httpClient' of constructor 'Void
.ctor(System.Net.Http.HttpClient,
xxx.Application.Configurations.IApplicationConfigurations,
Microsoft.Extensions.Logging.ILogger`1[xxx.Application.Services.Users.UserService])'.
But I've registered services in autofac module:
public class ServiceModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(typeof(ServiceModule).Assembly)
.Where(t => t.Namespace.StartsWith("xxx.Application.Services"))
.AsImplementedInterfaces().InstancePerLifetimeScope();
}
}
Here is my UserService class constructor:
public UserService (HttpClient httpClient, IApplicationConfigurations applicationConfig, ILogger<UserService> logger)
{
_httpClient = httpClient;
_applicationConfig = applicationConfig;
_logger = logger;
_remoteServiceBaseUrl = $"{_applicationConfig.WebApiBaseUrl}";
}
I have two questions:
What does the above error mean?
Is it good practice to use separate httpclients for different controllers in api?
By doing
services.AddHttpClient<IUserService, UserService>();
You will configure the native .net core dependency injection to inject HttpClient to UserService when a IUserService is requested.
Then you do
builder.RegisterAssemblyTypes(typeof(ServiceModule).Assembly)
.Where(t => t.Namespace.StartsWith("xxx.Application.Services"))
.AsImplementedInterfaces().InstancePerLifetimeScope();
which will erase the native dependency injection configuration for IUserService. The IUserService is now registered with UserService without any HttpClient in mind.
The simplest way to add HttpClient would be to register it like this :
builder.Register(c => new HttpClient())
.As<HttpClient>();
or
services.AddHttpClient(); // register the .net core IHttpClientFactory
builder.Register(c => c.Resolve<IHttpClientFactory>().CreateClient())
.As<HttpClient>();
If you want to configure your httpclient for a specific service you can create an autofac module which add parameters like this :
public class HttpClientModule<TService> : Module
{
public HttpClientModule(Action<HttpClient> clientConfigurator)
{
this._clientConfigurator = clientConfigurator;
}
private readonly Action<HttpClient> _clientConfigurator;
protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration)
{
base.AttachToComponentRegistration(componentRegistry, registration);
if (registration.Activator.LimitType == typeof(TService))
{
registration.Preparing += (sender, e) =>
{
e.Parameters = e.Parameters.Union(
new[]
{
new ResolvedParameter(
(p, i) => p.ParameterType == typeof(HttpClient),
(p, i) => {
HttpClient client = i.Resolve<IHttpClientFactory>().CreateClient();
this._clientConfigurator(client);
return client;
}
)
});
};
}
}
}
Then
builder.RegisterModule(new HttpClientModule<UserService>(client =>
{
client.BaseAddress = new Uri("https://api.XXX.com/");
client.DefaultRequestHeaders.Add("Accept", "application/vnd.XXX.v3+json");
client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-XXX");
}));
Cyril's implementation of using an Autofac module works wonderfully, but unfortunately is not compatible with Autofac 6.0+.
In order to configure an HttpClient in Autofac 6.0+ for a specific service, an Autofac middleware needs to be implemented:
public class HttpClientMiddleware<TService> : IResolveMiddleware
{
private readonly Action<HttpClient> _clientConfigurator;
public HttpClientMiddleware(Action<HttpClient> clientConfigurator)
{
_clientConfigurator = clientConfigurator;
}
public PipelinePhase Phase => PipelinePhase.ParameterSelection;
public void Execute(ResolveRequestContext context, Action<ResolveRequestContext> next)
{
if (context.Registration.Activator.LimitType == typeof(TService))
{
context.ChangeParameters(context.Parameters.Union(
new[]
{
new ResolvedParameter(
(p, _) => p.ParameterType == typeof(HttpClient),
(_, i) => {
var client = i.Resolve<IHttpClientFactory>().CreateClient();
_clientConfigurator(client);
return client;
}
)
}));
}
next(context);
}
}
Then the service can be registered, utilizing the middleware:
builder.RegisterType<UserService>()
.As<IUserService>()
.ConfigurePipeline(p =>
{
p.Use(new HttpClientMiddleware<UserService>(client =>
{
client.BaseAddress = new Uri("https://api.XXX.com/");
client.DefaultRequestHeaders.Add("Accept", "application/vnd.XXX.v3+json");
client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-XXX");
}));
});
You can register any type with httpclient as follows extension method.
public static ContainerBuilder RegisterWithHttpClient<TInterface, TClass>(this ContainerBuilder builder, Action<IComponentContext, HttpClient> config)
where TClass: class
{
builder
.RegisterType<TClass>()
.AsSelf()
.As<TInterface>()
.WithParameter(new ResolvedParameter(
(info, context) => info.ParameterType.IsAssignableFrom(typeof(HttpClient)),
(info, context) =>
{
var httpClient = context.Resolve<IHttpClientFactory>().CreateClient();
config.Invoke(context, httpClient);
return httpClient;
}
))
.InstancePerLifetimeScope();
return builder;
}
and register your type.
//in startup.cs or autofac module.
public void ConfigureContainer(ContainerBuilder container)
{
container.RegisterWithHttpClient<IEmailSender, MyEmailSender>((context, client) =>
{
var settings = context.Resolve<IOptionsSnapshot<EmailSenderSettings>>().Value;
client.BaseAddress = new Uri($"{settings.ApiBaseUrl.TrimEnd('/')}/");
client.Timeout = TimeSpan.FromSeconds(settings.TimeoutSeconds);
});
}

DI of httpclientfactory in a backgroundservice for a console app

I have a console program with a simple consuming backgroundservice which need to call function from a scopedservice that uses a HttpClientFactory to call an external API and return result to consuming backgroundservice.
I want very simple after looking at a couple example online to remove all possible complexity from the code.
public class Program
{
public static async Task Main(string[] args)
{
var host = new HostBuilder()
.ConfigureLogging((hostContext, config) =>
{
config.AddConsole();
})
.ConfigureAppConfiguration((hostContext, config) =>
{
config.AddJsonFile("appsettings.json", optional: true);
config.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true);
config.AddCommandLine(args);
})
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<ConsumeMessageService>();
services.AddScoped<IScopedArcGisServices, ScopedArcGisServices>();
})
.UseConsoleLifetime()
.Build();
using (host)
{
// Start the host
await host.StartAsync();
// Wait for the host to shutdown
await host.WaitForShutdownAsync();
}
}
}
public class ConsumeMessageService : IHostedService
{
private readonly ILogger _logger;
public IServiceProvider _serviceProvider { get; }
public ConsumeMessageService(IServiceProvider services,
ILogger<ConsumeMessageService> logger)
{
_serviceProvider = services;
_logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation(
"Consume message service hosted service is starting.");
DoWork();
return Task.CompletedTask;
}
private void DoWork()
{
_logger.LogInformation(
"Consume message service hosted service is working.");
using (var scope = _serviceProvider.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedServices>();
scopedProcessingService.DoWork();
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is stopping.");
return Task.CompletedTask;
}
}
internal interface IScopedServices
{
void DoWork();
}
internal class ScopedServices : IScopedServices
{
private readonly ILogger _logger;
private readonly IHttpClientFactory _clientFactory;
public string JsonResult { get; private set; }
public ScopedServices(ILogger<ScopedServices> logger, IHttpClientFactory clientFactory)
{
_logger = logger;
_clientFactory = clientFactory;
}
public void DoWork()
{
_logger.LogInformation("Scoped processing service is working.");
}
}
As soon as I had the scopedservice with the HttpClientFactory I get this message :
Unable to resolve service for type
'System.Net.Http.IHttpClientFactory' while attempting to activate
'Integration.BackgroundService.Services.ScopedServices'.'
IHttpClientFactory is not added by default.
You have to call services.AddHttpClient() when configuring services to add access to the factory and its related clients.
//...
.ConfigureServices((hostContext, services) => {
services.AddHttpClient(); //<-- THIS IS NEEDED
services.AddHostedService<ConsumeMessageService>();
services.AddScoped<IScopedArcGisServices, ScopedArcGisServices>();
})
//...
For more on how to configure the clients created by the factory,
Reference Use HttpClientFactory to implement resilient HTTP requests

asp.net core - Integration test and view components

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.

StructureMap .Net Core Windows Service Nested Containers

There are lots of articles talking about how to use Structure Map with ASP.NET Core, but not very many talking about console applications or windows services. The default behavior in ASP.Net Core is that StructureMap creates a Nested Container per HTTPRequest so that a concrete class will be instantiated only once per HTTP Request.
I am creating a .Net Core Windows Service using the PeterKottas.DotNetCore.WindowsService nuget package. I setup StructureMap using this article: https://andrewlock.net/using-dependency-injection-in-a-net-core-console-application/
My windows service is setup on a Timer and performs an action every X number of seconds. I want each of these actions to use a nested container similar to how ASP.NET does it. In other words, I want everything created for polling pass #1 to be disposed of once that polling pass completes. When polling pass #2 starts I want all new instances of objects to be instantiated. However, within the scope of a single polling pass I only want one instance of each object to be created.
What is the proper way to do this?
Here is my program class
public class Program
{
public static ILoggerFactory LoggerFactory;
public static IConfigurationRoot Configuration;
static void Main(string[] args)
{
var applicationBaseDirectory = AppContext.BaseDirectory;
string environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
if (string.IsNullOrWhiteSpace(environment))
throw new ArgumentNullException("Environment not found in ASPNETCORE_ENVIRONMENT");
ConfigureApplication(applicationBaseDirectory, environment);
var serviceCollection = ConfigureServices();
var serviceProvider = ConfigureIoC(serviceCollection);
ConfigureLogging(serviceProvider);
var logger = LoggerFactory.CreateLogger<Program>();
logger.LogInformation("Starting Application");
ServiceRunner<IWindowsService>.Run(config =>
{
var applicationName = typeof(Program).Namespace;
config.SetName($"{applicationName}");
config.SetDisplayName($"{applicationName}");
config.SetDescription($"Service that matches Previous Buyers to Vehicles In Inventory to Fine Upgrade Opportunities.");
config.Service(serviceConfig =>
{
serviceConfig.ServiceFactory((extraArgs, microServiceController) =>
{
return serviceProvider.GetService<IWindowsService>();
});
serviceConfig.OnStart((service, extraArgs) =>
{
logger.LogInformation($"Service {applicationName} started.");
service.Start();
});
serviceConfig.OnStop((service =>
{
logger.LogInformation($"Service {applicationName} stopped.");
service.Stop();
}));
serviceConfig.OnError(error =>
{
logger.LogError($"Service {applicationName} encountered an error with the following exception:\n {error.Message}");
});
});
});
}
private static void ConfigureApplication(string applicationBaseDirectory, string environment)
{
Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);
var builder = new ConfigurationBuilder()
.SetBasePath(applicationBaseDirectory)
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{environment}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
private static IServiceCollection ConfigureServices()
{
var serviceCollection = new ServiceCollection().AddLogging().AddOptions();
serviceCollection.AddDbContext<JandLReportingContext>(options => options.UseSqlServer(Configuration.GetConnectionString("JandLReporting")), ServiceLifetime.Transient);
//serviceCollection.AddDbContext<JLMIDBContext>(options => options.UseSqlServer(Configuration.GetConnectionString("JLMIDB")), ServiceLifetime.Scoped);
serviceCollection.Configure<TimerSettings>(Configuration.GetSection("TimerSettings"));
serviceCollection.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
return serviceCollection;
}
private static IServiceProvider ConfigureIoC(IServiceCollection serviceCollection)
{
//Setup StructureMap
var container = new Container();
container.Configure(config =>
{
config.Scan(scan =>
{
scan.AssemblyContainingType(typeof(Program));
scan.AssembliesFromApplicationBaseDirectory();
scan.AssembliesAndExecutablesFromApplicationBaseDirectory();
scan.WithDefaultConventions();
});
config.Populate(serviceCollection);
});
return container.GetInstance<IServiceProvider>();
}
private static void ConfigureLogging(IServiceProvider serviceProvider)
{
LoggerFactory = serviceProvider.GetService<ILoggerFactory>()
.AddConsole(Configuration.GetSection("Logging"))
.AddFile(Configuration.GetSection("Logging"))
.AddDebug();
}
}
Here is my WindowsService class:
public class WindowsService : MicroService, IWindowsService
{
private readonly ILogger _logger;
private readonly IServiceProvider _serviceProvider;
private readonly TimerSettings _timerSettings;
public WindowsService(ILogger<WindowsService> logger, IServiceProvider serviceProvider, IOptions<TimerSettings> timerSettings)
{
_logger = logger;
_serviceProvider = serviceProvider;
_timerSettings = timerSettings.Value;
}
public void Start()
{
StartBase();
Timers.Start("ServiceTimer", GetTimerInterval(), async () =>
{
await PollingPassAsyc();
},
(error) =>
{
_logger.LogCritical($"Exception while starting the service: {error}\n");
});
}
private async Task PollingPassAsyc()
{
using (var upgradeOpportunityService = _serviceProvider.GetService<IUpgradeOpportunityService>())
{
await upgradeOpportunityService.FindUpgradeOpportunitiesAsync();
}
}
private int GetTimerInterval()
{
return _timerSettings.IntervalMinutes * 60 * 1000;
}
public void Stop()
{
StopBase();
_logger.LogInformation($"Service has stopped");
}
}
There is extension method CreateScope for IServiceProvider in Microsoft.Extensions.DependencyInjection namespace. What it does is resolve special interface (IServiceScopeFactory) from current DI container, which is responsible for creating new scopes, and creates new scope using this factory. StructureMap registers implementation of this interface, so when you call CreateScope - StructureMap will create nested container. Sample usage:
using (var scope = _serviceProvider.CreateScope()) {
// use scope.ServiceProvider, not _serviceProvider to resolve instance
var service = scope.ServiceProvider.GetService<IUpgradeOpportunityService>‌​();
}

Categories