AddHttpContextAccessor in ConfigureServices vs per HttpClient - c#

Is there any difference between adding the httpContextAccessor one time in ConfigureServices method versus adding the HttpContextAccessor per HttpClient configured.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// FIRST VERSION
services.AddHttpContextAccessor();
// SECOND VERSION
var myService1 = services.AddHttpClient<TestHttpClient1>(c =>
{
c.BaseAddress = new Uri(Configuration["TestHttpClient1"]);
});
myService1.Services.AddHttpContextAccessor();
var myService2 = services.AddHttpClient<TestHttpClient2>(c =>
{
c.BaseAddress = new Uri(Configuration["TestHttpClient2"]);
});
myService2.Services.AddHttpContextAccessor();
}
My guess would be to think that in the second version, we have two singleton, one will be used for class TestHttpClient1 and the other one for TestHttpClient2 but I dont see why we do that because I saw this code in production.

Is there any difference between adding the httpContextAccessor one time in ConfigureServices method versus adding the HttpContextAccessor per HttpClient configured.
No, there's no difference whatsoever. myService1.Services and myService2.Services both reference the same IServiceCollection as the services variable. The first call (services.AddHttpContextAccessor()) will register the service, but the next two calls (myService1.Services.AddHttpContextAccessor() and myService2.Services.AddHttpContextAccessor()) will no-op (do nothing).
To put that all in to context, here's an extract from the source code for AddHttpClient<TClient>(...) (source):
var builder = new DefaultHttpClientBuilder(services, name);
// ...
return builder;
A new instance of DefaultHttpClientBuilder is created, which wraps up the IServiceCollection that's passed in. As this is an extension method, services here refers to the same services as in your ConfigureServices method. This is then exposed through IHttpClientBuilder.Services, which is what you're using when you reference e.g. myService1.Services.
The call to AddHttpContextAccessor uses TryAddSingleton, which will register the service only if it hasn't already been registered (source):
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
In your example, it has already been registered by that first call to services.AddHttpContextAccessor(), which means the next two registration attempts do nothing.

Related

Extension Method C# ASP.NET Core

I have this extension method and I have a problem when I call it in my startup class:
public static class ServiceExtensions
{
public static void ConfigureMySqlContext(this IServiceCollection services, IConfiguration config)
{
var connectionString = config["mysqlconnection:connectionString"];
services.AddDbContext<RepositoryContext>(o => o.UseSqlServer(connectionString));
}
}
And this in startup class - I get an error to add IServiceCollection (.NET Core 6)
ServiceExtensions.ConfigureMySqlContext(builder.Configuration);
You are calling this extension in the wrong way. Since it is an "Extension" method, you need to invoke it on the concrete object, it is extending. By using the this keyword, you are extending the type you are invoking this method on by a specific functionality, without deriving from with a custom type.
services.ConfigureMySqlContext(builder.Configuration);
or, if you are basing your code off of the .net 6 minimal templates:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.ConfigureMySqlContext(builder.Configuration);
var app = builder.Build();
Documentation: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods

Passing IOptions<T> to method in StartUp Configuration

