Getting IHostingEnvironment.ContentRootPath inside DatabaseInitializer - asp.net-core

I'm trying to seed my db with some data stored in json files.
I need to inject an IHostingEnvironment inside my IDatabaseInitializer.Seed() method so I can read the json files using IHostingEnvironment.ContentRootPath.
This property is injected by the main container by default but the constructor of an DbConfiguration must be parameterless, so I can't pipe IHostingEnvironment through DbConfiguration into SetDatabaseInitializer(new DatabaseInitializer()).
/*
* Database Context
*/
[DbConfigurationType(typeof(DatabaseConfiguration))]
public class DatabaseContext : DbContext
{
public DbSet<User> Users { get; set; }
public DatabaseContext(string nameOrConnectionString) : base(nameOrConnectionString) { }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<System.Data.Entity.ModelConfiguration.Conventions.PluralizingTableNameConvention>();
base.OnModelCreating(modelBuilder);
}
}
/*
* Database Configuration
*/
public class DatabaseConfiguration : DbConfiguration
{
// Can't receive injected IHostingEnvironment env because constructor must be parameterless
public DatabaseConfiguration()
{
SetProviderServices("System.Data.SqlClient", SqlProviderServices.Instance);
// Could pass IHostingEnvironment through constructor
SetDatabaseInitializer(new DatabaseInitializer());
}
}
/*
* Database Initializer
*/
public class DatabaseInitializer : DropCreateDatabaseAlways<DatabaseContext>
{
private readonly IHostingEnvironment env;
// Receives IHostingEnvironment from DatabaseConfiguration
public DatabaseInitializer(IHostingEnvironment env)
{
this.env = env;
}
protected override void Seed(DatabaseContext context)
{
// Read some .json files
}
}

