Unit test exception when calling API service using IHttpContextAccessor [duplicate] - api

I have a method to get header value using IHttpContextAccessor
public class HeaderConfiguration : IHeaderConfiguration
{
public HeaderConfiguration()
{
}
public string GetTenantId(IHttpContextAccessor httpContextAccessor)
{
return httpContextAccessor.HttpContext.Request.Headers["Tenant-ID"].ToString();
}
}
I am testing GetBookByBookId method
Let's say the method looks like this:
public class Book
{
private readonly IHttpContextAccessor _httpContextAccessor;
private IHeaderConfiguration _headerConfiguration;
private string _tenantID;
public Book(IHeaderConfiguration headerConfiguration, IHttpContextAccessor httpContextAccessor){
var headerConfig = new HeaderConfiguration();
_httpContextAccessor = httpContextAccessor;
_tenantID = headerConfig.GetTenantId(_httpContextAccessor);
}
public Task<List<BookModel>> GetBookByBookId(string id){
//do something with the _tenantId
//...
}
}
Here's my unit test for GetBookByBookId method
[Fact]
public void test_GetBookByBookId()
{
//Arrange
//Mock IHttpContextAccessor
var mockHttpContextAccessor = new Mock<IHttpContextAccessor>();
mockHttpContextAccessor.Setup(req => req.HttpContext.Request.Headers["Tenant-ID"].ToString()).Returns(It.IsAny<string>());
//Mock HeaderConfiguration
var mockHeaderConfiguration = new Mock<IHeaderConfiguration>();
mockHeaderConfiguration.Setup(x => x.GetTenantId(mockHttpContextAccessor.Object)).Returns(It.IsAny<string>());
var book = new Book( mockHttpContextAccessor.Object, mockHeaderConfiguration.Object);
var bookId = "100";
//Act
var result = book.GetBookByBookId(bookId);
//Assert
result.Result.Should().NotBeNull().And.
BeOfType<List<BookModel>>();
}
But for this line:
mockHttpContextAccessor.Setup(req => req.HttpContext.Request.Headers["Tenant-ID"].ToString()).Returns(It.IsAny<string>());
It says
System.NotSupportedException: 'Type to mock must be an interface or an abstract or non-sealed class. '
I was wondering what's the proper way to mock IHttpContextAccessor with header value?

You can use the DefaultHttpContext as a backing for the IHttpContextAccessor.HttpContext. Saves you having to set-up too many things
Next you cannot use It.IsAny<string>() as a Returns result. They were meant to be used in the set up expressions alone.
Check the refactor
[Fact]
public async Task test_GetBookByBookId() {
//Arrange
//Mock IHttpContextAccessor
var mockHttpContextAccessor = new Mock<IHttpContextAccessor>();
var context = new DefaultHttpContext();
var fakeTenantId = "abcd";
context.Request.Headers["Tenant-ID"] = fakeTenantId;
mockHttpContextAccessor.Setup(_ => _.HttpContext).Returns(context);
//Mock HeaderConfiguration
var mockHeaderConfiguration = new Mock<IHeaderConfiguration>();
mockHeaderConfiguration
.Setup(_ => _.GetTenantId(It.IsAny<IHttpContextAccessor>()))
.Returns(fakeTenantId);
var book = new Book(mockHttpContextAccessor.Object, mockHeaderConfiguration.Object);
var bookId = "100";
//Act
var result = await book.GetBookByBookId(bookId);
//Assert
result.Should().NotBeNull().And.
BeOfType<List<BookModel>>();
}
There may also be an issue with the Class Under Test as it is manually initializing the HeaderConfiguration when it should actually be explicitly injected.
public Book(IHeaderConfiguration headerConfiguration, IHttpContextAccessor httpContextAccessor) {
_httpContextAccessor = httpContextAccessor;
_tenantID = headerConfiguration.GetTenantId(_httpContextAccessor);
}

