Inject dependency in the ConfigureServices - c#

In my ASP.Net Core application I need to inject some dependencies (a repository, in my case) in the ConfigureServices method.
The problem is that method does not allow the use of multiple arguments to inject dependencies. What to do instead ?
Here is my code
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
// ...
services.AddSingleton<ITableRepositories, TableClientOperationsService>();
// Add framework services.
services.AddOpenIdConnect(options =>
{
// options.ClientId = ...
options.Events = new OpenIdConnectEvents
{
OnTicketReceived = async context =>
{
var user = (ClaimsIdentity)context.Principal.Identity;
if (user.IsAuthenticated)
{
// ...
// vvv
// HERE, I need the ITableRepositories repository;
// vvv
var myUser = await repository.GetAsync<Connection>(userId);
// ...
}
return;
}
};
});
}
How can I inject the dependency here?
EDIT:
Following the Chris idea (bellow), that seem to work:
public class Startup
{
// private repository, used in ConfigureServices, initialized in Startup
ITableRepositories repository;
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
// ... etc etc
Configuration = builder.Build();
// init repository here
this.repository = new TableClientOperationsService();
}

You can access the service container via the HttpContext.RequestServices of the current context.
public void ConfigureServices(IServiceCollection services) {
services.AddOptions();
// ...
services.AddSingleton<ITableRepositories, TableClientOperationsService>();
// Add framework services.
services.AddOpenIdConnect(options => {
// options.ClientId = ...
options.Events = new OpenIdConnectEvents {
OnTicketReceived = async context => {
var user = (ClaimsIdentity)context.Principal.Identity;
if (user.IsAuthenticated) {
// ...
// get the ITableRepositories repository
var repository = context.HttpContext.RequestServices.GetService<ITableRepositories>();
var myUser = await repository.GetAsync<Connection>(userId);
// ...
}
return;
}
};
});
}
So technically you don't need access to the dependency within the ConfigureServices as the inline expression could be extracted into its own function.
public void ConfigureServices(IServiceCollection services) {
services.AddOptions();
// ...
services.AddSingleton<ITableRepositories, TableClientOperationsService>();
// Add framework services.
services.AddOpenIdConnect(options => {
// options.ClientId = ...
options.Events = new OpenIdConnectEvents {
OnTicketReceived = TicketReceived
};
});
}
private async Task TicketReceived(TicketReceivedContext context) {
var user = (ClaimsIdentity)context.Principal.Identity;
if (user.IsAuthenticated) {
// ...
// get the ITableRepositories repository
var repository = context.HttpContext.RequestServices.GetService<ITableRepositories>();
var myUser = await repository.GetAsync<Connection>(userId);
// ...
}
return;
}

Well, you can't. However, you can utilize the StartUp constructor to set one or more properties that you can utilize inside ConfigureServices. For example:
public StartUp(IHostingEnvironment env)
{
...
FooInstance = new Foo();
}
public IFoo FooInstance { get; }
public void ConfigureServices(IServiceCollection services)
{
// you can now use `Foo` here, without injecting it.
}
EDIT (based on code added to question)
In this particular scenario, especially since this is a singleton, you can simply create an instance manually and then bind to that instance instead of a generic type. For example:
var repository = new TableClientOperationsService();
services.AddSingleton<ITableRepositories>(repository);
Then, you can simply utilize this variable directly in the code below where you need it:
var myUser = await repository.GetAsync<Connection>(userId);

Related

How do you write unit test to test ForwardedHeadersOptions configured with .net services collection

