Endpoint is null when accessed in middleware during asp.net core 3.1 integration test - asp.net-core

I run integration tests for my asp.net core application, the call passes from multiple middle-wares but stops at one of them which has the following line :
var endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint;
var attribute = endpoint?.Metadata.GetMetadata<AllowAHeader>();
The endpoint is null.
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup>
where TStartup : class
{
protected override IHostBuilder CreateHostBuilder()
{
var builder = Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(x =>
{
x.UseStartup<TStartup>().UseTestServer();
});
return builder;
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
builder.ConfigureTestServices(services =>
{
services.RemoveAll<DbContext>();
services.RemoveAll<DbContextOptions>();
foreach (var option in services.Where(s =>
s.ServiceType.BaseType ==
typeof(DbContextOptions)).ToList())
{
services.Remove(option);
}
services.AddDbContext<DbContext>(options =>
{
options.UseInMemoryDatabase("Testing");
});
});
}
}
Here is the test
public class ClientTests : IClassFixture<CustomWebApplicationFactory<TestStartup>>
{
private readonly HttpClient _client;
public ClientTests(CustomWebApplicationFactory<TestStartup> customWebApplicationFactory)
{
_client = customWebApplicationFactory.CreateClient();
}
[Fact]
public async Task GetClients()
{
_client.DefaultRequestHeaders.Add("X-Integration-Testing", "True");
_client.DefaultRequestHeaders.Add("X-Integration-Authroize", "Basic");
var result = await _client.PostAsync("v1/client", null);
}
}
The TestStartup :
public class TestStartup : Startup
{
public TestStartup(IConfiguration configuration)
: base(configuration)
{
}
protected override void ConfigureMiddlewareForIntegrationTest(IApplicationBuilder app)
{
app.UseMiddleware<AuthenticatedTestRequestMiddleware>();
}
}
public class AuthenticatedTestRequestMiddleware
{
public const string TestingHeader = "X-Integration-Testing";
public const string TestingHeaderAuthValueValue = "X-Integration-Authroize";
private readonly RequestDelegate _next;
public AuthenticatedTestRequestMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
if (context.Request.Headers.Keys.Contains(TestingHeader))
{
if (context.Request.Headers.Keys.Contains(TestingHeaderAuthValueValue))
{
var encoded = "Basic " + System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes("user" + ":" + "123456"));
context.Request.Headers.Add("Authorization", encoded);
}
}
}
}

In ConfigureWebHostDefaults add:
x.UseHttpSys(opt =>
opt.RequestQueueMode = RequestQueueMode.Create;
)
Have not figured out exactly why it's needed, but I'm guessing it's a bug being the value of RequestQueueMode is 0 by default, same as RequestQueueMode.Create's value.

Related

ASP.NET Core middleware redirect to another route