In my scenario I had to mock IHttpContextAccessor and access the inner request url bits.
I'm sharing it here because I spent a decent amount of time figuring this out and hopefully it'll help someone.
readonly Mock<IHttpContextAccessor> _HttpContextAccessor =
new Mock<IHttpContextAccessor>(MockBehavior.Strict);
void SetupHttpContextAccessorWithUrl(string currentUrl)
{
var httpContext = new DefaultHttpContext();
setRequestUrl(httpContext.Request, currentUrl);
_HttpContextAccessor
.SetupGet(accessor => accessor.HttpContext)
.Returns(httpContext);
static void setRequestUrl(HttpRequest httpRequest, string url)
{
UriHelper
.FromAbsolute(url, out var scheme, out var host, out var path, out var query,
fragment: out var _);
httpRequest.Scheme = scheme;
httpRequest.Host = host;
httpRequest.Path = path;
httpRequest.QueryString = query;
}
}

If you are making use of the wonderful NSubstitute package for NUnit, you can do this...
var mockHttpAccessor = Substitute.For<IHttpContextAccessor>();
var context = new DefaultHttpContext
{
Connection =
{
Id = Guid.NewGuid().ToString()
}
};
mockHttpAccessor.HttpContext.Returns(context);
// usage...

Related

Sharing value from custom middleware to anywhere