I am working on dot net core application. And, I want to write unit test to test that the ForwardHeaderOptions are setup correctly. Please see below the exact scenario.
public static class ProxyForwardedHeadersExtensions
{
public static IServiceCollection AddProxyForwardedHeaders(this IServiceCollection services)
{
services
.Configure<ForwardedHeadersOptions>(
options =>
{
options.ForwardLimit = null;
options.RequireHeaderSymmetry = false;
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
options.ForwardedHeaders =
ForwardedHeaders.XForwardedHost
| ForwardedHeaders.XForwardedFor
| ForwardedHeaders.XForwardedProto;
});
return services;
}
}
In the above scenario, How can i test AddProxyForwardedHeaders method? I am not able to think of anything.
Thanks
I am trying to test with the following approach that creating web host and testclient. Calling AddProxyForwardedHeaders on services. But, I don't know how to verify if its working properly in unit test?
_builder = new HostBuilder()
.ConfigureWebHost(builder =>
builder
.UseTestServer()
.ConfigureServices(services => services.AddRouting().AddProxyForwardedHeaders())
.Configure(app =>
{
app.UseCustomMiddleware();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", context => Task.FromResult(new OkResult()));
});
app.UseProxyForwardedHeaders(sfName);
})
);
var host = _builder.Build();
await host.StartAsync();
var response = await host.GetTestClient().GetAsync("/");
Problem 2: I have one more method to test in same way like as below: public static void UseProxyForwardedHeaders(this IApplicationBuilder app, string serviceFabricServiceName) {
app.UseForwardedHeaders();
app.Use(
(ctx, next) =>
{
if (string.IsNullOrWhiteSpace(serviceFabricServiceName))
{
return next();
}
ctx.Request.Scheme = "https";
var servicePathBase = serviceFabricServiceName;
ctx.Request.PathBase = new PathString(servicePathBase);
return next();
});
}
The problem is: How to unit test the context set here in this method. Please guide me.
Thanks!
I tried to created a container and regist the options with the extension method then get the registed the options from the container and check the properties:
public void Test1()
{
var services = new ServiceCollection();
services.AddProxyForwardedHeaders();
var provider = services.BuildServiceProvider();
var targetoptions = provider.GetService<IOptions<SomeOptions>>().Value;
var exceptedoptions = new SomeOptions() { Num = 1, Prop1 = "Prop1", Prop2 = "Prop2" };
Assert.Equal(exceptedoptions.Num, targetoptions.Num);
}
public static class ExtensionMethodTest
{
public static IServiceCollection AddProxyForwardedHeaders(this IServiceCollection services)
{
services.Configure<SomeOptions>(x=> { x.Num = 1; x.Prop1 = "Prop1";x.Prop2 = "Prop2"; });
return services;
}
}
Result:

ASP.NET Core Web API, How can I access HttpContext in startup class

