Base solution for your next web application

Activities of "4Matrix"

Hi @ismcagdas, I don't see any error messages when creating the new database, it just creates the database outside of the Elastic Pool I have specified. It also creates it on a very expensive Tier!

Hi ismcagdas,

 public override string GetNameOrConnectionString(ConnectionStringResolveArgs args)
        {
         
            if (args["DbContextConcreteType"] as Type == typeof(TenantDbContext))
            {
                var tenantCacheItem = _tenantCache.Get(_currentUnitOfWorkProvider.Current.GetTenantId().Value);
                if (tenantCacheItem.ConnectionString.IsNullOrEmpty())
                {
                    return _appConfiguration[$"ConnectionStrings:{CloudConsts.TenantConnectionStringName}"];
				}
            
                return tenantCacheItem.ConnectionString;
                
            }

            return base.GetNameOrConnectionString(args);
        }

This is my Custom Connection String Resolver, it works perfectly but I can see this method is called so much I just want to make sure I cant improve it in any way as it currently does quite a lot! MultiTenancySide seemed like it would help but it is always either Host or Null, never Tenant, even though I have decorated my second db context with [MultiTenancySide(MultiTenancySides.Tenant)]. Its also Null most of the time for the main db context too so i cant just check for NULL. I am concerned that when we have 100s of people using this system, performance of this code block is going to be terrible

For now we have gone with creating the public site in the same angular app and just putting the routing in the root-routing module e.g.

    {
        path: 'about',
        component: LandingComponent,
        loadChildren: () => import('./landing/about/about.module').then((m) => m.AboutModule),
    },

This is working for now but it is not ideal. I would like to explore the option of having a second angular app for the public site, so that the main angular app included with AspNetZero would be served on my.company.com and a new "Landing" site would be served on www.company.com, just like the MVC Public Project Included with AspNetZero.

I started off by cloning the current angular folder to angular-Landing and adding the landing components there ( e.g. About, Contact etc ) but it became apparent that this was going to create some major issues as I didn't want to use the authentication from this project. I want the landing site to share a session with the main angular app. I sort of got this working because I was hosting them on the same domain and could wildcard the CORS but when it needed to login etc, it was trying to use the pages from the folder and not the main one. Another worry is that if we ever updated the AspNetZero template to a new version, this might mean the angular-landing site broke.

I think what I need to do it make a completely separate angular site from scratch but I want to be able to access the proxies from the main app and share the login/session. Any advice anyone has for me on this would be much appreciated!

I don't suppose AspNetZero have any plans to migrate the MVC Public site to Angular do they?!

Yes I did think about doing this but I want to share the user session with the main app. If they are logged in on app.mywebsite.com, www.mywebsite.com should show the logged in user menu, not the login button. I want to do this because if they are logged in, the landing site will show different menu items etc

To add to my original question, I have found that the "Deploy Azure Static Web App (Preview)" Task in the build pipeline of Azure doesnt seem to like the Agent Specification being set to Windows so I have tried splitting the pipeline into 2 different jobs, using different agents but it still wont build. In the end I gave up and pushed the dist folder manually using SWA Deploy. Not ideal but it will do for now

https://learn.microsoft.com/en-us/azure/static-web-apps/static-web-apps-cli-deploy

Thanks @ismcagdas, I did try decorating my second db context like this:

[MultiTenancySide(MultiTenancySides.Tenant)]
    public class SecondDbContext : AbpDbContext
    {
        public virtual DbSet<CustomEntity> Stuff { get; set; }  
    ...
    }

but it isnt picked up on my Custom Connection String Resolver:

  public override string GetNameOrConnectionString(ConnectionStringResolveArgs args)
        {

            if (args.MultiTenancySide == MultiTenancySides.Tenant)
            {
                //This is never hit
            }
        ...
        }

Any idea why? Is there something else I need to do? I could use the DbContextConcreteType but it seems like just using the MultiTenancySide would be much more efficient? Tenants will always have a connection string and will only access ONE Custom DbContext (In Addition to the AspNetZeroDbContext, for shared data lookups and user logins etc)

  public override string GetNameOrConnectionString(ConnectionStringResolveArgs args)
        {
            if (args["DbContextConcreteType"] as Type == typeof(SecondDbContext))
            {
                var tenantCacheItem = _tenantCache.Get(_currentUnitOfWorkProvider.Current.GetTenantId().Value);
                return tenantCacheItem.ConnectionString;
            }
            if (args["DbContextConcreteType"] as Type == typeof(ThirdDbContext))
            {
                var tenantCacheItem = _tenantCache.Get(_currentUnitOfWorkProvider.Current.GetTenantId().Value);
                return tenantCacheItem.ConnectionString;
            }

            return base.GetNameOrConnectionString(args);
        }