I'm trying to pass the client IP address from the middleware to anywhere in the project.
I have made three attempts (session, cookie, items), but none of them is working.
I get the value using custom middleware as follows:
public Task Invoke(HttpContext httpContext)
{
var httpConnectionFeature = httpContext.Features.Get<IHttpConnectionFeature>();
var remoteIpAddress = httpConnectionFeature?.RemoteIpAddress;
//var remoteIpAddress = httpContext.Connection.RemoteIpAddress;
if (remoteIpAddress.ToString() != "::1")
{
var cookieOptions = new CookieOptions
{
Secure = true,
HttpOnly = true,
SameSite = SameSiteMode.None,
Expires = DateTime.Now.AddMinutes(5),
IsEssential = true
};
httpContext.Response.Cookies.Append("anonymousIP", remoteIpAddress.ToString(), cookieOptions);
httpContext.Session.SetString("anonymousIP", remoteIpAddress.ToString());
httpContext.Items.Add("anonymousIP", remoteIpAddress.ToString());
}
return _next(httpContext);
}
but I can't receive it!
var ctxt = Context.GetHttpContext();
var x = ctxt.Session.GetString("anonymousIP");
var y = ctxt.Items["anonymousIP"];
var z = ctxt.Request.Cookies["anonymousIP"];
is there anything wrong? is it possible to share a value from the middleware to anywhere?
for a similar requirement, I created a class as below
public class CurrentUserService : ICurrentUserService
{
private readonly IHttpContextAccessor _httpContextAccessor;
public CurrentUserService(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public string MyVariable
{
get
{
// get data as you need, e.g. from header or request object.
return _httpContextAccessor.HttpContext.Request.Headers["MyVariable"].ToString();
}
}
}
then using Dependency Injection - inject "ICurrentUserService" anywhere you need.

xUnit, Moq with UnitOfWork and general repository

I tried a lot of examples but I do not get the good response.
The ReportUpload method creates some Report entity based on the list from ExcelManager list
and adds them to the Reports DbSet.
My goal would be to read out the added entities from the mocked DbSet and assert them.
How can I pass the mocked DbContext and Dbset into UnitOfWork, please?
GeneralRepository.cs
public class GeneralRepository<TContext, TEntity> : IGeneralRepository<TEntity>
where TEntity : class
where TContext : DbContext
{
protected readonly TContext _context;
protected readonly DbSet<TEntity> dbSet;
public GeneralRepository(TContext context)
{
_context = context;
dbSet = _context.Set<TEntity>();
}
public async Task AddAsync(TEntity entity)
{
await _context.Set<TEntity>().AddAsync(entity);
}
...
}
IUnitOfWork.cs
public interface IUnitOfWork : IDisposable
{
IGeneralRepository<Report> Reports{ get; }
...
}
UnitOfWork.cs
public sealed class UnitOfWork : IUnitOfWork
{
private readonly MyDbContext _context;
public UnitOfWork(MyDbContext context)
{
_context = context;
}
private IGeneralRepository<Report>_report;
public IGeneralRepository<Report> Reports => _report ??= new GeneralRepository<MyDbContext, Report>(_context);
...
}
ReportService.cs
public class ReportService : GeneralService, IReportService
{
private readonly IExcelManager _excelManager;
public ReportService(IUnitOfWork unitOfWork, IExcelManager excelManager)
{
UnitOfWork = unitOfWork;
_excelManager = excelManager;
}
public async Task<string> ReportUpload(MemoryStream ms)
{
var workingList = _excelManager.ReadExcel(ms);
var i = 0;
while (i < workingList.Count)
{
var report = new Report { ... }
await UnitOfWork.Reports.AddAsync(report);
}
....
}
ReportServiceTest.cs
public class ReportServiceTests
{
[Fact()]
public async Task ReportUploadTest()
{
//Arrange
....
var mockSet = new Mock<DbSet<Report>>();
var mockContext = new Mock<MyDbContext>();
mockContext.Setup(x => x.Reports).Returns(mockSet.Object);
var reportRepositoryMock = new Mock<IGeneralRepository<Report>>();
reportRepositoryMock.Setup(m => m.AddAsync(It.IsAny<Report>()));
var unitOfWorkMock = new Mock<IUnitOfWork>();
unitOfWorkMock.Setup(p => p.Reports)
.Returns(reportRepositoryMock.Object);
...
//Act
var reportService = new ReportService(unitOfWorkMock.Object,exelManagerMock.Object);
await reportService.ReportUpload(new MemoryStream());
//Assert
???
}
DbContext cannot be passed to the UnitOfWork object because its context field is private.
I had to use SQLite in memory to test the GeneralRepository method.
var exelManagerMock = new Mock<IExcelManager>();
exelManagerMock.Setup(p => p.ReadExcel(It.IsAny<MemoryStream>()))
.Returns(listOfExcelReadResult);
var dbFixture = new DatabaseFixture();
var context = dbFixture.CreateContext();
var unitOfWork = new UnitOfWork(context);
//I need some plus data
await unitOfWork.Providers.AddRangeAsync(providers);
await context.SaveChangesAsync();
var reportService = new ReportService(unitOfWork, exelManagerMock.Object);
await reportService.ReportUpload(new MemoryStream(), 2021);
var allReports = await reportService.UnitOfWork.Reports.GetAsync();
Assert.Equal(3, allReports.Count);

Configure OData Test Server

Trying to set-up unit / integration tests for some extensions I am writing for the OdataQueryOptions class. I am using .net core 3.1.
In order to create the SUT instance - I need a HttpRequest. Which I creating using the WebApplicationFactory
public class TestingWebApplicationFactoryFixture : WebApplicationFactory<TestStartUp>
{
protected override IHostBuilder CreateHostBuilder()
{
var builder = Host.CreateDefaultBuilder();
builder.ConfigureWebHost(hostBuilder =>
{
hostBuilder.ConfigureServices(services =>
{
services.AddMvc(options => options.EnableEndpointRouting = false);
services.AddOData();
}).Configure(app =>
{
app.UseMvc(routeBuilder =>
{
routeBuilder.EnableDependencyInjection();
routeBuilder.Select().Expand().OrderBy().Filter().MaxTop(int.MaxValue);
});
});
});
return builder;
}
I arrange the test to use the TestServer to produce the HttpContext. The OdataQueryContext and HttpRequest is then used to instantiate the OdataQueryOptions object.
const string path = "/?$filter=SalesOrderID eq 43659";
var httpContext = await _testingWebApplicationFactoryFixture.Server.SendAsync(context =>
{
context.Request.Method = HttpMethods.Get;
context.Request.Path = path;
});
var modelBuilder = new ODataConventionModelBuilder();
modelBuilder.AddEntityType(typeof(Customer));
var model = modelBuilder.GetEdmModel();
var odataQueryContext = new ODataQueryContext(model, typeof(Customer), new ODataPath());
var sut = new ODataQueryOptions<Customer>(odataQueryContext, httpContext.Request);
I am getting an exception during the instantiation of the object:
System.ArgumentNullException
Value cannot be null. (Parameter 'provider')
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T]
(IServiceProvider provider)
at Microsoft.AspNet.OData.Extensions.HttpRequestExtensions.CreateRequestScope(HttpRequest request,
String routeName)
at Microsoft.AspNet.OData.Extensions.HttpRequestExtensions.CreateRequestContainer(HttpRequest
request, String routeName)
at Microsoft.AspNet.OData.Extensions.HttpRequestExtensions.GetRequestContainer(HttpRequest request)
at Microsoft.AspNet.OData.Query.ODataQueryOptions..ctor(ODataQueryContext context, HttpRequest
request)
at Microsoft.AspNet.OData.Query.ODataQueryOptions`1..ctor(ODataQueryContext context, HttpRequest
request)
Digging into the actual method that is throwing - it is because the IServiceProvider is null. Shouldn't this be handled by the host?
UPDATE:
I modified the test method a bit so that I eliminate the WebApplicationFactory class.
Instead I create a TestServer with an IWebHostBuilder:
private IWebHostBuilder GetBuilder()
{
var webHostBuilder = new WebHostBuilder();
webHostBuilder
.UseTestServer()
.ConfigureServices(services =>
{
services.AddMvc(options => options.EnableEndpointRouting = false);
services.AddOData();
}).Configure(app =>
{
app.UseMvc(routeBuilder =>
{
routeBuilder.EnableDependencyInjection();
routeBuilder.Select().Expand().OrderBy().Filter().MaxTop(int.MaxValue);
});
});
return webHostBuilder;
}
And then create the TestServer:
[Fact]
public async Task QueryGenerator_Generate_SomeExpress_ShouldProduce()
{
const string path = "/?$filter=SalesOrderID eq 43659";
var testServer = new TestServer(GetBuilder());
var httpContext = await testServer.SendAsync(context =>
{
context.Request.Method = HttpMethods.Get;
context.Request.Path = path;
});
var modelBuilder = new ODataConventionModelBuilder();
modelBuilder.AddEntityType(typeof(Customer));
var model = modelBuilder.GetEdmModel();
var odataQueryContext = new ODataQueryContext(model, typeof(Customer), new ODataPath());
var sut = new ODataQueryOptions<Customer>(odataQueryContext, httpContext.Request);
}
I get the same exception. Why is the IServiceProvider null?
Never got a solution to using TestServer, but I found a work around. At the end of the day I needed the OdataQueryOptions class generated by the framework. So I created an IClassFixture<> in Xunit to manually get it created.
public class OdataQueryOptionFixture
{
public IServiceProvider Provider { get; private set; }
private IEdmModel _edmModel;
public OdataQueryOptionFixture()
{
SetupFixture();
}
public ODataQueryOptions<T> CreateODataQueryOptions<T>(HttpRequest request)
where T : class
{
var odataQueryContext = CreateOdataQueryContext<T>();
var odataQueryOptions = new ODataQueryOptions<T>(odataQueryContext, request);
return odataQueryOptions;
}
private ODataQueryContext CreateOdataQueryContext<T>()
where T : class
{
var odataQueryContext = new ODataQueryContext(_edmModel, typeof(T), new ODataPath());
return odataQueryContext;
}
private void SetupFixture()
{
var collection = new ServiceCollection();
collection.AddOData();
collection.AddTransient<ODataUriResolver>();
collection.AddTransient<ODataQueryValidator>();
Provider = collection.BuildServiceProvider();
ConfigureRoutes();
BuildModel();
}
private void ConfigureRoutes()
{
var routeBuilder = new RouteBuilder(Mock.Of<IApplicationBuilder>(x => x.ApplicationServices == Provider));
routeBuilder.Select().Expand().OrderBy().Filter().MaxTop(int.MaxValue).Count();
routeBuilder.EnableDependencyInjection();
}
private void BuildModel()
{
var edmContext = new AdventureWorksEdmContext();
_edmModel = edmContext.BuildModel();
}
Using the class fixture in a test class to construct the OdataQueryOptions
private QueryOptionsBuilder<Customer> GetSut(HttpRequest request)
{
var odataQueryOptions = _odataQueryOptionFixture.CreateODataQueryOptions<Customer>(request);
var odataQuerySettings = new ODataQuerySettings();
var odataValidationSettings = new ODataValidationSettings();
var customerExpandBinder = new CustomerExpandBinder(odataValidationSettings, odataQueryOptions.SelectExpand);
var customerOrderByBinder = new CustomerOrderByBinder(odataValidationSettings, odataQueryOptions.OrderBy);
var customerSelectBinder = new CustomerSelectBinder(odataValidationSettings, odataQueryOptions.SelectExpand);
var customerCompositeBinder = new CustomerCompositeBinder(customerExpandBinder, customerOrderByBinder, customerSelectBinder);
return new QueryOptionsBuilder<Customer>(customerCompositeBinder, odataQuerySettings);
}
TestServer would have been easier - but this gets the job done.

Blazor recaptcha validation attribute IHttpContextAccessor is always null

I thought I would have a go at using Blazor server-side, and so far I've managed to overcome most headaches one way or another and enjoyed it, until now.
I'm trying to write a validator for Google Recaptcha v3, which requires a users IP address. Usually I would just get the IHttpContextAccessor with:
var httpContextAccessor = (IHttpContextAccessor)validationContext.GetService(typeof(IHttpContextAccessor));
But that now returns null! I also found that trying to get IConfiguration in the same way failed, but for that, I could just make a static property in Startup.cs.
This is the last hurdle in a days work, and it's got me baffled.
Any ideas on how to get that IP address into a validator?
Thanks!
Edit:
I just found the error making httpContextAccessor null!
((System.RuntimeType)validationContext.ObjectType).DeclaringMethodthrew an exception of type 'System.InvalidOperationException'
this is the validator:
public class GoogleReCaptchaValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Lazy<ValidationResult> errorResult = new Lazy<ValidationResult>(() => new ValidationResult("Google reCAPTCHA validation failed", new String[] { validationContext.MemberName }));
if (value == null || String.IsNullOrWhiteSpace(value.ToString()))
{
return errorResult.Value;
}
var configuration = Startup.Configuration;
string reCaptchResponse = value.ToString();
string reCaptchaSecret = configuration["GoogleReCaptcha:SecretKey"];
IHttpContextAccessor httpContextAccessor = validationContext.GetService(typeof(IHttpContextAccessor)) as IHttpContextAccessor;
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("secret", reCaptchaSecret),
new KeyValuePair<string, string>("response", reCaptchResponse),
new KeyValuePair<string, string>("remoteip", httpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString())
});
HttpClient httpClient = new HttpClient();
var httpResponse = httpClient.PostAsync("https://www.google.com/recaptcha/api/siteverify", content).Result;
if (httpResponse.StatusCode != HttpStatusCode.OK)
{
return errorResult.Value;
}
String jsonResponse = httpResponse.Content.ReadAsStringAsync().Result;
dynamic jsonData = JObject.Parse(jsonResponse);
if (jsonData.success != true.ToString().ToLower())
{
return errorResult.Value;
}
return ValidationResult.Success;
}
}
For this issue, it is caused by that when DataAnnotationsValidator call AddDataAnnotationsValidation, it did not pass IServiceProvider to ValidationContext.
For this issue, you could check Make dependency resolution available for EditContext form validation so that custom validators can access services. #11397
private static void ValidateModel(EditContext editContext, ValidationMessageStore messages)
{
var validationContext = new ValidationContext(editContext.Model);
var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(editContext.Model, validationContext, validationResults, true);
// Transfer results to the ValidationMessageStore
messages.Clear();
foreach (var validationResult in validationResults)
{
foreach (var memberName in validationResult.MemberNames)
{
messages.Add(editContext.Field(memberName), validationResult.ErrorMessage);
}
}
editContext.NotifyValidationStateChanged();
}
For a workaround, you could implement your own DataAnnotationsValidator and AddDataAnnotationsValidation .
Follow steps below:
Custom DataAnnotationsValidator
public class DIDataAnnotationsValidator: DataAnnotationsValidator
{
[CascadingParameter] EditContext DICurrentEditContext { get; set; }
[Inject]
protected IServiceProvider ServiceProvider { get; set; }
protected override void OnInitialized()
{
if (DICurrentEditContext == null)
{
throw new InvalidOperationException($"{nameof(DataAnnotationsValidator)} requires a cascading " +
$"parameter of type {nameof(EditContext)}. For example, you can use {nameof(DataAnnotationsValidator)} " +
$"inside an EditForm.");
}
DICurrentEditContext.AddDataAnnotationsValidationWithDI(ServiceProvider);
}
}
Custom EditContextDataAnnotationsExtensions
public static class EditContextDataAnnotationsExtensions
{
private static ConcurrentDictionary<(Type ModelType, string FieldName), PropertyInfo> _propertyInfoCache
= new ConcurrentDictionary<(Type, string), PropertyInfo>();
public static EditContext AddDataAnnotationsValidationWithDI(this EditContext editContext, IServiceProvider serviceProvider)
{
if (editContext == null)
{
throw new ArgumentNullException(nameof(editContext));
}
var messages = new ValidationMessageStore(editContext);
// Perform object-level validation on request
editContext.OnValidationRequested +=
(sender, eventArgs) => ValidateModel((EditContext)sender, serviceProvider, messages);
// Perform per-field validation on each field edit
editContext.OnFieldChanged +=
(sender, eventArgs) => ValidateField(editContext, serviceProvider, messages, eventArgs.FieldIdentifier);
return editContext;
}
private static void ValidateModel(EditContext editContext, IServiceProvider serviceProvider,ValidationMessageStore messages)
{
var validationContext = new ValidationContext(editContext.Model, serviceProvider, null);
var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(editContext.Model, validationContext, validationResults, true);
// Transfer results to the ValidationMessageStore
messages.Clear();
foreach (var validationResult in validationResults)
{
foreach (var memberName in validationResult.MemberNames)
{
messages.Add(editContext.Field(memberName), validationResult.ErrorMessage);
}
}
editContext.NotifyValidationStateChanged();
}
private static void ValidateField(EditContext editContext, IServiceProvider serviceProvider, ValidationMessageStore messages, in FieldIdentifier fieldIdentifier)
{
if (TryGetValidatableProperty(fieldIdentifier, out var propertyInfo))
{
var propertyValue = propertyInfo.GetValue(fieldIdentifier.Model);
var validationContext = new ValidationContext(fieldIdentifier.Model, serviceProvider, null)
{
MemberName = propertyInfo.Name
};
var results = new List<ValidationResult>();
Validator.TryValidateProperty(propertyValue, validationContext, results);
messages.Clear(fieldIdentifier);
messages.Add(fieldIdentifier, results.Select(result => result.ErrorMessage));
// We have to notify even if there were no messages before and are still no messages now,
// because the "state" that changed might be the completion of some async validation task
editContext.NotifyValidationStateChanged();
}
}
private static bool TryGetValidatableProperty(in FieldIdentifier fieldIdentifier, out PropertyInfo propertyInfo)
{
var cacheKey = (ModelType: fieldIdentifier.Model.GetType(), fieldIdentifier.FieldName);
if (!_propertyInfoCache.TryGetValue(cacheKey, out propertyInfo))
{
// DataAnnotations only validates public properties, so that's all we'll look for
// If we can't find it, cache 'null' so we don't have to try again next time
propertyInfo = cacheKey.ModelType.GetProperty(cacheKey.FieldName);
// No need to lock, because it doesn't matter if we write the same value twice
_propertyInfoCache[cacheKey] = propertyInfo;
}
return propertyInfo != null;
}
}
Replace DataAnnotationsValidator with DIDataAnnotationsValidator
<EditForm Model="#starship" OnValidSubmit="#HandleValidSubmit">
#*<DataAnnotationsValidator />*#
<DIDataAnnotationsValidator />
<ValidationSummary />
</EditForm>
For IHttpContextAccessor, you need to register in Startup.cs like
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddHttpContextAccessor();
}

CookiePolicyOptions how to set from the database

I have repository structure... IUnitOfWork unitOfWork,
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;
});
options.CheckConsentNeeded
I wanna set it from the database
var culture = Thread.CurrentThread.CurrentCulture.ToString();
var checkConsentNeededCookieettings = _unitOfWork.Settings.GetAll().Where(i => i.Language.Culture == culture).FirstOrDefault().CheckConsentNeededCookie;
you know that these are services
how to configure it and where it is ?
di ? middleware or override ?
can you give me advice about that? and example
I would use IPostConfigureOptions for this. Create a class that implements IPostConfigureOptions<CookiePolicyOptions>:
internal class CookiePolicyPostConfigureOptions : IPostConfigureOptions<CookiePolicyOptions>
{
private readonly IUnitOfWork _unitOfWork;
public CookiePolicyPostConfigureOptions(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public void PostConfigure(string name, CookiePolicyOptions options)
{
// do something with _unitOfWork and set values on `options`
}
}
Then in Startup:
services.ConfigureOptions<CookiePolicyPostConfigureOptions>();
If I use same way for CustomIdentityOptions, there is a error that :
InvalidOperationException: ValueFactory attempted to access the Value property of this instance.
System.Lazy.ViaFactory(LazyThreadSafetyMode mode)
what is the different between IdentityOptions to CookiePolicyOptions
services.ConfigureOptions<CustomIdentityOptions>();
internal class CustomIdentityOptions : IPostConfigureOptions<IdentityOptions>
{
private readonly IUnitOfWork _unitOfWork;
private readonly ILogger<CustomIdentityOptions> _logger;
public CustomIdentityOptions(IUnitOfWork unitOfWork, ILogger<CustomIdentityOptions> logger)
{
_unitOfWork = unitOfWork;
_logger = logger;
}
public void PostConfigure(string name, IdentityOptions options)
{
var staticSettings = _unitOfWork.StaticSettings.GetAll().OrderBy(i => i.StaticSettingId).FirstOrDefault();
options.Lockout = new LockoutOptions
{
AllowedForNewUsers = staticSettings.AllowedForNewUsers,
DefaultLockoutTimeSpan = TimeSpan.FromMinutes(staticSettings.DefaultLockoutTimeSpanFromMinutes),
MaxFailedAccessAttempts = staticSettings.MaxFailedAccessAttempts
};
options.Password = new PasswordOptions
{
RequireDigit = staticSettings.RequireDigit,
RequiredLength = staticSettings.RequiredLength,
RequiredUniqueChars = staticSettings.RequiredUniqueChars,
RequireLowercase = staticSettings.RequireLowercase,
RequireNonAlphanumeric = staticSettings.RequireNonAlphanumeric,
RequireUppercase = staticSettings.RequireUppercase
};
options.SignIn = new SignInOptions
{
RequireConfirmedEmail = staticSettings.RequireConfirmedEmail,
RequireConfirmedPhoneNumber = staticSettings.RequireConfirmedPhoneNumber
};
}
}
I'm using .NET5, and if your database/repository is injected using the out of the box DI, than you can get the service right from the httpContext, so just use a lambda function like this:
services.Configure<CookiePolicyOptions>( (options) => {
options.CheckConsentNeeded = (context) => {
// get repo from DI
var repo = context.RequestServices.GetService<IRepository>();
// .. Determine result
return repo.GetAll().Where(i => i.Language.Culture == culture).FirstOrDefault().CheckConsentNeededCookie;
};
});