I'm trying to access HttpContext to get RemoteIpAddress and User-Agent, but within Startup.cs.
public class Startup
{
public Startup(IConfiguration configuration, IHttpContextAccessor httpContextAccessor)
{
Configuration = configuration;
_httpContextAccessor = httpContextAccessor;
}
public IConfiguration Configuration { get; }
public IHttpContextAccessor _httpContextAccessor { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
IdentityModelEventSource.ShowPII = true;
var key = Encoding.ASCII.GetBytes(Configuration.GetValue<string>("claveEncriptacion"));
var ip = _httpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString();
var userAgent = _httpContextAccessor.HttpContext.Request.Headers["User-Agent"].ToString();
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
//x.Audience = ip + "-" + userAgent;
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = true
};
});
With the previous code I have an error executing the project.
Unable to resolve service for type 'Microsoft.AspNetCore.Http.IHttpContextAccessor' while attempting to activate 'JobSiteMentorCore.Startup'.'
According to the ASP.NET Core documentation , only the following service types can be injected into the Startup constructor when using the Generic Host (IHostBuilder):
IWebHostEnvironment
IHostEnvironment
IConfiguration
So you cannot inject IHttpContextAccessor to Startup constructor.
However you can get DI resolved service in ConfigureServices method of the Startup class as follows:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IYourService, YourService>();
// Build an intermediate service provider
var serviceProvider = services.BuildServiceProvider();
// Resolve the services from the service provider
var yourService = serviceProvider.GetService<IYourService>();
}
But you can not get the HttpContext using IHttpContextAccessor similarly because HttpContext is null unless the code executed during any HttpRequest. So you have to do your desired operation from any custom middleware in Configure method of the Startup class as follows:
public class YourCustomMiddleMiddleware
{
private readonly RequestDelegate _requestDelegate;
public YourCustomMiddleMiddleware(RequestDelegate requestDelegate)
{
_requestDelegate = requestDelegate;
}
public async Task Invoke(HttpContext context)
{
// Your HttpContext related task is in here.
await _requestDelegate(context);
}
}
Then in the Configure method of the Startup class as follows:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware(typeof(YourCustomMiddleMiddleware));
}
I've finally found a possible solution, using middleware to validate the token.
I created a class ValidationHandler, and this class can use HttpContext.
public class ValidationRequirement : IAuthorizationRequirement
{
public string Issuer { get; }
public string Scope { get; }
public HasScopeRequirement(string scope, string issuer)
{
Scope = scope ?? throw new ArgumentNullException(nameof(scope));
Issuer = issuer ?? throw new ArgumentNullException(nameof(issuer));
}
}
public class ValidationHandler : AuthorizationHandler<ValidationRequirement>
{
IHttpContextAccessor _httpContextAccessor;
public HasScopeHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, HasScopeRequirement requirement)
{
var ip = _httpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString();
var userAgent = _httpContextAccessor.HttpContext.Request.Headers["User-Agent"].ToString();
//context.Succeed(requirement);
return Task.CompletedTask;
}
}
Finally in the Startup.cs class it is necessary to add the following.
services.AddAuthorization(options =>
{
options.AddPolicy("read:messages", policy => policy.Requirements.Add(new HasScopeRequirement("read:messages", "david")));
});
Thank you vidgarga. I would upvote but I have a new account. Your answer helped me on my project. I'm coming from f# land so I am including my implementation for any fsharpers that need this solution.
type HasScopeHandler() =
inherit AuthorizationHandler<HasScopeRequirement>()
override __.HandleRequirementAsync(context, requirement) =
let scopeClaimFromIssuer = Predicate<Claim>(fun (c: Claim) -> c.Type = "scope" && c.Issuer = requirement.Issuer)
let userDoesNotHaveScopeClaim = not (context.User.HasClaim(scopeClaimFromIssuer))
let isRequiredScope s = (s = requirement.Scope)
let claimOrNull = context.User.FindFirst(scopeClaimFromIssuer)
if (userDoesNotHaveScopeClaim) then
Task.CompletedTask
else
match claimOrNull with
| null -> Task.CompletedTask
| claim ->
let scopes = claim.Value.Split(' ')
let hasRequiredScope = scopes.Any(fun s -> isRequiredScope s)
if (hasRequiredScope) then
context.Succeed(requirement)
Task.CompletedTask
else
Task.CompletedTask

Net Core: Execute All Dependency Injection in Xunit Test for AppService, Repository, etc

