Create database context from cookie and base path in Entity Framework Core - asp.net-core

Postgres database has multiple schemes like company1, company2, ... companyN
Browser sends cookie containing scheme name . Data access operations should occur in this scheme. Web application user can select different scheme. In this case different cookie value is set.
Npgsql EF Core Data provider is used.
ASP NET MVC 5 Core application registers factory in StartUp.cs :
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddScoped<IEevaContextFactory, EevaContextFactory>();
....
Home controller tries to use it:
public class HomeController : EevaController
{
public ActionResult Index()
{
var sm = new SchemeManager();
sm.PerformInsert();
....
This throws exception since factory member is null. How to fix this ?
public interface IEevaContextFactory
{
EevaContext Create();
}
public class EevaContextFactory : IEevaContextFactory
{
private IHttpContextAccessor httpContextAccessor;
private IConfiguration configuration;
public EevaContextFactory(IHttpContextAccessor httpContextAccessor, IConfiguration configuration)
{
this.httpContextAccessor = httpContextAccessor;
this.configuration = configuration;
}
public EevaContext Create()
{
var builder = new DbContextOptionsBuilder<EevaContext>();
var pathbase = httpContextAccessor.HttpContext.Request.PathBase.Value;
var scheme = httpContextAccessor.HttpContext.Request.Cookies["Scheme"];
var csb = new NpgsqlConnectionStringBuilder()
{
Host = pathbase,
SearchPath = scheme
};
builder.UseNpgsql(csb.ConnectionString);
return new EevaContext(builder.Options);
}
}
Scheme data acess methods:
public class SchemeManager
{
readonly IEevaContextFactory factory;
public SchemeManager(IEevaContextFactory factory)
{
this.factory = factory;
}
public SchemeManager()
{
}
public void PerformInsert()
{
using (var context = factory.Create())
{
var commandText = "INSERT into maksetin(maksetin) VALUES (CategoryName)";
context.Database.ExecuteSqlRaw(commandText);
}
}
}

var sm = new SchemeManager()
... will call the no-parameter constructor on SchemeManager so the IEevaContextFactory is not injected. You should inject your factory into your controller and pass it into your SchemeManager.
Remove your no-parameter constructor. It's not needed.
public class HomeController : EevaController
{
private IEevaContextFactor eevaFactory;
public HomeController(IEevaContextFactory factory)
{
eevaFactory = factory;
}
public ActionResult Index()
{
var sm = new SchemeManager(eevaFactory);
sm.PerformInsert();
....
}
}
Your other option is to put the SchemeManager in the DI container and then the DI container will auto-resolve IEevaContextFactory on the constructor and then just inject SchemeManager into your controller.
Either way, remove that no-parameter constructor.

Related

How to inject custom service on startup in .NET Core 5

I want to read my data from database and control it, and I need to do this in the request pipeline at startup.
So I have to do dependency injection at startup.
This is my (DI)
public Startup(IConfiguration configuration,IAuthHelper authHelper)
{
Configuration = configuration;
AuthHelper = authHelper;
}
public IConfiguration Configuration { get; }
public IAuthHelper AuthHelper;
I encounter this error
An error occurred while starting the application.
InvalidOperationException: Unable to resolve service for type 'Laboratory.Core.Services.Interfaces.IAuthHelper' while attempting to activate 'Laboratory.Startup'.
I used service like this
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
var siteDirectory = AuthHelper.GetSiteSetting().MediaPath;
var fileServerOptions = new FileServerOptions
{
FileProvider = new PhysicalFileProvider(Path.Combine
(env.WebRootPath, $#"{siteDirectory}User Picture\")),
RequestPath = "/ServerFiles"
};
app.UseFileServer(fileServerOptions);
}
This is my service
public class AuthHelper : IAuthHelper
{
private readonly LaboratoryContext _context;
private readonly IRazorPartialToStringRenderer _renderer;
private readonly IHttpContextAccessor _httpContext;
private readonly IHttpClientFactory _clientFactory;
public AuthHelper(LaboratoryContext context, IRazorPartialToStringRenderer renderer, IHttpContextAccessor httpContext, IHttpClientFactory clientFactory)
{
_context = context;
_renderer = renderer;
_httpContext = httpContext;
_clientFactory = clientFactory;
}
public TableSiteSetting GetSiteSetting()
{
try
{
return _context.TableSiteSettings.AsNoTracking().FirstOrDefault();
}
catch (SqlException)
{
return new TableSiteSetting() { StaticIp = "ServerConnectionError" };
}
catch (Exception)
{
return new TableSiteSetting() { StaticIp = "ServerError" };
}
}
}
Thanks for any help.
Your service can't be injected in Startup constructor because it has not been added yet to the dependency injection container. But you can inject it to the Configure method.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IAuthHelper authHelper)
{
...
}
I assume you have already registered the service in ConfigureServices
services.AddSingleton<IAuthHelper, AuthHelper>(); // Or scoped/transient depends what your service does.
You can get dbcontext service in program.cs and do what ever you like to your database data.
for example I use this approach to seed my database:
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var context = services.GetRequiredService<ApplicationDbContext>();
await ApplicationDbContextSeed.SeedSampleDataAsync(context)
}
host.Run();

static types cannot be used as type arguments

I am porting from traditional .net mvc application to .netcore.
Original application flow:
OSMSelectController -> OSMDB_Loader.cs -> StatusController
where the connectionstring is read and DAL is initialized.
This connectionstring is coming from static class but when I debug the value is null in here:
WPSGlobalSettings.ToolboxConnString
I have a static class for reading connectionstring from appsettings.
WPSGlobalSettings
public static class WPSGlobalSettings
{
public static NpgsqlConnectionStringBuilder ToolboxConnString = build_conn_str(ToolboxDatabaseName);
private static NpgsqlConnectionStringBuilder build_conn_str(string dbname)
{
string dbSetting = ConfigurationHelper.config.GetSection("ConnectionStrings")["DefaultConnection"];
...
}
}
Controller
public class StatusController : Microsoft.AspNetCore.Mvc.ControllerBase
{
protected StatusDAL status_dal = new StatusDAL(WPSGlobalSettings.ToolboxConnString);
}
Here it gives type exception, wpsglobalsettings was not initialized and toolboxconnstring is null.
I have tried adding it as singleton to Startup but then i get
static types cannot be used as type arguments
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConfiguration>(Configuration);
services.AddControllersWithViews();
services.AddSingleton<WPSGlobalSettings>();
ConfigurationHelper.Initialize(Configuration);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
WPSGlobalSettings.Configure(env);
...
}
Edit:
I have removed following from Startup.cs
services.AddSingleton<WPSGlobalSettings>();
Also, introduced DI as follows
protected StatusDAL status_dal;//= new StatusDAL(WPSGlobalSettings.ToolboxConnString);
public StatusController(IConfiguration config)
{
status_dal = new StatusDAL(config.GetConnectionString("toolboxConnectionStrWPS"));
}
Now another problem is older code calls controller constructor from another class as follows:
OSMDB_Loader.cs
public StatusAck LoadOSMSections(OsmLoadRequest request)
{
StatusAck statusack = new StatusController().PostStatusRecord();
}
Therefore I also added simple constructor in StatusController:
public StatusController()
{
}
but now ofcourse status_dal is always null
Something is not quiet right!
I got some help here and here
Also, the fact that static creates problem with multi-threading..from here
Here is my solution:
Original application flow was
OSMSelectController -> OSMDB_Loader.cs -> StatusController
where the connectionstring was read and DAL was initialized.
Now:
I injected IConfiguration in OSMSelectController and passed it to OSMDB_Loader.cs
private IConfiguration _config;
public OsmSelectController(IConfiguration config)
{
_config = config;
}
public IActionResult AddRoadsOsm([FromBody]OsmLoadRequest request)
{
...
StatusAck statusack = new OSM_DBLoader(user_id).LoadOSMSections(request, _config);
}
OSMDB_Loader.cs
public StatusAck LoadOSMSections(OsmLoadRequest request, IConfiguration conf)
{
StatusAck statusack = new StatusController(conf).PostStatusRecord(); // Issue here is that you need to get the uri
...
}
This way StatusController's correct constructor is hit from where I am able to read the configuration value for connection string
StatusController
protected StatusDAL status_dal;
public StatusController()
{
status_dal = new StatusDAL(WPSGlobalSettings.ToolboxConnString);
}
public StatusController(IConfiguration config)
{
status_dal = new StatusDAL(config.GetConnectionString("toolboxConnectionStrWPS"));
}
I would still appreciate if someone can detail how GlobalSettings Static files pose a problem and to cater which we have DependencyInjection and Singleton pattern in .net core.

.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();
}
}