I'm initializing my db, and had to write a separate class, which I call from configure, you could do the same thing and pass in the path as a parameter
Here's how I call it:
using (var scope = scopeFactory.CreateScope())
{
// Initialise Database
var initializer = scope.ServiceProvider.GetService<IDbInitializer>();
initializer.SeedAsync().Wait();
// initialize plugin manager
var manager = scope.ServiceProvider.GetService<IPluginManager>();
manager.Initialize(dbConnection);
if (Configuration.GetSection("PluginService").GetValue<bool>("RunAtStartup") == true)
manager.Start();
}
and here's the db initialize class
public interface IDbInitializer
{
Task SeedAsync();
}
public class DbInitializer : IDbInitializer
{
private ApplicationDbContext _context;
private RoleManager<IdentityRole> _roleManager;
private UserManager<ApplicationUser> _userManager;
public DbInitializer(ApplicationDbContext context,
RoleManager<IdentityRole> roleManager,
UserManager<ApplicationUser> userManager)
{
_context = context;
_roleManager = roleManager;
_userManager = userManager;
}
public async Task SeedAsync()
{
await CreateRolesAsync();
await CreateAdminUserAsync();
await SeedMenuAsync();
}
private async Task CreateRolesAsync()
{
List<IdentityRole> roles = new List<IdentityRole>();
roles.Add(new IdentityRole { Name = "Admin", NormalizedName = "ADMINISTRATOR" });
roles.Add(new IdentityRole { Name = "Member", NormalizedName = "MEMBER" }); // An email confirmed memeber
roles.Add(new IdentityRole { Name = "Guest", NormalizedName = "GUEST" }); // Used for a user that has only checked out as guest
roles.Add(new IdentityRole { Name = "NotConfirmed", NormalizedName = "NOTCONFIRMED" }); // Used when a guest hasnt confirmed there registration
foreach (var role in roles)
{
var roleExists = await _roleManager.RoleExistsAsync(role.Name);
if (!roleExists)
{
await _roleManager.CreateAsync(role);
}
}
}
...
Hope that helps

Related

How to convert ODataQueryOptions<DtoType> to ODataQueryOptions<EntityType> to query the underlying storage?

I have a webapi controller using ProductDTO type for clients but the repository is using a Product type.
I would like to use odata on my endpoint. I receive the ODataQueryOptions parameter and I want to pass it to repository (implemented using CosmosDB).
I cant seem to figure out how to convert from ODataQueryOptions<ProductDTO> to ODataQueryOptions<Product>.
[Route("api/[controller]")]
public class ProductsController<ProductsDTO, Product> : ControllerBase
{
IRepository<Product> _repository;
IMapper _mapper;
[HttpGet]
public async Task<ActionResult<IList<ProductDTO>>> Get(ODataQueryOptions<ProductDTO> queryOptions)
{
var mappedQueryOptions = ... // convert 'queryOptions' to ODataQueryOptions<Product> ???
var products = await _repository.Get(mappedQueryOptions);
return Ok(_mapper.Map<IEnumerable<Product>, IEnumerable<ProductDTO>>(products));
}
}
In my aspnetcore service composition I create and inject automapper
var configuration = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new ProductProfile());
cfg.AddExpressionMapping();
});
internal class ProductProfile : Profile
{
public ProductProfile()
{
CreateMap<Product, ProductDto>().ReverseMap();
}
}
I managed to extract the queryoptions filter as a lambda expression Expression<Func<Product, bool>> (using automapper MapExpression) and passed it to repository , that works to a certain extent but I want to get the select , top, skip, etc. as well.
Any suggestions on how that could be done?
can you review the example?
[ODataRouteComponent("api")]
public class UsersController : ODataController
{
#region ctor
private readonly ILogger<UsersController> _logger;
private readonly IMapper _mapper;
private readonly UserManager<AspNetUser> _userManager;
public UsersController(IMapper mapper,
UserManager<AspNetUser> userManager,
ILogger<UsersController> logger)
{
_mapper = mapper;
_userManager = userManager;
_logger = logger;
}
#endregion
[HttpGet]
[EnableQuery(PageSize = 10)]
public async Task<IActionResult> Get(ODataQueryOptions<UserDto> options)
{
var user = await _userManager.GetUserAsync(User);
_logger.LogInformation("{UserUserName} is getting all users", user.UserName);
return Ok(await _userManager.Users.GetAsync(_mapper, options));
}
[HttpGet]
[EnableQuery]
public async Task<IActionResult> Get(int key, ODataQueryOptions<UserDto> options)
{
var user = await _userManager.GetUserAsync(User);
_logger.LogInformation("{UserUserName} is getting user with id {Key}", user.UserName, key.ToString());
return Ok(_userManager.Users.Get(_mapper, options).FirstOrDefault(x => x.Id == key));
}
}
services.AddControllers(opt =>{}).AddOData((opt, _) =>
{
opt.EnableQueryFeatures().AddRouteComponents("api", EdmModelBuilder.GetEdmModelv1());
});
public class MappingProfile : Profile
{
public MappingProfile() : base(nameof(WebUI))
{
CreateMap<AspNetUser, UserDto>();
}
}
internal static class EdmModelBuilder
{
internal static IEdmModel GetEdmModelv1()
{
ODataConventionModelBuilder builder = new();
builder.EnableLowerCamelCase();
#region UserDto
builder.EntitySet<UserDto>(nameof(DataListContext.Users))
.EntityType
.Page(25, 15);
var f5 = builder.Function("Get");
f5.Parameter<int>("key").Required();
f5.ReturnsFromEntitySet<UserDto>(nameof(DataListContext.Users));
#endregion
return builder.GetEdmModel();
}
}

Value cannot be null - In-Memory Database .Net