I am trying to implement Dependency Injection in Xunit test for AppService. Ideal goal is to run the original application program Startup/configuration, and use any dependency injection that was in Startup, instead of reinitializing all the DI again in my test, thats the whole Goal in question.
Update: Mohsen's answer is close. Need to update couple syntax/requirement errors to work.
For some reason, original application works and can call Department App Service. However, it cannot call in Xunit. Finally got Testserver working using Startup and Configuration from original application.
Now receiving error below:
Message: The following constructor parameters did not have matching fixture data: IDepartmentAppService departmentAppService
namespace Testing.IntegrationTests
{
public class DepartmentAppServiceTest
{
public DBContext context;
public IDepartmentAppService departmentAppService;
public DepartmentAppServiceTest(IDepartmentAppService departmentAppService)
{
this.departmentAppService = departmentAppService;
}
[Fact]
public async Task Get_DepartmentById_Are_Equal()
{
var options = new DbContextOptionsBuilder<SharedServicesContext>()
.UseInMemoryDatabase(databaseName: "TestDatabase")
.Options;
context = new DBContext(options);
TestServer _server = new TestServer(new WebHostBuilder()
.UseContentRoot("C:\\OriginalApplication")
.UseEnvironment("Development")
.UseConfiguration(new ConfigurationBuilder()
.SetBasePath("C:\\OriginalApplication")
.AddJsonFile("appsettings.json")
.Build()).UseStartup<Startup>());
context.Department.Add(new Department { DepartmentId = 2, DepartmentCode = "123", DepartmentName = "ABC" });
context.SaveChanges();
var departmentDto = await departmentAppService.GetDepartmentById(2);
Assert.Equal("123", departmentDto.DepartmentCode);
}
}
}
I am receiving this error:
Message: The following constructor parameters did not have matching fixture data: IDepartmentAppService departmentAppService
Need to use Dependency injection in testing just like real application.
Original application does this. Answers below are not currently sufficient , one uses mocking which is not current goal, other answer uses Controller which bypass question purpose.
Note: IDepartmentAppService has dependency on IDepartmentRepository which is also injected in Startup class, and Automapper dependencies. This is why calling the whole startup class.
Good Resources:
how to unit test asp.net core application with constructor dependency injection
Dependency injection in Xunit project
You are mixing unit test with integration test. TestServer is for integration test and if you want to reuse Startup class to avoid register dependencies again, you should use HttpClient and make HTTP call to controller and action that use IDepartmentAppService.
If you want do unit test, you need to setup DI and register all needed dependencies to test IDepartmentAppService.
Using DI through Test Fixture:
public class DependencySetupFixture
{
public DependencySetupFixture()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddDbContext<SharedServicesContext>(options => options.UseInMemoryDatabase(databaseName: "TestDatabase"));
serviceCollection.AddTransient<IDepartmentRepository, DepartmentRepository>();
serviceCollection.AddTransient<IDepartmentAppService, DepartmentAppService>();
ServiceProvider = serviceCollection.BuildServiceProvider();
}
public ServiceProvider ServiceProvider { get; private set; }
}
public class DepartmentAppServiceTest : IClassFixture<DependencySetupFixture>
{
private ServiceProvider _serviceProvide;
public DepartmentAppServiceTest(DependencySetupFixture fixture)
{
_serviceProvide = fixture.ServiceProvider;
}
[Fact]
public async Task Get_DepartmentById_Are_Equal()
{
using(var scope = _serviceProvider.CreateScope())
{
// Arrange
var context = scope.ServiceProvider.GetServices<SharedServicesContext>();
context.Department.Add(new Department { DepartmentId = 2, DepartmentCode = "123", DepartmentName = "ABC" });
context.SaveChanges();
var departmentAppService = scope.ServiceProvider.GetServices<IDepartmentAppService>();
// Act
var departmentDto = await departmentAppService.GetDepartmentById(2);
// Arrange
Assert.Equal("123", departmentDto.DepartmentCode);
}
}
}
Using dependency injection with unit test is not good idea and you should avoid that. by the way if you want don't repeat your self for registering dependencies, you can wrap your DI configuration in another class and use that class anywhere you want.
Using DI through Startup.cs:
public class IocConfig
{
public static IServiceCollection Configure(IServiceCollection services, IConfiguration configuration)
{
serviceCollection
.AddDbContext<SomeContext>(options => options.UseSqlServer(configuration["ConnectionString"]));
serviceCollection.AddScoped<IDepartmentRepository, DepartmentRepository>();
serviceCollection.AddScoped<IDepartmentAppService, DepartmentAppService>();
.
.
.
return services;
}
}
in Startup class and ConfigureServices method just useIocConfig class:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
IocConfig.Configure(services, configuration);
services.AddMvc();
.
.
.
if you don't want use IocConfig class, change ConfigureServices in Startup class:
public IServiceCollection ConfigureServices(IServiceCollection services)
{
.
.
.
return services;
and in test project reuse IocConfig or Startup class:
public class DependencySetupFixture
{
public DependencySetupFixture()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", false, true));
configuration = builder.Build();
var services = new ServiceCollection();
// services = IocConfig.Configure(services, configuration)
// or
// services = new Startup(configuration).ConfigureServices(services);
ServiceProvider = services.BuildServiceProvider();
}
public ServiceProvider ServiceProvider { get; private set; }
}
and in test method:
[Fact]
public async Task Get_DepartmentById_Are_Equal()
{
using (var scope = _serviceProvider.CreateScope())
{
// Arrange
var departmentAppService = scope.ServiceProvider.GetServices<IDepartmentAppService>();
// Act
var departmentDto = await departmentAppService.GetDepartmentById(2);
// Arrange
Assert.Equal("123", departmentDto.DepartmentCode);
}
}
Use Custom Web Application Factory and ServiceProvider.GetRequiredService below, feel free to edit and optimize the answer
CustomWebApplicationFactory:
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration((hostingContext, configurationBuilder) =>
{
var type = typeof(TStartup);
var path = #"C:\\OriginalApplication";
configurationBuilder.AddJsonFile($"{path}\\appsettings.json", optional: true, reloadOnChange: true);
configurationBuilder.AddEnvironmentVariables();
});
// if you want to override Physical database with in-memory database
builder.ConfigureServices(services =>
{
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
services.AddDbContext<ApplicationDBContext>(options =>
{
options.UseInMemoryDatabase("DBInMemoryTest");
options.UseInternalServiceProvider(serviceProvider);
});
});
}
}
Integration Test:
public class DepartmentAppServiceTest : IClassFixture<CustomWebApplicationFactory<OriginalApplication.Startup>>
{
public CustomWebApplicationFactory<OriginalApplication.Startup> _factory;
public DepartmentAppServiceTest(CustomWebApplicationFactory<OriginalApplication.Startup> factory)
{
_factory = factory;
_factory.CreateClient();
}
[Fact]
public async Task ValidateDepartmentAppService()
{
using (var scope = _factory.Server.Host.Services.CreateScope())
{
var departmentAppService = scope.ServiceProvider.GetRequiredService<IDepartmentAppService>();
var dbtest = scope.ServiceProvider.GetRequiredService<ApplicationDBContext>();
dbtest.Department.Add(new Department { DepartmentId = 2, DepartmentCode = "123", DepartmentName = "ABC" });
dbtest.SaveChanges();
var departmentDto = await departmentAppService.GetDepartmentById(2);
Assert.Equal("123", departmentDto.DepartmentCode);
}
}
}
Resources:
https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-2.2
https://fullstackmark.com/post/20/painless-integration-testing-with-aspnet-core-web-api
When you are testing. You need to use mocking libraries or Inject your service directly on contructor ie.
public DBContext context;
public IDepartmentAppService departmentAppService;
/// Inject DepartmentAppService here
public DepartmentAppServiceTest(DepartmentAppService departmentAppService)
{
this.departmentAppService = departmentAppService;
}