Convert HttpContext.User before API Method

I currently have a .Net Core API application with a bunch of API get methods. Currently in every single method I am needing to write this line:
[ProducesResponseType(200, Type = typeof(MetadataAttributeModel))]
[ProducesResponseType(400, Type = typeof(ValidationResultModel))]
[ProducesResponseType(500, Type = typeof(ErrorResultModel))]
public ActionResult<MetadataAttributeModel> GetAsync(string name)
{
List<Entities.DocumentAttributeView> attributes = documentAttributeViewRepo.GetByAttributeName(name);
SiteUser currentUser = new SiteUser(db, User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress").Value);
return Unauthorized();
}
Is there a way I can convert the HttpContext.User object to our own SiteUser object before I get to the method? I don't want to have to write this line in ALL of the API methods:
SiteUser currentUser = new SiteUser(db, HttpContext.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress").Value);
TIA,
Alex
The AspNet Mvc mechanism for "Do something for every Action" is Filters.
Filters can run before the method is called, and they can, for instance, set the Http.Context.User.
A filter can be applied to an action, a controller, or (by writing code in Startup) globally.
[SwapUserToAuthorizedDatabaseUser]
public class MyController
{
public IActionResult About() => Ok(User);
}
Which will invoke this filter for every Action on the Controller :
public class SwapUserToAuthorizedDatabaseUserAttribute : Attribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
SiteUser currentUser = new SiteUser(db, User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress").Value);
if (currentUser == null)
{
context.Result= new RedirectToRouteResult("/Identity/Logout");
}
else
{
var claimsIdentity =
new ClaimsIdentity(
new Claim[]
{
new Claim("Id", currentUser.Id),
new Claim("UserName", currentUser.UserName),
new Claim("WhateverElseYourSiteUserHas", currentUser.Something.ToString()),
}
);
context.HttpContext.User = new ClaimsPrincipal(new[]{claimsIdentity});
}
}
public void OnActionExecuted(ActionExecutedContext context){}
}
If overwriting the HttpContext.User isn't what you need, then it's much less code to use HttpContext.Items :
public class SwapUserToAuthorizedDatabaseUserAttribute : Attribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
context.HttpContext.Items["SiteUser"]= new SiteUser(db, User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress").Value);
}
public void OnActionExecuted(ActionExecutedContext context){}
}
Instead of an IActionFilter to run on every Action, you can use an IAuthorizationFilter which has a public void OnAuthorization(AuthorizationFilterContext context) method. This would save repeatedly calling the database, but does mean you must cache your currentUser somewhere, presumably in Session.
The problem is, how do you get access to your database? If you go the route of adding a Global filter by adding it in Startup:
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc(o=>o.Filters.Add(new SwapUserToAuthorizedDatabaseUserAttribute(provide a db instance here)));
}
Then you can give your Filter a constructor and pass in a database. There's also an overload for using the DependencyInjection system.
If you don't use the startup method, you have to do some DIY injection, for instance by having a static method to return a DbContext.
You can move this logic to a service:
public class UserService : IUserService
{
private readonly HttpContext context;
private readonly Db db;
public UserService(IHttpContextAccessor context, Db db)
{
this.context = context.HttpContext;
this.db = db;
}
public SiteUser GetUser()
{
return new SiteUser(db, context.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress").Value);
}
}
Inject it to controllers where it's required:
public MyController(IUserService userService) { ... }
Register it as a Scoped service in ConfigureServices in Startup.cs along with IHttpContextAccessor (should be singleton):
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<UserService>();
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}

