Why is AppTenant null only while seeding data? - c#

I'm currently fiddeling around with Ben Fosters Saaskit.
I have extended the ApplicationUser with a AppTenantId property and created a custom UserStore, which uses the AppTenant to identify the user:
public class TenantEnabledUserStore : IUserStore<ApplicationUser>, IUserLoginStore<ApplicationUser>,
IUserPasswordStore<ApplicationUser>, IUserSecurityStampStore<ApplicationUser>
{
private bool _disposed;
private AppTenant _tenant;
private readonly ApplicationDbContext _context;
public TenantEnabledUserStore(ApplicationDbContext context, AppTenant tenant)
{
_context = context;
_tenant = tenant;
}
/*... implementation omitted for brevity*/
}
If a user registers or logs in, this works fine. The AppTenant is set correctly. The problem occurs, when SeedData.Initialize(app.ApplicationServices); is called at the end of my Statup.Configure() method:
public static class SeedData
{
public async static void Initialize(IServiceProvider provider)
{
using (var context = new ApplicationDbContext(
provider.GetRequiredService<DbContextOptions<ApplicationDbContext>>()))
{
var admin = new ApplicationUser
{
AppTenantId = 1,
Email = "foo#bar.com",
UserName = "Administrator",
EmailConfirmed = true
};
if(!context.Users.Any(u => u.Email == admin.Email))
{
var userManager = provider.GetRequiredService<UserManager<ApplicationUser>>();
await userManager.CreateAsync(admin, "Penis123#");
}
context.SaveChanges();
}
}
}
The usermanager is is calling the custom userstore, but now AppTenant is null.
When the code finally reaches
public Task<ApplicationUser> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
{
return _context.Users.FirstOrDefaultAsync(u => u.NormalizedUserName == normalizedUserName && u.AppTenantId == _tenant.AppTenantId, cancellationToken);
}
I am facing a System.InvalidoperationException, because AppTenant is passed as null in the constructor of above mentioned userstore.
What am I doing wrong? Am I seeding the wrong way or do I forget something fundamental here?
Update:
For now I have taken the crowbar-approach, avoided the usermanager and created my own instance of a userstore with a mock AppTenant:
if (!context.Users.Any(u => u.Email == admin.Email))
{
var userStore = new TenantEnabledUserStore(context, new AppTenant
{
AppTenantId = 1
});
await userStore.SetPasswordHashAsync(admin, new PasswordHasher<ApplicationUser>().HashPassword(admin, "VeryStrongPassword123#"), default(CancellationToken));
await userStore.SetSecurityStampAsync(admin, Guid.NewGuid().ToString("D"), default(CancellationToken));
await userStore.CreateAsync(admin, default(CancellationToken));
}
Nontheless, I'm still interested in a more clean approach, that doesn't feel that hacky.

When using Saaskit, you configure an AppTenantResolver that determines how to set the TenantContext<T> based on the provided HttpContext. It then stores the retrieved TenantContext<T> in the Items property of the HttpContext. This is a Scope level cache, so the tenant is only stored there for the duration of the request.
When you inject an AppTenant into a class it attempts to resolve it from HttpContext.Items. If no tenant is found, then it injects null instead.
When you call SeedData.Initialize(app.ApplicationServices), you are not in the context of a request, and so the AppTenantResolver middleware has not run, and will not have resolved an AppTenant.
Unfortunately, not having the full details of your code, it's hard to say exactly how to fix your issue. You would need to make sure you create a new Scope in your SeedData method and resolve an AppTenant within that scope so that subsequent calls to the IoC will allow it to be inserted.

Related

I am facing issue thread related issue in asp.net core identity in asp.net core?