Sorry to keep coming back to this but I still don't have a working solution for this. We have 2 different types of tenant, which is denoted by a property on the Tenant called "TenantType". I have created 2 extra dbContexts for these, each with their own DbContextConfigurer, DbContextFactory and DbMigrator Class (Which inherits from my iTenantMigrator interface, as suggested above). I have also created a CustomConnectionStringResolver class but I dont really want to use args["DbContextConcreteType"] in order to work out what dbContext is requesting the connection string, is there not a better way? I was hoping to use args.MultiTenancySide but this seems to be coming through as NULL when it is from my second dbContext, which is decorated with [MultiTenancySide(MultiTenancySides.Tenant)]. Am I misunderstanding what the args.MultiTenancySide property does?

I was thinking that If I added a row to the abpUsers table for each tenant that the user is linked to but use the abpUserAccounts table to do the actaual login... So Username/Email would be unique in abpUserAccounts but will exist multiple times in abpUsers if the user is linked to multiple teants? Then when the abpUserAccount has been logged in, they will then choose an abpUser to use for the session and provide a way of switching this in the user menu, like you do for Linked Accounts? We work with Groups of schools and certain users will need access to all the schools in their groiup.

Thank you @jeffmh and @ismcagdas for all your help, I love asp net zero and getting support through the forum is great!

So my current thinking is that maybe I move the authentication fields from abpUsers to abpUserAccounts. Log the user in with the info in abpUserAccounts. Each "UserAccount" will have a row in the abpUsers table for each tenant they are linked with. After logging in, they will select the Tenant they want from the ones they are linked with, which will effectivly log them in using the abpUser row? Does this make sense or is there a better approach?

Ok so I think I have pretty much got the multiple db context thing figured out now so thank you for all your help! It was mostly the connectionstringresolver I needed. Here is a simpified version of it, just incase it helps anyone else:

namespace MyProject.EntityFrameworkCore
{
    public class MyConnectionStringResolver : DefaultConnectionStringResolver
    {
        private readonly IConfigurationRoot _appConfiguration;
        private readonly ICurrentUnitOfWorkProvider _currentUnitOfWorkProvider;
        private readonly ITenantCache _tenantCache;

        public MyConnectionStringResolver(
            IAbpStartupConfiguration configuration,
            ICurrentUnitOfWorkProvider currentUnitOfWorkProvider,
            ITenantCache tenantCache)
            : base(configuration)
        {
            _currentUnitOfWorkProvider = currentUnitOfWorkProvider;
            _tenantCache = tenantCache;
        }

        public override string GetNameOrConnectionString(ConnectionStringResolveArgs args)
        {
            if (args["DbContextConcreteType"] as Type == typeof(MyProjectDbContextSecond))
            {
                                var tenantCacheItem = _tenantCache.Get(_currentUnitOfWorkProvider.Current.GetTenantId().Value);
                return tenantCacheItem.ConnectionString;
            }
            
            return base.GetNameOrConnectionString(args);
        }
    }

}

So now all my main aspnetzero data is accessed from the Host Db and My custom tenant specific entities are stored in their own databases. Is this the best way to achieve it or is there a better way to identify which Connection string to use without a Type comparison? I have tried marking my second dbcontext with

[MultiTenancySide(MultiTenancySides.Tenant)]

But args.MultiTenancySide is always Host or Null? I was thinking maybe I could have some sort of dbcontext interface with a property on it I could check to see what dbcontext to use?

Anyway, my next issue is that I am being asked to allow users to have access to multiple tenants and be able to set roles against users and tenants. My users all sign up with a unique email address and I want them to only exist once. I cant use the linked account or "Delegation" as this requires a user to map to from the tenant linked to. I could add a table for this but I am not sure it will work as even if they can login, they wont get the right data because of the tenant filters? What is the best aproach here?

Showing 1 to 10 of 27 entries