Which HttpConfiguration object do I need to access to create a custom HttpParameterBinding?

In this post, Mike Wasson states:
"Besides ParameterBindingAttribute, there is another hook for adding a custom HttpParameterBinding. On the HttpConfiguration object"
But I have three HttpConfiguration objects in my Web API app, namely:
public static void Register(HttpConfiguration config, IWindsorContainer container) <-- in WebApiConfig.cs
private static void MapRoutes(HttpConfiguration config) <-- ""
public static void ConfigureWindsor(HttpConfiguration configuration) <-- in Global.asax.cs
Which of these (config, config, or configuration) should I use (if any)?
UPDATE
I tried this, with a breakpoint on the "if" line:
public static void ConfigureWindsor(HttpConfiguration configuration)
{
_container = new WindsorContainer();
_container.Install(FromAssembly.This());
_container.Kernel.Resolver.AddSubResolver(new CollectionResolver(_container.Kernel, true));
var dependencyResolver = new WindsorDependencyResolver(_container);
configuration.DependencyResolver = dependencyResolver;
if (configuration.Properties.Values.Count > 0) // <-- I put a Casey Jones here
{
object o = configuration.Properties.Values.ElementAt(configuration.Properties.Values.Count - 1);
string s = o.ToString();
}
}
...but I only hit that spot once, on the server starting up, but not when the client sent a request to it...there must be some event that gets fired when a server passes a request where the incoming URL can be examined...no?
Usually you do have only one instance of HttpConfiguration which is the one you get from GlobalConfiguration.Configuration.
Said so, that's how I plugged custom binders
In global.asax
var binderMappings = new Dictionary<Type, Type>
{
{typeof(YourModelType), typeof(YourModelTypeBinder)},
//....
};
config.Services.Add(
typeof(ModelBinderProvider),
new WindsorModelBinderProvider(container, binderMappings));
WindsorModelBinderProvider
public class WindsorModelBinderProvider : ModelBinderProvider
{
private readonly IWindsorContainer _container;
private readonly IDictionary<Type, Type> _binderMappings;
public WindsorModelBinderProvider(IWindsorContainer container, IDictionary<Type, Type> binderMappings)
{
_container = container;
_binderMappings = binderMappings;
}
public override IModelBinder GetBinder(HttpConfiguration configuration, Type modelType)
{
IModelBinder binder = null;
if (_binderMappings.ContainsKey(modelType))
{
binder = _container.Resolve(_binderMappings[modelType]) as IModelBinder;
if (binder == null)
{
throw new ComponentNotFoundException(modelType);
}
}
return binder;
}
}
YourModelTypeBinder
public class YourModelTypeBinder : IModelBinder
{
public YourModelTypeBinder(IYourServiceToLoadYourModelType service)
{
//...
}
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
bindingContext.Model = YourCustomCodeToLoadYourModelTypeUsingTheConstructorDependecies(actionContext.Request);
return true;
}
private YourModelType YourCustomCodeToLoadYourModelTypeUsingTheConstructorDependecies(HttpRequestMessage requestMessage)
{
...
}
}
YourModelTypeBinder will be resolved by the container(see WindsorModelBinderProvider), so you need to registered it first.
After all that plumbing, your controller may have a parameter, among others, as following
[ModelBinder]YourModelType user