How do I inject IHttpClientFactory in my Azure Service Fabric application?

I am trying to use IHttpClientFactory in my solution instead of just instances of HttpClient.
startup.cs:
services.AddHttpClient("Test", client =>
{
client.BaseAddress = new Uri("http://localhost:57863");
client.Timeout = new TimeSpan(0, 0, 30);
client.DefaultRequestHeaders.Clear();
});
and in my services in need of a HttpClient:
private readonly Uri _clusterLinuxUri;
private readonly IHttpClientFactory _clientFactory;
public LiasseService(ConfigSettings settings, IHttpClientFactory clientFactory)
{
_clusterLinuxUri = new Uri($"{settings.LinuxClusterEndpoint}");
_clientFactory = clientFactory;
}
public async Task<LiasseDetails> CreateLiasseAsync(LiasseCreate liasseData)
{
using (var response = await _clientFactory.CreateClient("Test")
.PostAsJsonAsync($"{_clusterLinuxUri}{_createPath}", liasseData))
{
await response.CheckHttpError($"{nameof(CreateLiasseAsync)} - error in CL");
var detailsList = await response.Content.ReadAsAsync<LiasseDetailsList>();
return detailsList.Details.FirstOrDefault();
}
}
The part I haven't figured out is how to inject it in Autofac.
program.cs
private static void Main()
{
try
{
var builder = new ContainerBuilder();
builder.RegisterModule(new GlobalAutofacModule());
builder.RegisterServiceFabricSupport();
builder.RegisterStatelessService<FacadeCore>("xxx.FacadeCoreType");
using (builder.Build())
{
ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(FacadeCore).Name);
Thread.Sleep(Timeout.Infinite);
}
}
catch (Exception e)
{
ServiceEventSource.Current.ServiceHostInitializationFailed(e.ToString());
throw;
}
}
public class GlobalAutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<ConfigSettings>();
builder.RegisterType<PaymentService>().As<IPaymentService>();
builder.RegisterType<MailerService>().As<IMailerService>();
builder.RegisterType<LiasseService>().As<ILiasseService>();
builder.RegisterType<AnalyseFinanciereService>().As<IAnalyseFinanciereService>();
builder.RegisterType<ApimService>().As<IApimService>();
builder.RegisterType<UserRepository>().As<IUserRepository>();
builder.RegisterType<ApplicationProcessRepository>().As<IApplicationProcessRepository>();
builder.RegisterType<LiasseRepository>().As<ILiasseRepository>();
builder.RegisterType<CustomUserIdProvider>().As<IUserIdProvider>();
}
}
Am I supposed to create some custom client that implements IHttpClientFactory to be able to inject it? How should I do this? Any examples? Thanks.
Please see Interface documentation here
So to answer your question:
1) Using IServiceCollection from 'ConfigureServices' method call .AddHttpClient()
2) Create new Autofac container builder and populate it with IServiceCollection mentioned above
3) From ConfigureServices method return new AutofacServiceProvider
public IServiceProvider ConfigureServices(IServiceCollection services)
{
...
services.AddHttpClient();
var containerBuilder = new ContainerBuilder();
containerBuilder.Populate(services);
var container = containerBuilder.Build();
return new AutofacServiceProvider(container);
}
P.S.
Make sure to add - Autofac.Extensions.DependencyInjection nuget package, in order to be able to use AutofacServiceProvider class.
Apart from doing using a WebHostBuilder based solution similar to what you get when creating a vanilla Asp.Net Core Service Fabric project, you can alternatively just do this
public class HttpClientModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.Register(_ =>
{
var services = new ServiceCollection();
services.AddHttpClient();
var containerBuilder = new ContainerBuilder();
containerBuilder.Populate(services);
var container = containerBuilder.Build();
return new AutofacServiceProvider(container);
})
.As<IServiceProvider>()
.SingleInstance();
builder.Register(ctx =>
{
var scope = ctx.Resolve<IComponentContext>();
var provider = scope.Resolve<IServiceProvider>();
var factory = provider.GetService<IHttpClientFactory>();
return factory.CreateClient();
}).As<HttpClient>();
}
}