I have an ASP.NET 6 Web API controller and it has two methods.
This is my code:
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
[HttpPost]
[Route("Test")]
public IEnumerable<WeatherForecast> Test()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
[HttpGet]
[HttpPost]
[Route("User")]
public IEnumerable<WeatherForecast> UserInfo()
{
return Enumerable.Range(1, 2).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
Then I wrote a middleware component:
public class RequestResponseMiddleware
{
private RequestDelegate _next;
public RequestResponseMiddleware(RequestDelegate next)
{
this._next = next;
}
public async Task Invoke(HttpContext context)
{
var req = context.Request;
context.Response.Redirect("/weatherforecast/User",true,true);
await _next(context);
}
}
I used
https://localhost:7290/weatherforecast/Test?t=1675343945003&m=&k=sdf
to test it.
In the middleware, response can redirect to /weatherforecast/User, but it will fire 5 times. It means that the method UserInfo() will be invoked 5 times.
What's wrong with my code? And how to fix the problem?
----update----
mapping example.
{
"Routes":[
{
"Controllor": "WeatherForecast",
"Metods":[
{
"Test": "User"
},
{
"Weather": "Weather"
}
]
}
]
}
---- update2 ----
context.Response.Redirect
will invoke WeatherForecast/ Test1 time, and invoke WeatherForecast/User4 times.
---- update 3 ----
code
code here
i know what's wrong with my code . There is not a exist condition before redirect.
a solution likes
if (context.Request.Path.HasValue && context.Request.Path == "/weatherforecast/Test")
{
context.Response.Redirect("/weatherforecast/User", false);
}
else
{
await _next.Invoke(context);
}
You have to branch the pineline,or the next time you already redirected to /weatherforecast/User the middleware would redirect you to the same uri again
Check this document
For example:
app.MapWhen(x => x.Request.Path.Value.Contains("Test"), app =>
{    
app.UseMiddleware<RequestResponseMiddleware>();
});
Create a Class to bind the configration file:
public class RouteMap
{
public string Controllor { get; set; }
public List<Dictionary<string,string>> Methods { get; set; }
}
In program.cs:
var routemap=new List<RouteMap>();
app.Configuration.GetSection("Routes").Bind(routemap);
app.MapWhen(x => x.Request.Path.Check(routemap), app =>
{
app.UseMiddleware<RequestResponseMiddleware>();
});
Complete the extension method:
public static class Extension
{
public static bool Check(this PathString pathString,List<RouteMap> routeMaps)
{
bool exist=false;
// you need to modify the logical here yourself to match the route
var controllername= pathString.Value?.Split('/')[1];
var methodname = pathString.Value?.Split('/')[2];
foreach (RouteMap routeMap in routeMaps)
{
if (routeMap.Controllor == controllername)
{
foreach(var methodmaps in routeMap.Methods)
{
if (methodmaps.ContainsKey(methodname))
{
exist =true;
break;
}
}
if (exist)
{
break;
}
}
}
return exist;
}
}
The result:

Unable to resolve service for type 'BusinessServices.CustomerBusinessService' while attempting to activate 'Repository.CustomerRepository'

Issue Message:
System.AggregateException: 'Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Repository.ICustomerRepository Lifetime: Scoped ImplementationType: Repository.CustomerRepository': Unable to resolve service for type 'BusinessServices.CustomerBusinessService' while attempting to activate 'Repository.CustomerRepository'.)'
API Controller:
namespace WebAPIforAzureSQL.Controllers
{
[Route("api/[controller]")]
[ApiController]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
public class AzureController : ControllerBase
{
private readonly ICustomerRepository customerRepository;
private readonly ICustomerBusinessService customerBusinessService;
public AzureController(ICustomerRepository _customerRepository, ICustomerBusinessService _customerBusinessService)
{
customerRepository = _customerRepository;
customerBusinessService = _customerBusinessService;
}
[MapToApiVersion("1.0")]
[HttpGet]
public IEnumerable<Customers> Customers()
{
return customerRepository.GetAllCustomers();
}
[MapToApiVersion("2.0")]
[HttpGet()]
public Customers OneCustomer([FromHeader] int customerID)
{
return customerRepository.GetCustomer(customerID);
}
[HttpGet()]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
public bool NewCustomer([FromHeader] Customers c)
{
return customerRepository.AddCustomer(c);
}
}
}
Program.cs:
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.AspNetCore.Mvc;
using WebAPIforAzureSQL;
using Microsoft.EntityFrameworkCore;
using Repository;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Contracts;
using BusinessServices;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddControllers().AddXmlSerializerFormatters();
builder.Services.AddSwaggerGen();
builder.Services.ConfigureOptions<ConfigureSwaggerOptions>();
builder.Services.AddApiVersioning(opt =>
{
opt.DefaultApiVersion = new ApiVersion(1, 0);
opt.AssumeDefaultVersionWhenUnspecified = true;
opt.ReportApiVersions = true;
opt.ApiVersionReader = ApiVersionReader.Combine(
new UrlSegmentApiVersionReader(),
new HeaderApiVersionReader("api-version")
);
});
// Add ApiExplorer to discover versions
builder.Services.AddVersionedApiExplorer(setup =>
{
setup.GroupNameFormat = "'v'VVV";
setup.SubstituteApiVersionInUrl = true;
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddDbContext<AmounDB001DbContext>(option =>
{
option.UseSqlServer(builder.Configuration.GetConnectionString("DB001"));
});
//adding Services
builder.Services.AddScoped<ICustomerRepository, CustomerRepository>();
builder.Services.AddScoped<ICustomerBusinessService, CustomerBusinessService>();
var app = builder.Build();
var apiVersionDescriptionProvider =
app.Services.GetRequiredService<IApiVersionDescriptionProvider>();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(
options =>
{
foreach (var description in apiVersionDescriptionProvider.ApiVersionDescriptions)
{
options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json",
description.GroupName.ToUpperInvariant());
}
}
);
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Repository Class
namespace Repository
{
public class CustomerRepository : ICustomerRepository
{
private readonly AmounDB001DbContext amounDB001DbContext;
private readonly CustomerBusinessService customerBusinessService;
public CustomerRepository(
AmounDB001DbContext _amounDB001DbContext,
CustomerBusinessService _customerBusinessService
)
{
this.amounDB001DbContext = _amounDB001DbContext;
customerBusinessService= _customerBusinessService;
}
public bool AddCustomer(Customers c)
{
if (c != null && customerBusinessService.CheckAllFields(c))
{
amounDB001DbContext.Add(c);
amounDB001DbContext.SaveChangesAsync();
return true;
}
return false;
}
public bool RemoveCustomer(Customers c)
{
if (c != null && customerBusinessService.CheckAllFields(c))
{
amounDB001DbContext.Customers.Remove(c);
amounDB001DbContext.SaveChanges();
return true;
}
return false;
}
public bool UpdateCustomer(Customers c)
{
if (c != null && customerBusinessService.CheckAllFields(c))
{
amounDB001DbContext.Customers.Update(c);
amounDB001DbContext.SaveChanges();
return true;
}
return false;
}
public List<Customers> GetAllCustomers()
{
return amounDB001DbContext.Customers.ToList();
}
public Customers GetCustomer(int customerID)
{
if (customerID == 0)
{
return null;
}
return amounDB001DbContext.Customers.FirstOrDefault(c => c.CustomerID == customerID);
}
}
}
I'm not sure what I'm doing wrong. Any ideas?
I think you should change the constructor of CustomerRepository, replacing the parameter type CustomerBusinessService with the interface you registered as a service ICustomerBusinessService.
public class CustomerRepository : ICustomerRepository
{
private readonly AmounDB001DbContext amounDB001DbContext;
private readonly ICustomerBusinessService customerBusinessService;
public CustomerRepository(
AmounDB001DbContext _amounDB001DbContext,
ICustomerBusinessService _customerBusinessService
)
{
this.amounDB001DbContext = _amounDB001DbContext;
customerBusinessService= _customerBusinessService;
}
...

How to make Route based on a sub-domain MVC Core

How to make this
- user1.domain.com goes to user1/index (not inside area)
- user2.domain.com goes to user2/index (not inside area)
I mean's the
user1.domain.com/index
user2.domain.com/index
Are same view but different data depending on user{0}
using MVC Core 2.2
There're several approaches depending on your needs.
How to make this - user1.domain.com goes to user1/index (not inside area) - user2.domain.com goes to user2/index (not inside area)
Rewrite/Redirect
One approach is to rewrite/redirect the url. If you don't like do it with nginx/iis, you could create an Application Level Rewrite Rule. For example, I create a sample route rule for your reference:
internal enum RouteSubDomainBehavior{ Redirect, Rewrite, }
internal class RouteSubDomainRule : IRule
{
private readonly string _domainWithPort;
private readonly RouteSubDomainBehavior _behavior;
public RouteSubDomainRule(string domain, RouteSubDomainBehavior behavior)
{
this._domainWithPort = domain;
this._behavior = behavior;
}
// custom this method according to your needs
protected bool ShouldRewrite(RewriteContext context)
{
var req = context.HttpContext.Request;
// only rewrite the url when it ends with target doamin
if (!req.Host.Value.EndsWith(this._domainWithPort, StringComparison.OrdinalIgnoreCase)) { return false; }
// if already rewrite, skip
if(req.Host.Value.Length == this._domainWithPort.Length) { return false; }
// ... add other condition to make sure only rewrite for the routes you wish, for example, skip the Hub
return true;
}
public void ApplyRule(RewriteContext context)
{
if(!this.ShouldRewrite(context)) {
context.Result = RuleResult.ContinueRules;
return;
}
var req = context.HttpContext.Request;
if(this._behavior == RouteSubDomainBehavior.Redirect){
var newUrl = UriHelper.BuildAbsolute( req.Scheme, new HostString(this._domainWithPort), req.PathBase, req.Path, req.QueryString);
var resp = context.HttpContext.Response;
context.Logger.LogInformation($"redirect {req.Scheme}://{req.Host}{req.Path}?{req.QueryString} to {newUrl}");
resp.StatusCode = 301;
resp.Headers[HeaderNames.Location] = newUrl;
context.Result = RuleResult.EndResponse;
}
else if (this._behavior == RouteSubDomainBehavior.Rewrite)
{
var host = req.Host.Value;
var userStr = req.Host.Value.Substring(0, host.Length - this._domainWithPort.Length - 1);
req.Host= new HostString(this._domainWithPort);
var oldPath = req.Path;
req.Path = $"/{userStr}{oldPath}";
context.Logger.LogInformation($"rewrite {oldPath} as {req.Path}");
context.Result = RuleResult.SkipRemainingRules;
}
else{
throw new Exception($"unknow SubDomainBehavoir={this._behavior}");
}
}
}
(Note I use Rewrite here. If you like, feel free to change it to RouteSubDomainBehavior.Redirect.)
And then invoke the rewriter middleware just after app.UseStaticFiles():
app.UseStaticFiles();
// note : the invocation order matters!
app.UseRewriter(new RewriteOptions().Add(new RouteSubDomainRule("domain.com:5001",RouteSubDomainBehavior.Rewrite)));
app.UseMvc(...)
By this way,
user1.domain.com:5001/ will be rewritten as (or redirected to) domain.com:5001/user1
user1.domain.com:5001/Index will be rewritten as(or redirected to) domain.com:5001/user1/Index
user1.domain.com:5001/Home/Index will be rewritten as (or redirected to) domain.com:5001/user1//HomeIndex
static files like user1.domain.com:5001/lib/jquery/dist/jquery.min.js won't be rewritten/redirected because they're served by UseStaticFiles.
Another Approach Using IModelBinder
Although you can route it by rewritting/redirecting as above, I suspect what your real needs are binding parameters from Request.Host. If that's the case, I would suggest you should use IModelBinder instead. For example, create a new [FromHost] BindingSource:
internal class FromHostAttribute : Attribute, IBindingSourceMetadata
{
public static readonly BindingSource Instance = new BindingSource( "FromHostBindingSource", "From Host Binding Source", true, true);
public BindingSource BindingSource {get{ return FromHostAttribute.Instance; }}
}
public class MyFromHostModelBinder : IModelBinder
{
private readonly string _domainWithPort;
public MyFromHostModelBinder()
{
this._domainWithPort = "domain.com:5001"; // in real project, use by Configuration/Options
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var req = bindingContext.HttpContext.Request;
var host = req.Host.Value;
var name = bindingContext.FieldName;
var userStr = req.Host.Value.Substring(0, host.Length - this._domainWithPort.Length - 1);
if (userStr == null) {
bindingContext.ModelState.AddModelError(name, $"cannot get {name} from Host Domain");
} else {
var result = Convert.ChangeType(userStr, bindingContext.ModelType);
bindingContext.Result = ModelBindingResult.Success(result);
}
return Task.CompletedTask;
}
}
public class FromHostBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) { throw new ArgumentNullException(nameof(context)); }
var has = context.BindingInfo?.BindingSource == FromHostAttribute.Instance;
if(has){
return new BinderTypeModelBinder(typeof(MyFromHostModelBinder));
}
return null;
}
}
Finally, insert this FromHostBinderProvider in your MVC binder providers.
services.AddMvc(otps =>{
otps.ModelBinderProviders.Insert(0, new FromHostBinderProvider());
});
Now you can get the user1.domain.com automatically by:
public IActionResult Index([FromHost] string username)
{
...
return View(view_model_by_username);
}
public IActionResult Edit([FromHost] string username, string id)
{
...
return View(view_model_by_username);
}
The problem after login the Identity cookie not shared in sub-domain
Here my Code where's wrong !!!
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public static Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder dataProtectionBuilder;
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("ConnectionDb")));
services.AddIdentity<ExtendIdentityUser, IdentityRole>(options =>
{
options.Password.RequiredLength = 8;
options.Password.RequireUppercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequiredUniqueChars = 0;
options.Password.RequireLowercase = false;
}).AddEntityFrameworkStores<ApplicationDbContext>(); // .AddDefaultTokenProviders();
services.ConfigureApplicationCookie(options => options.CookieManager = new CookieManager());
services.AddHttpContextAccessor();
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddScoped<IExtendIdentityUser, ExtendIdentityUserRepository>();
services.AddScoped<IItems, ItemsRepository>();
services.AddMvc(otps =>
{
otps.ModelBinderProviders.Insert(0, new FromHostBinderProvider());
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseAuthentication();
//app.UseHttpsRedirection();
app.UseCookiePolicy();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
And this class to sub-domain like that https://user1.localhost:44390/Home/Index
internal class FromHostAttribute : Attribute, IBindingSourceMetadata
{
public static readonly BindingSource Instance = new BindingSource("FromHostBindingSource", "From Host Binding Source", true, true);
public BindingSource BindingSource { get { return FromHostAttribute.Instance; } }
}
public class MyFromHostModelBinder : IModelBinder
{
private readonly string _domainWithPort;
public MyFromHostModelBinder()
{
this._domainWithPort = "localhost:44390"; // in real project, use by Configuration/Options
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var req = bindingContext.HttpContext.Request;
var host = req.Host.Value;
var name = bindingContext.FieldName;
var userStr = req.Host.Value.Substring(0, host.Length - this._domainWithPort.Length);
if (string.IsNullOrEmpty(userStr))
{
bindingContext.ModelState.AddModelError(name, $"cannot get {name} from Host Domain");
}
else
{
var result = Convert.ChangeType(userStr, bindingContext.ModelType);
bindingContext.Result = ModelBindingResult.Success(result);
}
return Task.CompletedTask;
}
}
public class FromHostBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) { throw new ArgumentNullException(nameof(context)); }
var has = context.BindingInfo?.BindingSource == FromHostAttribute.Instance;
if (has)
{
return new BinderTypeModelBinder(typeof(MyFromHostModelBinder));
}
return null;
}
}
Using ICookieManager
public class CookieManager : ICookieManager
{
#region Private Members
private readonly ICookieManager ConcreteManager;
#endregion
#region Prvate Methods
private string RemoveSubdomain(string host)
{
var splitHostname = host.Split('.');
//if not localhost
if (splitHostname.Length > 1)
{
return string.Join(".", splitHostname.Skip(1));
}
else
{
return host;
}
}
#endregion
#region Public Methods
public CookieManager()
{
ConcreteManager = new ChunkingCookieManager();
}
public void AppendResponseCookie(HttpContext context, string key, string value, CookieOptions options)
{
options.Domain = RemoveSubdomain(context.Request.Host.Host); //Set the Cookie Domain using the request from host
ConcreteManager.AppendResponseCookie(context, key, value, options);
}
public void DeleteCookie(HttpContext context, string key, CookieOptions options)
{
ConcreteManager.DeleteCookie(context, key, options);
}
public string GetRequestCookie(HttpContext context, string key)
{
return ConcreteManager.GetRequestCookie(context, key);
}
#endregion
}

