I'm currently developing a system where dependent on what domain a request comes from different website settings need to be loaded (eg. default language id) which is then used in the rest of the application. These settings are stored in a class WebsiteSettings which are injected into the rest of the application when needed.
The first option I tried was registering a service to access the HttpContext by doing this in my ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
//Register other services
services.TryAddScoped<IHttpContextAccessor, HttpContextAccessor>();
services.TryAddScoped<WebsiteSettingsFiller>();
services.TryAddScoped(typeof(WebsiteSettings), s =>
{
var settingFiller = s.GetService<WebsiteSettingsFiller>();
return settingFiller.Create();
});
}
Next, in my WebsiteSettingsFiller service, I inject the IHttpContextAccessor and some other services that I need to load the site settings.
public class WebsiteSettingsFiller
{
protected readonly IRepository Database;
private readonly IHttpContextAccessor _accessor;
private readonly StartupSitePropertyService _sitePropertyService;
private IQueryable<Site> AllSites => Database.All<Site>();
private IQueryable<SiteLanguage> AllSiteLanguages => Database.All<SiteLanguage>();
public WebsiteSettingsFiller(IRepository db, StartupSitePropertyService siteProperties, IHttpContextAccessor accessor)
{
Database = db;
_accessor = accessor;
_sitePropertyService = siteProperties;
}
public WebsiteSettings Create()
{
var domain = _accessor.HttpContext.Request.Host.Host; //null exception on this line
#if DEBUG
domain = "www.somewebsite.com";
#endif
var config = GetConfigByDomain(domain);
return config;
}
private WebsiteSettings GetConfigByDomain(string domain)
{
var site = AllSites.OrderByDescending(s => s.Created).FirstOrDefault(t => t.Host == domain);
if (site == null) return null;
var languages = AllSiteLanguages.Where(sl => sl.SiteId == site.Id).ToList();
//get more variables
return new WebsiteSettings
{
/* Set variables */
}
}
}
Example injection of WebsiteSettings:
public class RouteService : BaseService
{
private IDictionary<int, string> _routeLanguages = null;
private readonly WebsiteRedisService _websiteRedisService;
public RouteService(IRepository db,
WebsiteSettings settings,
WebsiteRedisService websiteRedisService)
: base(db, settings)
{
_websiteRedisService = websiteRedisService;
}
public async Task<IDictionary<int, string>> RouteLanguagesAsync()
{
return _routeLanguages ??
(_routeLanguages = await _websiteRedisService.SiteLanguagesToAsync(Settings.SiteId));
}
}
Sadly, no matter what I try the HttpContext reference is always null. Does anyone have any idea what I can try to resolve this? Or am I just approaching this problem the wrong way? Any suggestions are greatly appreciated!
Related
I'm writing a test with xunit on .NET Core 3.0 and I have a problem with the in-memory database. I need a separate database for each test but now I create a single database that causes problems, but I have no idea how to create a new database for each test.
public class AccountAdminTest : IClassFixture<CustomWebApplicationFactory<Startup>>
{
private readonly HttpClient _client;
private IServiceScopeFactory scopeFactory;
private readonly CustomWebApplicationFactory<Startup> _factory;
private ApplicationDbContext _context;
public AccountAdminTest(CustomWebApplicationFactory<Startup> factory)
{
_factory = factory;
_client = _factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = true,
BaseAddress = new Uri("https://localhost:44444")
});
scopeFactory = _factory.Services.GetService<IServiceScopeFactory>();
var scope = scopeFactory.CreateScope();
_context = scope.ServiceProvider.GetService<ApplicationDbContext>();
}
}
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
var descriptor = services.SingleOrDefault(
d => d.ServiceType ==
typeof(DbContextOptions<ApplicationDbContext>));
if (descriptor != null)
{
services.Remove(descriptor);
}
services.AddDbContext<ApplicationDbContext>((options, context) =>
{
context.UseInMemoryDatabase("IdentityDatabase");
});
});
}
}
Now it's look like this but still dosen't work. When i change lifetime on AddDbContext it doesn't change anything.
public class AccountAdminTest : IDisposable
{
public AccountAdminTest(ITestOutputHelper output)
{
this.output = output;
_factory = new CustomWebApplicationFactory<Startup>();
_client = _factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = true,
BaseAddress = new Uri("https://localhost:44444")
});
scopeFactory = _factory.Services.GetService<IServiceScopeFactory>();
_scope = scopeFactory.CreateScope();
_context = _scope.ServiceProvider.GetService<ApplicationDbContext>();
var _user = User.getAppAdmin();
_context.Add(_user);
_context.SaveChanges(); //Here i got error on secound test. It says "An item with the same key has already been added"
}
public void Dispose()
{
_scope.Dispose();
_factory.Dispose();
_context.Dispose();
_client.Dispose();
}
I can't get token when use Guid as db name. It says that username/password is not valid. I use IdentityServer for authentication
public async Task<string> GetAccessToken(string userName, string password, string clientId, string scope)
{
var disco = await _client.GetDiscoveryDocumentAsync("https://localhost:44444");
if (!String.IsNullOrEmpty(disco.Error))
{
throw new Exception(disco.Error);
}
var response = await _client.RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = clientId,
Scope = scope,
UserName = userName,
Password = password,
});
return response.AccessToken;
}
All you need to change is this code here:
services.AddDbContext<ApplicationDbContext>((options, context) =>
{
context.UseInMemoryDatabase("IdentityDatabase");
});
Instead of the constant value "IdentityDatabase", use something like Guid.NewGuid().ToString():
context.UseInMemoryDatabase(Guid.NewGuid().ToString());
Then, every time the context is fetched, it will be using a new in-memory database.
Edit: You must specify an unique name for every test run to avoid sharing the same InMemory database as slightly mentioned here and already answered here and here.
Nonetheless the suggestion below to switch to "Constructor and Dispose" still applies.
IClassFixture is not the right tool to use, the documentation says:
When to use: when you want to create a single test context and share it among all the tests in the class, and have it cleaned up after all the tests in the class have finished.
In your case you have to use "Constructor and Dispose", the documentation says:
When to use: when you want a clean test context for every test (sharing the setup and cleanup code, without sharing the object instance).
So your test will be:
public class AccountAdminTest
{
private readonly HttpClient _client;
private IServiceScopeFactory scopeFactory;
private readonly CustomWebApplicationFactory<Startup> _factory;
private ApplicationDbContext _context;
public AccountAdminTest(CustomWebApplicationFactory<Startup> factory)
{
//
_factory = new CustomWebApplicationFactory<Startup>();
_client = _factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = true,
BaseAddress = new Uri("https://localhost:44444")
});
scopeFactory = _factory.Services.GetService<IServiceScopeFactory>();
_scope = scopeFactory.CreateScope();
_context = scope.ServiceProvider.GetService<ApplicationDbContext>();
}
//Tests...
public void Dispose() {
_scope.Dispose();
_factory.Dispose();
//Dispose and cleanup anything else...
}
}
Alternatively you can specify a lifetime of ServiceLifetime.Transient for your DbContext using this overload of .AddDbContext
Is there a way to have a global variable that is not session dependent?
I want to have a list of all the SignalR connections and the user ID form the db.
So far I have something like this :
public class SignalRUsersService : ISignalRUsersService
{
private int userId;
private readonly IHttpContextAccessor _accessor;
public List<ConnectedSignalR> SignalRUsers;
public SignalRUsersService( IHttpContextAccessor accessor)
{
_accessor = accessor;
try
{
userId = Convert.ToInt32(_accessor.HttpContext.Session.GetString("uID"));
}
catch
{
userId = 0;
}
SignalRUsers = new List<ConnectedSignalR>();
}
public void AddSignalRUser(string ConnID)
{
SignalRUsers.Add(new ConnectedSignalR()
{
ConnID = ConnID,
UserID = userId
});
}
public void RemoveSignalRUser(string ConnID)
{
var usr = SignalRUsers.Where(a => a.ConnID == ConnID).FirstOrDefault();
if (usr != null)
{
SignalRUsers.Remove(usr);
}
}
public List<ConnectedSignalR> GetSignalRUsers()
{
return SignalRUsers;
}
}
The problem is every time I use the interface it seems it's re-initializing my SignalRUsers list. And even if I push the data in the list I find it null when I need it.
Is SignalRUsers session dependent? Or is it just a matter of not using the interface in a right way?
Any help is much appreciated.
SignalR hubs are transient, which means a new instance is created when a method call is received from a client (see: https://learn.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/hubs-api-guide-server).
You are probably injecting this service into a hub class, which means you can use dependency injection configuration to control the lifetime of your service class.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ISignalRUsersService, SignalRUsersService>();
}
I have problem when I'm trying to get httpcontext from IHttpContextAccessor field is always null in class.
There is my startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDistributedMemoryCache();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<IUserContextServices, UserContextService>();
services.AddSession(options =>
{
options.Cookie.Name = ".AdventureWorks.Session";
options.IdleTimeout = TimeSpan.FromSeconds(10);
});
}
This is My UserContext Class which implements IUserContext interface
public class UserContextService : IUserContextServices
{
private readonly IHttpContextAccessor contextAccessor;
PQADBContext _context = new PQADBContext();
public UserContextService(IHttpContextAccessor accessor)
{
contextAccessor = accessor;
}
public UserContextService()
{
}
public HttpContext Context
{
get
{
return contextAccessor.HttpContext;
}
}
public int UserID()
{
return Context.Session.GetID("UserID").ConvertToInt();
}
public bool isLogin()
{
return Context.Session.GetBoolean("isLogin").ConvertToBool();
}
public UserAccount CreateSession(LoginViewModel logindata, bool EncryptPwd = true)
{
string error;
error = "";
try
{
string EncPwd = EncryptPwd ? EncryptDecryptHelper.Encrypt(logindata.Password) : logindata.Password;
var UserDetail =_context.UserAccount.Where(e => e.P_No == logindata.PNo && e.Password == EncPwd).SingleOrDefault();
if (UserDetail != null)
{
//HttpContext.Session.SetInt32()
// ///put all the properties in session variables
Context.Session.SetBoolean("isLogin", true);
Context.Session.SetID("UserID",UserDetail.AccountId);
Context.Session.SetID("P_No",Convert.ToInt32(UserDetail.P_No));
Context.Session.SetBoolean("isActive", true);
Context.Session.SetBoolean("Email", true);
Context.Session.SetID("RoleId", 1);
Context.Session.SetString("userName", "admin");
}
httpContext available in above class and also set the Session values but when i try to access httpcontext in this class it gives me null object reference
public class UserService
{
private readonly IHttpContextAccessor contextAccessor;
public IUserContextServices _userContext = new UserContextService();
public UserService()
{
}
public bool CreateEmployee(AppEmployees appemployee, int RoleId, bool isEmailSend, out string error)
{
appemployee.CreatedBy = _userContext.UserID(); //this line shows null reference exception
appemployee.CreatedDate = DateTime.Now;
}
You are newing up the UserContextService using the parameterless constructor
public IUserContextServices _userContext = new UserContextService();
instead of relying on Dependency Injection.
You need to configure your UserService to be used with your DI container - via constructor injection that would be
public class UserService
{
private readonly IUserServiceContext _userServiceContext;
public UserService(IUserServiceContext userServiceContext)
{
_userServiceContext = userServiceContext;
}
}
You will also need to amend your Startup.cs to register the UserService and you may want it to implement an interface too
Why do you use default constructor in your UserService?
You use next code:
public IUserContextServices _userContext = new UserContextService();
Of course here you have null for IHttpContextAccessor.
You need to use DI in your UserService.
Example:
private readonly IUserContextService _userContextService;
public UserService(IUserContextService userContextService)
{
_userContextService = userContextService;
}
There is good post about DI in .NET Core.
If you have set UserId directly as
public CurrentUserService(IHttpContextAccessor httpContextAccessor)
{
UserId = httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier);
}
This way you might get null UserId most of the time instead create httpContextAccessor field first like
public CurrentUserService(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public string UserId { get { return httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier); } }
Then get UserId, this way the problem of getting null UserId will be resolved.
I have common DI usage in asp core application.
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped(typeof(IUnitOfWork), typeof(UnitOfWork));//DAL
services.AddScoped(typeof(IUpService), typeof(UpService));//BLL
...
}
Controller get UpService by DI:
public BaseController(IUpService upService)
{
_upService = upService;
}
And in its turn BLL (upservice) get unitofwork instance:
public UpService(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
In base controller i have property CurrentUser
public User CurrentUser
{
get
{
User user = null;
var tokenCookie = HttpContext.Request.Headers.FirstOrDefault(c => c.Key == "token");
if (!string.IsNullOrEmpty(tokenCookie.Value) && tokenCookie.Value != "undefined")
{
user = _upService.GetUserById(new Guid(tokenCookie.Value));
}
return user;
}
}
What i need is to pass CurrentUser to BL layer (to UpService). How to realise it by DI in asp core?
UPD:
Here the part of UpService, where i need current user.
public class UpService : IUpService
{
private IUnitOfWork _unitOfWork;
public User CurrentUser { get; set; }
public UpService(IUnitOfWork unitOfWork, IUserContext userContext)
{
_unitOfWork = unitOfWork;
//CurrentUser = userContext.CurrentUser;
}
public void Update(Document doc)
{
//here complex BL and then...
Document local;
var entity = _unitOfWork.DocumentsRepository.GetByID(doc.Id);
if (entity != null && HasChanges(local, entity))
{
entity.ChangedById = CurrentUser.Id;
_unitOfWork.Save();
}
}
public User GetUserById(Guid id)
{
return _unitOfWork.UserRepository.GetByID(id);
}
...
You should extract the CurrentUser into an abstraction, let's call it IUserContext. This abstraction can be implemented in your core layer. This way BL and DAL can access it. As part of your web application you create an implementation that adapts to ASP.NET Core and basically contains the logic that your CurrentUser property has. For instance:
public interface IUserContext
{
User CurrentUser { get; }
}
The adapter can look as follows:
public class AspNetUserContextAdapter : IUserContext
{
private readonly IHttpContextAccessor _accessor;
private readonly IUpService _upService;
public AspNetUserContextAdapter(IHttpContextAccessor accessor, IUpService _upService) {
_accessor = accessor;
_upService = upService;
}
public User CurrentUser
{
get
{
var context = _accessor.HttpContext;
var tokenCookie = context.Request.Headers.FirstOrDefault(c => c.Key == "token");
return !string.IsNullOrEmpty(tokenCookie.Value) && tokenCookie.Value != "undefined"
? _upService.GetUserById(new Guid(tokenCookie.Value))
: null;
}
}
}
This adapter can be registered as usual:
services.AddTransient<IUserContext, AspNetUserContextAdapter>();
On top of that, you might need to register ASP.NET Core's IHttpContextAccessor, since (in old versions of ASP.NET Core) it is not registered by default:
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
I have an http client wrapper that I'm injecting into all my controllers. If a user is authenticated, the injected wrapper should have some properties set with the authenticated user information.
I currently have this:
[System.Web.Mvc.Authorize]
public class ProfileController : Controller
{
private readonly IMyClient client;
public ProfileController()
{
string apiKey = ConfigurationManager.AppSettings["ApiKey"];
client = new MyClient(apiKey);
SetupClient();
}
private void SetupClient()
{
if (Thread.CurrentPrincipal.Identity.IsAuthenticated)
{
var identity = Thread.CurrentPrincipal.Identity as ClaimsIdentity;
var tokenClaim = identity.Claims.First(c => c.Type == ClaimTypes.Sid);
client.AddCredentials(tokenClaim.Value);
}
}
}
I would like to offload SetupClient to somewhere that will allow me to do dependency injection of IMyClient.
Essentially I want to implement this solution:
ProfileController.cs
[System.Web.Mvc.Authorize]
public class ProfileController : Controller
{
private readonly IMyClient client;
public ProfileController(IMyClient client)
{
this.client = client;
}
}
Startup.cs
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
IoCConfig.RegisterIoC(app);
ConfigureAuth(app);
}
}
IoCConfig.cs
public class IoCConfig
{
public static void RegisterIoC(IAppBuilder app)
{
var container = new Container();
container.Register<IMyClient>(
() =>
{
var apiKey = ConfigurationManager.AppSettings["APIKey"];
var myClient= new MyClient(apiKey);
// This will not work as this code is executed on app start
// The identity will not be of the user making the web request
var identity = Thread.CurrentPrincipal.Identity as ClaimsIdentity;
var tokenClaim = identity.Claims.First(c => c.Type == ClaimTypes.Sid);
client.AddCredentials(tokenClaim.Value);
return myClient;
});
// Register the dependency resolver.
DependencyResolver.SetResolver(
new SimpleInjectorDependencyResolver(container));
}
}
I'm stuck in the code for IoCConfig to extract information of the authenticated user (if the user is authenticated) and setup the client for injection. Any help here?
My IoC framework is SimpleInjector but I'd like an agnostic solution.
This is how I would do it
public class ProfileController : Controller
{
private readonly MyClient _client;
public ProfileController()
{
var clientInfo = Resolve<IClientInfo>(); // call out to your service locator
_client = clientInfo.GetClient();
}
}
public interface IClientInfo
{
MyClient GetClient();
}
public interface IAuth
{
System.Security.Claim GetSidClaim();
}
public class ClientInfo : IClientInfo
{
private readonly IAuth _auth;
public ClientInfo(IAuth auth)
{
_auth = auth;
}
public MyClient GetClient()
{
var apiKey = ApiKey;
var client = new MyClient(apiKey);
var claim = _auth.GetSidClaim();
client.AddCredentials(claim.Value);
return client;
}
protected virtual string ApiKey
{
get { return ConfigurationManager.AppSettings["APIKey"]; }
}
}
I'd take a look at NInject and the MVC extensions...
http://ninject.codeplex.com/wikipage?title=Dependency%20Injection%20With%20Ninject
http://www.codeproject.com/Articles/412383/Dependency-Injection-in-asp-net-mvc-and-webapi-us
When setup correctly it's just a matter of creating a binding for IMyClient NInject will implicitly inject it for you. There are lots of other injection frameworks out there, NInject is just the one I've chosen. Each of them will give you a substantial benefit over anything you could cook up on your own. e.g. with NInject you can create bindings that inject a singleton across your app or a binding that injects a singleton for each request.
In NInject you could create a binding something like
Bind<IMyClient>().ToMethod(x => SetupClient(x)).InRequestScope();
private IMyClient SetupClient(IContext context)
{
string apiKey = ConfigurationManager.AppSettings["ApiKey"];
var client = new MyClient(apiKey);
if (Thread.CurrentPrincipal.Identity.IsAuthenticated)
{
var identity = Thread.CurrentPrincipal.Identity as ClaimsIdentity;
var tokenClaim = identity.Claims.First(c => c.Type == ClaimTypes.Sid);
client.AddCredentials(tokenClaim.Value);
}
return client;
}
InRequestScope says that NInject should create a single instance for each request...
https://github.com/ninject/Ninject.Web.Common/wiki/InRequestScope
I think the equivalent in SimpleInjector is...
https://simpleinjector.codeplex.com/wikipage?title=ObjectLifestyleManagement#PerWebRequest
Is the answer as simple as changing your code to...
public static void RegisterIoC(IAppBuilder app)
{
var container = new Container();
container.RegisterPerWebRequest<IMyClient>(
() =>
{
...
I solved this by a version of what CRice posted by using a factory delegate:
ProfileController.cs
[System.Web.Mvc.Authorize]
public class ProfileController : Controller
{
private readonly IMyClient client;
public ProfileController(Func<IMyClient> clientFactory)
{
client = clientFactory.Invoke();
}
}
IoCConfig.cs
public class IoCConfig
{
public static void RegisterIoC(IAppBuilder app)
{
// Create the container as usual.
Container container = new Container();
// Registering as a factory delegate because we need the user authentication information if any.
container.RegisterSingle<Func<IMyClient>>(() =>
{
string apiKey = ConfigurationManager.AppSettings["ApiKey"];
var myClient = new MyClient(apiKey);
if (Thread.CurrentPrincipal.Identity.IsAuthenticated)
{
var identity = Thread.CurrentPrincipal.Identity as ClaimsIdentity;
var tokenClaim = identity.Claims.First(c => c.Type == ClaimTypes.Sid);
myClient.AddCredentials(tokenClaim.Value);
}
return myClient;
});
// This is an extension method from the integration package.
container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
// This is an extension method from the integration package as well.
container.RegisterMvcIntegratedFilterProvider();
container.Verify();
DependencyResolver.SetResolver(
new SimpleInjectorDependencyResolver(container));
}
}