Error Message :A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext
public async Task<UserSearchDto> GetSingle(string userId, string baseUrl)
{
var user =await _userManager.FindByIdAsync(userId);
if (user != null)
{
UserSearchDto userSearches = new UserSearchDto
{
data
};
return userSearches;
}
}
In above service FindByIdAsync throwing this exeption
while i am debugging step by step then i am not facing this error
my setup in startup file as below
services.AddTransient<IAuthService, AuthService>();
Even i changed above service method but its not working
why it requires more time to perform or there is any another issue?
Edit
these manager are passed in service
private readonly UserManager<ApplicationUser> _userManager;
private readonly RoleManager<ApplicationRole> _roleManager;
this is ctor
public AuthService(UserManager<ApplicationUser> _userManager,
RoleManager<ApplicationRole> _roleManager,
IConfiguration configuration) : base(configuration)
{
this._userManager = _userManager;
this._roleManager = _roleManager;
}
User manage and role manager are used from Microsoft.AspNetCore.Identity
services.AddDbContext<Db>(options =>
{
options.UseSqlServer(mySqlConnectionStr);
}
);
The DbContext has a scoped service lifetime, coupled to an asp.net request. Thus services using the context should preferably also have a scoped service lifetime.
I can recommend you such approach (TModel can be yours UserSearchDto):
// Or your db context directly in class but this is better
private readonly IServiceScopeFactory _factory;
public async Task<TModel> FindByIdAsync(ulong id)
{
using var scope = _factory.CreateScope();
// your context gets here
await using var userManager = scope.ServiceProvider.GetRequiredService<UserManagerContext>();
// this is important
var entities = userManager.Set<TModel>().AsNoTracking();
// data should be filled after FindByIdAsync(ulong id), not in this method
return await entities.FirstOrDefaultAsync(t => t.Id == id);
}

How to get an IHttpContextAccessor instance (or equivalent) in a background task?