I am trying to use a In-Memory Database, but I get this message:
System.ArgumentNullException: 'Value cannot be null. (Parameter
'source')'
I read a lot some similar question related with this issue, but every article is related with the connection String, and I think I must not use a ConnectionString because is a In-Memory Database. What Do I do wrong? I leave my code:
DbInitializer.cs - In this class appears the error
public static class DbInitializer
{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var _context = new AppDBContext(serviceProvider.GetRequiredService<DbContextOptions<AppDBContext>>()))
{
if (_context.AgentsRole.Any()) //In this line appears the error
return;
_context.AgentsRole.AddRange(
new Agent { Id = 1, Role_Name = "David Lopez", Is_Active = true, Role_Description = "This is a test for David Lopez" },
new Agent { Id = 2, Role_Name = "James Norris", Is_Active = false, Role_Description = "This is a test for James Norris" },
new Agent { Id = 3, Role_Name = "Jhon Norris", Is_Active = true, Role_Description = "This is a test for Jhon Norris" },
new Agent { Id = 4, Role_Name = "James Norr", Is_Active = true, Role_Description = "This is a test for James Norr" }
);
_context.SaveChanges();
}
}
}
Startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDBContext>(options=> options.UseInMemoryDatabase(databaseName: "InMemory_DB"));
services.AddControllers();
services.AddSwaggerGen();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My Test1 Api v1");
});
}
}
Program.cs:
public class Program
{
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var context = services.GetRequiredService<AppDBContext>();
DbInitializer.Initialize(services);
}
host.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Controller.cs:
[ApiController]
[Route("api/[controller]")]
public class AgentRoleController : ControllerBase
{
private readonly ILogger<AgentRoleController> _logger;
private readonly AppDBContext _context;
public AgentRoleController(ILogger<AgentRoleController> logger, AppDBContext context)
{
_logger = logger;
_context = context;
}
[HttpGet]
[SwaggerOperation("GetAgentsRole")]
[SwaggerResponse((int)HttpStatusCode.OK)]
[SwaggerResponse((int)HttpStatusCode.NotFound)]
public IEnumerable<Agent> Get()
{
return _context.AgentsRole;
}
}
AppDBContext.cs:
public class AppDBContext : DbContext
{
public AppDBContext(DbContextOptions<AppDBContext> options)
:base(options)
{
}
public DbSet<Agent> AgentsRole;
}
The solution is in AppDBContext.cs, I missed the initialization of AgentsRole, the solution is:
public class AppDBContext : DbContext
{
public AppDBContext(DbContextOptions<AppDBContext> options)
:base(options)
{
}
public DbSet<Agent> AgentsRole { get; set; } // I added this Part!
}

Admin lock or unlock account user in .Net Core