I have set up my project to use the IOptions pattern for reading data from the appSettings file.
I have a class that has the following simple constructor to it:
public PlayClass(IOptions<MySettings> settings)
{
_settings = settings;
}
In my ConfigureServices method I have my config set up here:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MySettings>(options => Configuration.GetSection("MyOptions").Bind(options));
}
When I run or test this, everything works as expected. However, I need to call a method from my play PlayClass inside of ConfigureServices.
What is best way to achieve this?
I had originally thought it would be as simple as the following:
public void ConfigureServices(IServiceCollection services)
{
var x = services.Configure<BitBucketSettings>(options => Configuration.GetSection("BitBucketOptions").Bind(options));
var pc = new PlayClass(x);
pc.MyMethod();
}
But this only results in an error: cannot convert from IServiceCollection to IOptions<MySettings>
It is not clear why you want to create an object of class in Startup class. But you can solve your problem as following.
IServiceCollection is used only for create the dependency graph but to resolve the actual dependencies at runtime, ServiceProvider is needed.
To build ServiceProvider, BuildServiceProvider method needs to be called on ServiceCollection. You can see that in the code below.
public void ConfigureServices(IServiceCollection services)
{
//Register the configuration section in the service collection.
services.Configure<BitBucketSettings>(Configuration.GetSection("BitBucketOptions");
// Register the class in service collection.
services.AddScoped<PlayClass, PlayClass>();
// Build Service Provider
var sp = services.BuildServiceProvider();
// Resolve instance or PlayClass from service builder.
var pc = sp.GetService<PlayClass>();
// Call method on instance of PlayClass
pc.MyMethod();
}
I hope this will help you solve your issue.

Inject different implementions in Controller based on Route

I've got an ASP.NET Core application and I'd like to use different strategies based on the Route selected.
As an example if someone navigates to /fr/Index I want to inject the French translation implementation into my Controller.
Likewise when someone navigates to /de/Index I'd like the German translation injected.
This is to avoid having every single action on my Controller read the "language" parameter and pass it on.
From a higher level, I'd like to have something like that:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Stuff here
app.MapWhen(
context => context.Request.Query["language"] == "fr",
builder =>
{
builder.Register<ILanguage>(FrenchLanguageImplementation);
});
app.MapWhen(
context => context.Request.Query["language"] == "de",
builder =>
{
builder.Register<ILanguage>(GermanLanguageImplementation);
});
}
Unfortunately, it doesn't look like I get the IoC container resolving context at that level.
PS: I'm using Lamar as IoC.
You can use the AddScoped overload on IServiceCollection (or ServiceRegistry, which also implements IServiceCollection) to provide a factory-based service registration to the DI container. Here's an example implementation of ConfigureContainer, with explanatory comments inline:
public void ConfigureContainer(ServiceRegistry services)
{
// ...
// Register IHttpContextAccessor for use in the factory implementation below.
services.AddHttpContextAccessor();
// Create a scoped registration for ILanguage.
// The implementation returned from the factory is tied to the incoming request.
services.AddScoped<ILanguage>(sp =>
{
// Grab the current HttpContext via IHttpContextAccessor.
var httpContext = sp.GetRequiredService<IHttpContextAccessor>().HttpContext;
string requestLanguage = httpContext.Request.Query["language"];
// Determine which implementation to use for the current request.
return requestLanguage switch
{
"fr" => FrenchLanguageImplementation,
"de" => GermanLanguageImplementation,
_ => DefaultLanguageImplementation
};
});
}
Disclaimer: Up until testing the information in this answer, I've never used Lamar, so this Lamar-specific setup is taken from the docs and a best-guess effort. Without Lamar, the first line in the sample code would be public void ConfigureServices(IServiceCollection services) with no other changes.

How to Unit Test Startup.cs in .NET Core