In my ASP.Net Core 3.1 webapi, I'm registering the IHttpContextAccessor as a singleton and injecting it into all my controllers. I have an interface that also gets injected into all my controllers and my services (which in turn connect to the db). The implementation is:
public class PrincipalProvider : IPrincipalProvider
{
private readonly UserPrincipal principal;
public PrincipalProvider(IHttpContextAccessor accessor)
{
accessor.HttpContext.Items.TryGetValue("principal", out object principal);
this.principal = principal as UserPrincipal;
}
public UserPrincipal GetPrincipal()
{
return principal;
}
}
The ctor of a service looks like:
public MyService(
IPrincipalProvider provider,
ILogger<MyService> logger,
IUnitOfWork unitOfWork) : base(provider, logger, unitOfWork)
{ }
All the above works as expected as long as I'm within the request context.
I have a controller action that starts a background task using the new IHostedService implementation with a background queue, and it gets started like this:
backgroundQueue.QueueBackgroundWorkItem(async (scope, hubContext, ct) =>
{
await hubContext.Clients.Client(provider.GetPrincipal().ConnectionId).Notify();
var myService = scope.Resolve<IMyService>();
}
where scope is ILifetimeScope and hubConext is IHubContext<MyHub, IMyHub>. The provider variable is the IPrincipalProvider that was injected into the controller ctor.
The problem is that when I try to resolve IMyService within the task, it creates an instance of IPrincipalProvider and that in turn requires IHttpContextAccessor which doesn't exist anymore.
What is the solution in this case? Do I need to have a second ctor on the service with a different IPrincipalProvider which gets the context from somewhere else? And if that's the case, from where?
The nicest solution would be to have 2 implementations of IPrincipalProvider, the one that use the httpContextAccessor and another one that use something else. Unfortunately it is not always easy to have the other implementation.
When you create the child lifetimeScope you can add registration to this child lifetime scope. You can register a StaticPrincipalProvider here.
private async Task BackgroundProcessing(...) {
...
try {
using(ILifetimeScope queueScope = this._rootScope.BeginLifetimeScope(builder => {
builder.RegisterInstance(new StaticPrincipalProvider(principal))
.As<IPrincipalProvider>();
})){
await workItem(queueScope, stoppingToken);
}
}
...
}
All you have to do now is to find a way to get the corresponding principal when you dequeue the task. To do this you can change the implementation of BackgroundTaskQueue to use a ConcurrentQueue<WorkItem> instead of ConcurrentQueue<Func<ILifetimeScope, CancellationToken, Task>> where WorkItem is
public class WorkItem {
public Func<ILifetimeScope, CancellationToken, Task> Work { get; private set; }
public IPrincipal Principal { get; private set; }
// or
public Action<ContainerBuilder> builderAccessor { get; private set; }
}
and because BackgroundTaskQueue is instanciated with a request scope you will have access to the current principal.

Cache expensive object for thread-scope using DI, without blocking (async)

I've got an expensive "current user" obejct that I want to cache for the duration of the request. To do this I'm using the built-in DI in asp.net core, to create a ICurrentUser object when requested. It looks like this:
public class CurrentUserCache : ICurrentUser
{
public CurrentUserCache(IHttpContextAccessor httpContextAccessor, UserManager userManager)
{
var httpContextAccessor1 = httpContextAccessor;
_user = new Lazy<User>(() => httpContextAccessor1.HttpContext.User != null ? userManager.GetUserAsync(httpContextAccessor1.HttpContext.User).Result : null);
}
private Lazy<User> _user;
public User User {
get => _user.Value;
set {}
}
}
It's using a Lazy object to defer the retrieval of the object, since some controller actions might not need to make use of it.
My problem is - the code inside the lazy to get the user, is blocking (.Result). I don't want to do that, since it's quite expensive.
I don't know how to make this code async. I could possibly create a Lazy<Task<user>> to get the user, but then I can't await that in my user property, because it's a property and properties can't be async.
So - how can I turn this code into something that works well for async?
Thanks!
Turn the property into an awaitable function
public class CurrentUserCache : ICurrentUser {
public CurrentUserCache(IHttpContextAccessor httpContextAccessor, UserManager userManager) {
_user = new Lazy<Task<User>>(() =>
userManager.GetUserAsync(httpContextAccessor.HttpContext.User)
);
}
private Lazy<Task<User>> _user;
public Task<User> GetUserAsync() {
return _user.Value;
}
}

Dispose ApplicationDbContext in ASP.NET Core

I have a web application and host it in Azure but the problem is that the SQL pool gets full fast regardless of tier.
I was thinking that maybe I need to dispose ApplicationDbContext, is this required? would this help?
The following is a part of my class with the method Index utilizing most of SQL time:
private readonly ApplicationDbContext _context;
private readonly IEmailSender _emailSender;
public MessagesController(ApplicationDbContext context, IEmailSender emailSender)
{
_context = context;
_emailSender = emailSender;
}
// GET: Message
[Authorize]
public async Task<IActionResult> Index()
{
var user = _context.Users.Where(u => u.Email.Equals(User.Identity.Name)).Select(u =>
new { u.Name, u.Subdomain, u.PhotoURL }).FirstOrDefault();
ViewData["Name"] = user.Name;
ViewData["Subdomain"] = user.Subdomain;
ViewData["PhotoURL"] = (user.PhotoURL == null) ? "../../img/avatar.png" : user.PhotoURL;
List<Message> messages = await _context.Messages.Where(m => m.UserName.Equals(User.Identity.Name))
.Select(m => new Message { ID = m.ID, DateTime = m.DateTime, Text = m.Text }).ToListAsync();
return View(messages);
}
Should I call _context.dispose() although I'm using the same context in other ActionResult methods?
No, you don't have to call _context.dispose() - the IoC container will release resources based on the lifetime setting which was used while registering them.
What is more, you wrote that "I'm using the same context in other ActionResult methods?". That's not true - well at least it shouldn't be true. The context should be created per request. So that while calling each of the controller actions your are generating new request and container is creating new context to be injected (each time when new request is done the controller is created once again).
Here is how you should register your context:
services.AddDbContext<SchoolContext>(options => options.UseSqlServer(CONNECTION_STRING));
By this you are using default lifetime for DbContext - scoped.

Can't find OwinContext in NancyContext

I have a self hosted Owin application that uses Nancy. In one of the NancyModules I need to get an instance of IOwinContext.
This question touches on the subject, but there's no solution in it: Get current owin context in self host mode
It says that for Nancy, you have to use NancyContext to get to the Items dictionary and look for the value corresponding to the key "OWIN_REQUEST_ENVIRONMENT".
I do have access to the NancyContext and I can see the Items dictionary and that it contains a key called "OWIN_REQUEST_ENVIRONMENT". (I could also call the NancyContext.GetOwinEnvironment() extension, which gives the same result
However, when I get that key it doesn't contain an actual IOwinContext.
It contains a lot of keys with information about Owin (some of the keys are owin.RequestPath, owin.RequestMethod, owin.CallCancelled, and more), but not an actual context object. And it is only really a dictionary with various keys, so I can't cast it to an IOwinContext either.
How can I get from a NancyContext to an IOwinContext object?
public class MyStartup
{
public void Start()
{
var options = new StartOptions()
options.Urls.Add(new Uri("http://*:8084"));
options.AppStartup(this.GetType().AssemblyQualifiedName;
var host = WebApp.Start(options, Configuration);
}
public void Configuration(IAppBuilder app)
{
app.UseNancy();
}
}
public class MyModule : NancyModule
{
Get["/", true] = async(x, ct) =>
{
var owinEnvironment = Context.GetOwinEnvironment();
// Now what?
}
}
var owinContext = new OwinContext(Context.GetOwinEnvironment());
example:
public class SecurityApi : NancyModule
{
public SecurityApi()
{
Post["api/admin/register", true] = async (_, ct) =>
{
var body = this.Bind<RegisterUserBody>();
var owinContext = new OwinContext(Context.GetOwinEnvironment());
var userManager = owinContext.GetUserManager<ApplicationUserManager>();
var user = new User {Id = Guid.NewGuid().ToString(), UserName = body.UserName, Email = body.Email};
var result = await userManager.CreateAsync(user, body.Password);
if (!result.Succeeded)
{
return this.BadRequest(string.Join(Environment.NewLine, result.Errors));
}
return HttpStatusCode.OK;
};
}
}
Actually, question that you mentioned has some tips that you probably missed.
For Nancy, you have to use NancyContext to get to the Items dictionary
and look for the value corresponding to the key
"OWIN_REQUEST_ENVIRONMENT". For SignalR, Environment property of
IRequest gives you access to OWIN environment. Once you have the OWIN
environment, you can create a new OwinContext using the environment.
So, once you called var owinEnvironment = Context.GetOwinEnvironment() and got the dictionary then you can create OwinContext (which is just wrapper for these dictionary values)
It has a constructor OwinContext(IDictionary<String, Object>) which, i guess, is what you need.
Also, you can get OwinContext from HttpContext:
// get owin context
var owinContext = HttpContext.Current.GetOwinContext();
// get user manager
var userManager = owinContext.GetUserManager<YourUserManager>();
I ended up solving this by creating new Owin middleware. In the middleware you have access to the current Owin context, which gives you access to the Owin environment.
When you have access to the Owin environment it's simply a case of adding the Owin context to the environment. When the context is in the environment you can retrieve it in the NancyModule.
After retrieving it like this I also had access to the GetUserManager() method on the context so that I could get my AspNetIdentity manager (as I mentioned in a comment to another answer). Just remember that the middleware must be added before Nancy to the Owin pipeline.
Startup
public class Startup
{
public void Start()
{
var options = new StartOptions()
options.Urls.Add(new Uri("http://*:8084"));
options.AppStartup(this.GetType().AssemblyQualifiedName;
var host = WebApp.Start(options, Configuration);
}
public void Configuration(IAppBuilder app)
{
app.Use(typeof(OwinContextMiddleware));
app.UseNancy();
}
}
Middleware
public class OwinContextMiddleware : OwinMiddleware
{
public OwinContextMiddleware(OwinMiddleware next)
: base(next)
{
}
public async override Task Invoke(IOwinContext context)
{
context.Environment.Add("Context", context);
await Next.Invoke(context);
}
}
NancyModule
public class MyModule : NancyModule
{
public MyModule()
{
Get["/", true] = async(x, ct) =>
{
IDictionary<string, object> environment = Context.GetOwinEnvironment();
IOwinContext context = (IOwinContext)environment["Context"]; // The same "Context" as added in the Middleware
}
}
Caveat
The middleware listed above is untested as the middleware I have is more complex and I haven't had the time to create a working example. I found a simple overview on how to create Owin middleware on this page.

Categories

Resources