closed generic type registration - Autofac – cannot resolve parameter x of constructor

I am getting the following exception:
"None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'NetCore.DAL.EF.Repositories.Core.Common.SystemSettingRepository' can be invoked with the available services and parameters:\r\nCannot resolve parameter 'NetCore.DAL.EF.DatabaseFactory1[NetCore.DAL.EF.AppDbContext] databaseFactory' of constructor 'Void .ctor(NetCore.DAL.EF.DatabaseFactory1[NetCore.DAL.EF.AppDbContext])'."
Here is the Autofac registration info for SystemSettingRepository:
autofacServiceProvider.Non-Public members._lifetimeScope.ComponentRegistry.Registrations[28] = {Activator = SystemSettingRepository (ReflectionActivator), Services = [Dcs.NetCore.ApplicationCore.BLL.Domain.Features.Common.SystemSettings.ISystemSettingRepository, Dcs.NetCore.Infrastructure.Common.IRepository`1[[Dcs.NetCore.ApplicationCore.BLL.Domain.Entities.Common.SystemSetting, Dcs.NetCore.ApplicationCore.BLL, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], NetCore.DAL.EF.Repositories.Core.Common.SystemSettingRepository], Lifetime = Autofac.Core.Lifetime.CurrentScopeLifetime, Sharing = None, Ownership = OwnedByLifetimeScope}
autofacServiceProvider.Non-Public members._lifetimeScope.ComponentRegistry.Registrations[28].Target.Services[0].ServiceType = {Dcs.NetCore.ApplicationCore.BLL.Domain.Features.Common.SystemSettings.ISystemSettingRepository}
autofacServiceProvider.Non-Public members._lifetimeScope.ComponentRegistry.Registrations[28].Target.Services[1].ServiceType = {Dcs.NetCore.Infrastructure.Common.IRepository`1[Dcs.NetCore.ApplicationCore.BLL.Domain.Entities.Common.SystemSetting]}
autofacServiceProvider.Non-Public members._lifetimeScope.ComponentRegistry.Registrations[28].Target.Services[2].ServiceType = {NetCore.DAL.EF.Repositories.Core.Common.SystemSettingRepository}
As you can see, the services are registered. However, the databaseFactory parameter is not. Here is my code:
public class Startup
{
…
public IServiceProvider ConfigureServices(IServiceCollection services)
{
…
return AutofacBuilder();
}
private AutofacServiceProvider AutofacBuilder()
{
var builder = new ContainerBuilder();
builder.RegisterModule<AutofacModule_AspNetCore>();
builder.Populate(_services);
this.AutofacContainer = builder.Build();
var autofacServiceProvider = new AutofacServiceProvider(this.AutofacContainer);
return autofacServiceProvider;
}
}
public class AutofacModule_AspNetCore : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
RegisterDbContexts();
RegisterComponents();
}
private void RegisterComponents()
{
_builder.RegisterGeneric(typeof(DatabaseFactory<>))
.As(typeof(IDatabaseFactory<>))
.InstancePerDependency();
_builder.RegisterAssemblyTypes(_assembly)
.AsClosedTypesOf(typeof(IRepository<>))
.WithParameter(new ResolvedParameter((p, i) => p.Name == "databaseFactory",
(p, i) => i.Resolve<DatabaseFactory<AppDbContext>>()))
//.WithParameter("databaseFactory", new DatabaseFactory<AppDbContext>())
//.WithParameter(ResolvedParameter//.ForNamed<IDbContext>("coreDomainDbContext"));
//.WithParameter("databaseFactory", )
.InstancePerDependency();
}
private void RegisterDbContexts()
{
RegisterAppDbContextInstance();
}
private void RegisterAppDbContextInstance()
{
// register DbContextOptionsBuilderHelper
_builder.RegisterType<AppDbContextOptionsBuilderHelper>()
.InstancePerDependency();
// configure DbContextOptions
var dbContextOptionsBuilderHelper = new AppDbContextOptionsBuilderHelper();
var dbContextOptionsBuilder = dbContextOptionsBuilderHelper.GetDbContextOptionsBuilder();
// register DbContext
_builder.RegisterType<AppDbContext>()
.WithParameter("options", dbContextOptionsBuilder.Options)
.InstancePerDependency();
}
}
public class DatabaseFactory<T> : Disposable, IDatabaseFactory<T>
where T : DbContext //, new()
{
private T _dbContext;
private AppDbContextOptionsBuilderHelper _appDbContextOptionsBuilderHelper;
private DefaultDbContextOptionsBuilderHelper _defaultDbContextOptionsBuilderHelper;
public DatabaseFactory()
{
this._appDbContextOptionsBuilderHelper = new AppDbContextOptionsBuilderHelper(); // TODO: refactor to ctor injection
this._defaultDbContextOptionsBuilderHelper = new DefaultDbContextOptionsBuilderHelper(); // TODO: refactor to ctor injection
}
public T Get()
{
if (_dbContext == null)
Create();
return _dbContext;
}
private void Create()
{
switch (typeof(T).Name)
{
case "AppDbContext":
{
CreateAppDbContext();
break;
}
case "DefaultDbContext":
{
CreateDefaultDbContext();
break;
}
}
}
private void CreateAppDbContext()
{
var dbContextOptionsBuilder = _appDbContextOptionsBuilderHelper.GetDbContextOptionsBuilder();
this._dbContext = new AppDbContext(dbContextOptionsBuilder.Options) as T;
}
//protected override void DisposeCore()
//{
//
// cref: Autofac.Util.Disposable
//
// //TODO: should I override Autofac.Util.Disposable here?
// var msg = "DatabaseFactory.DisposeCore() executing";
// if (_dbContext != null)
// _dbContext.Dispose();
//}
}
public partial class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{ }
public AppDbContext()
{}
}
public class SystemSettingRepository : RepositoryBase<SystemSetting, AppDbContext>, ISystemSettingRepository
{
public SystemSettingRepository(DatabaseFactory<AppDbContext> databaseFactory)
: base(databaseFactory)
{ }
}
public interface ISystemSettingRepository : IRepository<SystemSetting>
{}
public partial interface IRepository<T> where T : class
{}
public abstract class RepositoryBase<T, U> : IRepositoryBase<T, U>
where T : class
where U : DbContext //, new()
{
private U _dbContext;
private readonly DbSet<T> _dbset;
protected RepositoryBase(DatabaseFactory<U> databaseFactory)
{
DatabaseFactory = databaseFactory;
_dbset = DataContext.Set<T>();
}
protected virtual DatabaseFactory<U> DatabaseFactory
{
get;
private set;
}
protected virtual U DataContext
{
get { return _dbContext = DatabaseFactory.Get(); }
}
public virtual async Task<T> AddAsync(T dao)
{
EntityEntry<T> entityEntry = _dbset.Add(dao);
var result = await SaveChangesAsync();
if (result > 0 && entityEntry != null && entityEntry.Entity != null)
return entityEntry.Entity;
else
return null;
}
}
public interface IRepositoryBase<T, U>
where T : class
where U : DbContext //, new()
{ }
The issue is caused by the way of registration of DatabaseFactory<>. This type is registered as an interface IDatabaseFactory<>. But it is resolved as itself in lambda argument of method WithParameter() on registration of repositories:
_builder.RegisterAssemblyTypes(_assembly)
.AsClosedTypesOf(typeof(IRepository<>))
.WithParameter(new ResolvedParameter((p, i) => p.Name == "databaseFactory",
// resolving type it self
// while it was registered as interface
(p, i) => i.Resolve<DatabaseFactory<AppDbContext>>()))
Autofac doesn't know how to resolve it, because it doesn't have corresponding registration. To make your code work you can resolve DatabaseFactory as an inteface like this:
.WithParameter(new ResolvedParameter((p, i) => p.Name == "databaseFactory",
(p, i) => i.Resolve<IDatabaseFactory<AppDbContext>>()))
Or add AsSelf() call to registration:
_builder.RegisterGeneric(typeof(DatabaseFactory<>))
.As(typeof(IDatabaseFactory<>))
// Register also as DatabaseFactory<>
.AsSelf()
.InstancePerDependency();