How do people go about Unit Testing their Startup.cs classes in a .NET Core 2 application? All of the functionality seems to be provided by Static extensions methods which aren't mockable?
If you take this ConfigureServices method for example:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<BlogContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc();
}
How can I write tests to ensure that AddDbContext(...) & AddMvc() are called - the choice of implementing all of this functionality via extension methods seems to have made it untestable?
Well yes, if you want to check the fact that extension method AddDbContext was called on services you are in trouble.
The good thing is that you shouldn't actually check exactly this fact.
Startup class is an application composition root. And when testing a composition root you want to check that it actually registers all dependencies required for instantiation of the root objects (controllers in the case of ASP.NET Core application).
Say you have following controller:
public class TestController : Controller
{
public TestController(ISomeDependency dependency)
{
}
}
You could try checking whether Startup has registered the type for ISomeDependency. But implementation of ISomeDependency could also require some other dependencies that you should check.
Eventually you end up with a test that has tons of checks for different dependencies but it does not actually guarantee that object resolution will not throw missing dependency exception. There is not too much value in such a test.
An approach that works well for me when testing a composition root is to use real dependency injection container. Then I call a composition root on it and assert that resolution of the root object does not throw.
It could not be considered as pure Unit Test because we use other non-stubbed class. But such tests, unlike other integration tests, are fast and stable. And most important they bring the value of valid check for correct dependencies registration. If such test passes you could be sure that object will also be correctly instantiated in the product.
Here is a sample of such test:
[TestMethod]
public void ConfigureServices_RegistersDependenciesCorrectly()
{
// Arrange
// Setting up the stuff required for Configuration.GetConnectionString("DefaultConnection")
Mock<IConfigurationSection> configurationSectionStub = new Mock<IConfigurationSection>();
configurationSectionStub.Setup(x => x["DefaultConnection"]).Returns("TestConnectionString");
Mock<Microsoft.Extensions.Configuration.IConfiguration> configurationStub = new Mock<Microsoft.Extensions.Configuration.IConfiguration>();
configurationStub.Setup(x => x.GetSection("ConnectionStrings")).Returns(configurationSectionStub.Object);
IServiceCollection services = new ServiceCollection();
var target = new Startup(configurationStub.Object);
// Act
target.ConfigureServices(services);
// Mimic internal asp.net core logic.
services.AddTransient<TestController>();
// Assert
var serviceProvider = services.BuildServiceProvider();
var controller = serviceProvider.GetService<TestController>();
Assert.IsNotNull(controller);
}
I also had a similar problem, but managed to get around that by using the WebHost in AspNetCore and essentially re-creating what program.cs does, and then Asserting that all of my services exist and are not null. You could go a step further and execute specific extensions for IServices with .ConfigureServices or actually perform operations with the services you created to make sure they were constructed properly.
One key, is I created a unit test startup class that inherits from the startup class I'm testing so that I don't have to worry about separate assemblies. You could use composition if you prefer to not use inheritance.
[TestClass]
public class StartupTests
{
[TestMethod]
public void StartupTest()
{
var webHost = Microsoft.AspNetCore.WebHost.CreateDefaultBuilder().UseStartup<Startup>().Build();
Assert.IsNotNull(webHost);
Assert.IsNotNull(webHost.Services.GetRequiredService<IService1>());
Assert.IsNotNull(webHost.Services.GetRequiredService<IService2>());
}
}
public class Startup : MyStartup
{
public Startup(IConfiguration config) : base(config) { }
}
This approach works, and uses the real MVC pipeline, as things should only be mocked if you need to change how they work.
public void AddTransactionLoggingCreatesConnection()
{
var servCollection = new ServiceCollection();
//Add any injection stuff you need here
//servCollection.AddSingleton(logger.Object);
//Setup the MVC builder thats needed
IMvcBuilder mvcBuilder = new MvcBuilder(servCollection, new Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager());
IEnumerable<KeyValuePair<string, string>> confValues = new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>("TransactionLogging:Enabled", "True"),
new KeyValuePair<string, string>("TransactionLogging:Uri", "https://api.something.com/"),
new KeyValuePair<string, string>("TransactionLogging:Version", "1"),
new KeyValuePair<string, string>("TransactionLogging:Queue:Enabled", "True")
};
ConfigurationBuilder builder = new ConfigurationBuilder();
builder.AddInMemoryCollection(confValues);
var confRoot = builder.Build();
StartupExtensions.YourExtensionMethod(mvcBuilder); // Any other params
}
As an alternative approach to #datchung's answer with ASP.net Core 6 (or 7) Minimal start-up, it's possible to leverage WebApplicationFactory<T> to run startup. Note that this requires defining InternalsVisibleTo from API to test project for the Program reference to be accessible.
Sample test, using xUnit:
[Fact]
public void StartupTest()
{
var waf = new WebApplicationFactory<Program>();
var server = waf.Server;
// Optional: check for individual services
var myService = server.Services.GetService<IMyService>();
Assert.NotNull(myService);
}
The .Server call there triggers the test server and ServiceCollection build. That, in turn, triggers validation unless "ValidateOnBuild" option has been turned off.
More about WAF internals in here: https://andrewlock.net/exploring-dotnet-6-part-6-supporting-integration-tests-with-webapplicationfactory-in-dotnet-6/
All of this does require that your Startup code works in test scenario (it shouldn't connect to online services etc.) but that is also useful for integration testing too (e.g. Alba).
In my case, I'm using .NET 6 with the minimal API (no Startup class).
My Program.cs originally looked like this:
// using statements
...
var builder = WebApplication.CreateBuilder(args);
...
builder.services.AddSingleton<IMyInterface, MyImplementation>();
...
I added StartupHelper.cs:
public class StartupHelper
{
private readonly IServiceCollection _services;
public StartupHelper(IServiceCollection services)
{
_services = services;
}
public void SetUpServices()
{
_services.AddSingleton<IMyInterface, MyImplementation>();
}
}
I used StartupHelper in Program.cs:
// using statements
...
var builder = WebApplication.CreateBuilder(args);
...
var startupHelper = new StartupHelper(builder.Services);
startupHelper.SetUpServices();
...
And my test (NUnit) looks like this:
[Test]
public void SetUpServices()
{
var builder = WebApplication.CreateBuilder(new string[0]);
var startupHelper = new StartupHelper(builder.Services);
startupHelper.SetUpServices();
var app = builder.Build();
var myImplementation = app.Services.GetService<IMyInterface>();
Assert.NotNull(myImplementation);
Assert.IsTrue(myImplementation is MyImplementation);
}
You should be install to Xunit project then add startup.cs file in base directory .

ASP.NET Core View Injection problems

Has anyone tried to use the new View Injection from ASP.NET Core?
I'm trying to use straight forward as described on the documentation (https://docs.asp.net/en/latest/mvc/views/dependency-injection.html) but no success at all.
The unique diference from my implementation and the documentation is that I'm using AutoFac for DI.
When I try to use the injection on my view I get an exception that my Service has not been registered.
#inject Domain.Service.LevelService LevelService
Error Message:
ComponentNotRegisteredException: The requested service 'Domain.Service.LevelService' 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.
Btw, the service is correctly registered and can be accessed from the controller for example.
Edit to include Startup:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
services.AddMemoryCache();
services.AddSession();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
return new AutofacServiceProvider(DependencyInjection.RegisterServices(services));
}
Code of the method RegisterServices:
public static IContainer RegisterServices(IServiceCollection services)
{
// Create the container builder.
var builder = new ContainerBuilder();
var assembly = Assembly.GetExecutingAssembly();
assembly.GetTypes()
.Where(x => x.IsSubclassOf(typeof(ServiceInjectionModule)))
.ToList()
.ForEach(x =>
{
var t = (ServiceInjectionModule)Activator.CreateInstance(x, new object[] { true });
t.AddtoContainer(builder);
});
// Add automapper configurations
var mapperConfiguration = AutoMapperConfig.Configure();
var mapper = mapperConfiguration.CreateMapper();
builder.RegisterInstance(mapper).As<IMapper>();
// Populate default services
builder.Populate(services);
return builder.Build();
}
The problem is in the assembly scanning section you've written. It's much easier to use the built in functionality of AutoFac. Not sure your code is .Net Core just based on the fact you're not using GetTypeInfo. GetTypeInfo is backwards compatible so will work with .Net 4.x
public static IContainer RegisterServices(IServiceCollection services)
{
// Create the container builder.
var builder = new ContainerBuilder();
var assembly = Assembly.GetExecutingAssembly();
builder.RegisterAssemblyTypes(assembly)
.Where(t => t.GetTypeInfo().IsSubclassOf(typeof(ServiceInjectionModule)))
.AsSelf();
// Add automapper configurations
var mapperConfiguration = AutoMapperConfig.Configure();
var mapper = mapperConfiguration.CreateMapper();
builder.RegisterInstance(mapper).As<IMapper>();
// Populate default services
builder.Populate(services);
return builder.Build();
}
OK, I solved the problem.
Well, I didn't paid attention and seems that no one too :p.
The problem is that I'm trying to inject an instance and not an interface. Just changed the implementation and everything started working.
Final code:
#inject Domain.Service.Interfaces.ILevelService LevelService

Categories

Resources