I am doing the management of a user's account when necessary I can Lock a user's account in case they violate it. Or can be unlocked if required. I got an error like this. Where am I wrong, I use .Net Core 5 to build my program. Error: "An unhandled exception occurred while processing the request.
NullReferenceException: Object reference not set to an instance of an object."
enter image description here
Interface
public bool LockUser(string email);
public bool UnlockUser(string email);
Repo
public bool LockUser(string email)
{
var userTask = _userManager.FindByEmailAsync(email);
userTask.Wait();
var user = userTask.Result;
var lockUserTask = _userManager.SetLockoutEnabledAsync(user, true);
lockUserTask.Wait();
var lockDateTask = _userManager.SetLockoutEndDateAsync(user, DateTimeOffset.Now);
lockDateTask.Wait();
return lockDateTask.Result.Succeeded && lockUserTask.Result.Succeeded;
}
Controller
public ActionResult LockUser(string email)
{
if (!_userRepository.LockUser(email))
{
throw new ArgumentException("Error");
}
return RedirectToAction("Index");
}
Please refer the following sample code, the UserRepository should like this, add the usermanager via the constructor parameter:
public interface IUserRepository
{
public bool LockUser(string email);
public bool UnlockUser(string email);
}
public class UserRepository : IUserRepository
{
private readonly UserManager<IdentityUser> _userManager;
public UserRepository(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
public bool LockUser(string email)
{
var userTask = _userManager.FindByEmailAsync(email);
userTask.Wait();
var user = userTask.Result;
var lockUserTask = _userManager.SetLockoutEnabledAsync(user, true);
lockUserTask.Wait();
var lockDateTask = _userManager.SetLockoutEndDateAsync(user, DateTimeOffset.Now);
lockDateTask.Wait();
return lockDateTask.Result.Succeeded && lockUserTask.Result.Succeeded;
}
public bool UnlockUser(string email)
{
//...
throw new NotImplementedException();
}
}
Then, add the service to the service container:
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddScoped<IUserRepository, UserRepository>();
services.AddControllersWithViews();
}
Then, in the MVC controller:
public class HomeController : Controller
{
private readonly IUserRepository _userRepository;
public HomeController(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public IActionResult Index(int id)
{
string email = "aa#hotmail.com";
if (!_userRepository.LockUser(email))
{
throw new ArgumentException("Error");
}
return View();
}
The debug screenshot like this:

.Net Core use new ApplicationBuilder instead of IApplicationBuilder from configure

I am attempting to create a "registry" so that just creating a new class implementing IServiceCollectionInitializer or IApplicationBuilderInitializer allows it to be loaded. Instead of having a giant start up class the registry would add those automatically.
My problem is I dont know how to make the app either use a new application builder or retrieve the the one given automatically without getting it from startup.
public class ServiceCollectionInitializerRegistry
{
private readonly IList<IServiceCollectionInitializer> _serviceCollectionInitializers;
private readonly IServiceCollection _serviceCollection;
private readonly IServiceProvider _serviceProvider;
public ServiceCollectionInitializerRegistry(IServiceCollection serviceCollection)
{
_serviceCollectionInitializers = new List<IServiceCollectionInitializer>();
_serviceCollection = serviceCollection;
_serviceProvider = serviceCollection.BuildServiceProvider();
}
public ServiceCollectionInitializerRegistry WithInitializers(
params IServiceCollectionInitializer[] initializers)
{
if (!initializers.Any())
{
return this;
}
foreach (var initializer in initializers)
{
_serviceCollectionInitializers.Add(initializer);
}
return this;
}
public ServiceCollectionInitializerRegistry WithAssemblyInitializers()
{
var assembly = Assembly.GetEntryAssembly();
var initializerTypes =
assembly.GetTypes()
.Where(type => typeof(IServiceCollectionInitializer).IsAssignableFrom(type));
if (!initializerTypes.Any())
{
return this;
}
foreach (var type in initializerTypes)
{
_serviceCollectionInitializers.Add((IServiceCollectionInitializer)Activator.CreateInstance(type));
}
return this;
}
public void Build()
{
var configuration = _serviceProvider.GetRequiredService<IConfiguration>();
foreach (var serviceCollectionInitializer in _serviceCollectionInitializers)
{
var logger = _serviceProvider.GetRequiredService<ILoggerProvider>()
.CreateLogger(serviceCollectionInitializer.GetType().AssemblyQualifiedName);
serviceCollectionInitializer
.Initialize(
configuration,
_serviceCollection, logger);
}
}
}
public class ExceptionHandlingInitializer : IServiceCollectionInitializer, IApplicationBuilderInitializer
{
public void Initialize(IConfiguration configuration, IServiceCollection services, ILogger logger)
{
services.AddSingleton<IExceptionMapper, ExceptionMapper>();
services.AddSingleton<IExceptionHandler, ExceptionHandler>();
}
public void Initialize(IConfiguration configuration, IApplicationBuilder builder, ILoggerFactory loggerFactory)
{
builder.UseExceptionHandlerMiddleware();
}
}

asp.net core how to change connection string at run time from entity framework (after login)

I have a .NET Core 3.0 web application. I would like to change the connection string at run time once login is successful.
IMO,you could not change the services.AddDbContext<T> at runtime.A workaround is that you add a DBContextFactory to create new dbcontext object when you login successfully.
Refer to following steps:
1.Create a DBContextFactory.cs
public static class DbContextFactory
{
public static Dictionary<string, string> ConnectionStrings { get; set; }
public static void SetConnectionString(Dictionary<string, string> connStrs)
{
ConnectionStrings = connStrs;
}
public static ApplicationDbContext Create(string connid)
{
if (!string.IsNullOrEmpty(connid))
{
var connStr = ConnectionStrings[connid];
var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
optionsBuilder.UseSqlServer(connStr);
return new ApplicationDbContext(optionsBuilder.Options);
}
else
{
throw new ArgumentNullException("ConnectionId");
}
}
}
2.Intialize DbContextFactory in startup Configure
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
Dictionary<string, string> connStrs = new Dictionary<string, string>();
connStrs.Add("DB1", "Your connection string 1");
connStrs.Add("DB2", "Your connection string 2");
DbContextFactory.SetConnectionString(connStrs);
//other middlewares
}
3.Usage
if(status)
{
var dbContext = DbContextFactory.Create("DB2");//get the dbcontext with connection string 2
}
i'm korean and Do not speak English well.
login after save session/cookie/DB UserConfig use, then DBContext edit
public class GroupwareContext : DbContext
{
private readonly HttpContext _httpContext;
public GroupwareContext(DbContextOptions<GroupwareContext> options, IHttpContextAccessor httpContextAccessor = null)
: base(options)
{
_httpContext = httpContextAccessor?.HttpContext;
}
public GroupwareContext(DbContextOptions<GroupwareContext> options)
: base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (_httpContext != null) {
if (_httpContext.Session.GetString("connectionString") != null)
{
var connectionString = (string)_httpContext.Session.GetString("connectionString");
optionsBuilder.UseSqlServer(connectionString);
}
}
}
public DbSet<Account> Account { get; set; }
}