SetterProperty injection using structuremap to Asp.Net MVC ActionFilter

Why I am not able to inject the SetterProperty via StructureMap to an MVC ActionFilter?
public class LockProjectFilter : ActionFilterAttribute
{
[SetterProperty]
public ISecurityService SecurityService { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var loggedinStaffId = SecurityService.GetLoggedInStaffId();
if (loggedinStaffId == 1)
throw new ArgumentNullException();
base.OnActionExecuting(filterContext);
}
}
public static IContainer Initialize()
{
ObjectFactory.Initialize(x =>
{
x.Scan(scan =>
{
scan.TheCallingAssembly();
scan.WithDefaultConventions();
scan.AssemblyContainingType<ISecurityService>();
});
x.SetAllProperties(p => p.OfType<ISecurityService>());
//x.ForConcreteType<LockProjectFilter>().Configure
// .Setter(c => c.SecurityService).IsTheDefault();
});
return ObjectFactory.Container;
}
You need to utilize the 'BuildUp' method off the ObjectFactory.
http://docs.structuremap.net/ConstructorAndSetterInjection.htm#section4
[Test]
public void create_a_setter_rule_and_see_it_applied_in_BuildUp_through_ObjectFactory()
{
var theGateway = new DefaultGateway();
ObjectFactory.Initialize(x =>
{
x.ForRequestedType<IGateway>().TheDefault.IsThis(theGateway);
// First we create a new Setter Injection Policy that
// forces StructureMap to inject all public properties
// where the PropertyType is IGateway
x.SetAllProperties(y =>
{
y.OfType<IGateway>();
});
});
// Create an instance of BuildUpTarget1
var target = new BuildUpTarget1();
// Now, call BuildUp() on target, and
// we should see the Gateway property assigned
ObjectFactory.BuildUp(target);
target.Gateway.ShouldBeTheSameAs(theGateway);
}
Then you can create a new FilterAttributeFilterProvider like this:
public class DependencyResolverFilterProvider : FilterAttributeFilterProvider
{
public override IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
var filters = base.GetFilters(controllerContext, actionDescriptor);
foreach (var filter in filters)
{
//DI via Setter Injection
DependencyResolver.BuildUp(filter.Instance);
}
return filters;
}
}
Then finally add your custom filter provider to the .net pipeline.
private static void RegisterProviderAndFilters()
{
var oldProvider = FilterProviders.Providers.Single(f => f is FilterAttributeFilterProvider);
FilterProviders.Providers.Remove(oldProvider);
FilterProviders.Providers.Add(new DependencyResolverFilterProvider());
RegisterGlobalFilters(GlobalFilters.Filters);
}
Hope this helps!
wm