Integration Testing with AutoMapper fails to initialise configuration

Frameworks & Packages
.NETCoreApp 1.1
Xunit 2.2.0
AutoMapper 6.0.2
Microsoft.AspNetCore.TestHost 1.1.1
Microsoft.NET.Test.Sdk 15.0.0
Integration Test
public class ControllerRequestsShould
{
private readonly TestServer _server;
private readonly HttpClient _client;
public ControllerRequestsShould()
{
_server = new TestServer(new WebHostBuilder()
.UseContentRoot(Constants.apiProjectRoot)
.UseStartup<Startup>()
.UseEnvironment(Constants.testingEnvironment));
_client = _server.CreateClient();
_client.BaseAddress = new Uri(Constants.localHostUri);
}
[Fact]
public async Task CreateAnEntity()
{
// Arrange
var entityForCreationDto = new entityForCreationDto { Code = "00001", Name = "Entity One" };
var jsonContent = JsonConvert.SerializeObject(entityForCreationDto);
var stringContent = new StringContent(jsonContent);
stringContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
// Act
var response = await _client.PostAsync("/api/controller", stringContent);
response.EnsureSuccessStatusCode();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
Startup.cs
public class Startup
{
public IConfigurationRoot Configuration { get; }
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
// Add framework services
services.AddMvc(setupAction =>
{
setupAction.ReturnHttpNotAcceptable = true;
setupAction.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
setupAction.InputFormatters.Add(new XmlDataContractSerializerInputFormatter());
});
// Db context configuration
var connectionString = Configuration["ConnectionStrings:DefaultConnection"];
services.AddDbContext<YourContext>(options =>
{
options.UseSqlServer(connectionString);
});
// Register services for dependency injection
services.AddScoped<IYourRepository, YourRepository>();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddScoped<IUrlHelper, UrlHelper>(implementationFactory =>
{
var actionContext =
implementationFactory.GetService<IActionContextAccessor>().ActionContext;
return new UrlHelper(actionContext);
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
loggerFactory.AddDebug(LogLevel.Information);
loggerFactory.AddNLog();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler(appBuilder =>
{
appBuilder.Run(async context =>
{
var exceptionHandlerFeature = context.Features.Get<IExceptionHandlerFeature>();
if (exceptionHandlerFeature != null)
{
var logger = loggerFactory.CreateLogger("Global exception logger");
logger.LogError(500,
exceptionHandlerFeature.Error,
exceptionHandlerFeature.Error.Message);
}
context.Response.StatusCode = 500;
await context.Response.WriteAsync("An unexpected fault happened. Try again later");
});
});
}
Mapper.Initialize(cfg =>
{
cfg.CreateMap<DataStore.Entities.Entity, Models.EntityDto>();
cfg.CreateMap<Models.EntityDto, DataStore.Entities.Entity>();
cfg.CreateMap<Models.EntityForCreationDto, DataStore.Entities.Entity>();
cfg.CreateMap<DataStore.Entities.Entity, Models.EntityForCreationDto>();
});
app.UseMvc();
}
Problem
The integration test fails after the controller method is invoked:
var response = await _client.PostAsync("/api/controller", stringContent);
It fails because AutoMapper has not been initialised.
The way I understood this was that since the TestServer has the UseStartup method, it should use all the services configured in the api Startup.cs class (the UseContentRoot is pointing to my api project root)
This clearly isn't happening. Could someone show me how I need to configure the TestServer so that the AutoMapper configuration is picked up correctly please?
You should specify the assembly in the ConfigureServices method :
var assembly = typeof(Program).GetTypeInfo().Assembly;
services.AddAutoMapper(assembly);
I'm using Automapper Modules, so the mapping config is picked up automatically by AutoMapper, but even then, you still need the above config.
Or just use this line
services.AddAutoMapper(typeof(Startup));
instead of
var assembly = typeof(Program).GetTypeInfo().Assembly;
services.AddAutoMapper(assembly);
which is more clear and clean in my opinion
Thank you, it's work for me. Additionally you can add configuration options like this.
var assembly = typeof(Program).GetTypeInfo().Assembly;
services.AddAutoMapper(cfg =>
{
cfg.AllowNullDestinationValues = true;
cfg.CreateMap<ApplicationUser, ApplicationUserView> ().IgnoreAllPropertiesWithAnInaccessibleSetter();}, assembly);

Categories

Resources