I have had another go and done some more googling but i still cant get it to work! If you would be interested in building a solution for me for a fee please do let me know how I can contact you directly! It would be a big help!
Thanks Jeffmh, I am still not sure I fully understand but I relly apreciate your help! I am not sure if this is allowed on here but if you were interested in some paid work to set this up for me I would be more than happy to pay! I am quite short on time so as much as I am determind to learn, it may be best for me to just get an expert in and then I can go over the code?
Thanks Jeffmh, I have duplicated the AbpZeroDbMigrator class and renamed it and put the correct db context on it. I also already created a Factory and Configurer for my new dbContext so I think I have all the right bits now, I am just not sure ow to modify the Tenant Manager to create the correct db context? I can see where the db is created:
//Create tenant database
_abpZeroDbMigrator.CreateOrMigrateForTenant(tenant);
Do I just need to Add my custom Migrator to the constructor and it will automatically get injected? Appologies for my naievaty, I am a winforms developer tasked with this huge project!
Thank you jeffmh, that is helpful! Sorry for the late reply, for some reason I didnt get a notification of your reply!
Ok so I have created a new aspnetzero solution and made the following changes:
Added a new custom db context and added a "Person" entity to it manually
namespace Cloud.EntityFrameworkCore
{
[MultiTenancySide(MultiTenancySides.Tenant)]
public class CloudDbContextSecond : AbpDbContext
{
/* Define an IDbSet for each entity of the application */
public virtual DbSet<People> People { get; set; }
public CloudDbContextSecond(DbContextOptions<CloudDbContextSecond> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder) { }
}
}
Update the main db context to be Host:
namespace Cloud.EntityFrameworkCore
{
[MultiTenancySide(MultiTenancySides.Host)]
public class CloudDbContext : AbpZeroDbContext<Tenant, Role, User, CloudDbContext>, IAbpPersistedGrantDbContext
{
Added a custom connection string resolver
namespace Cloud.EntityFrameworkCore
{
public class MyConnectionStringResolver : DefaultConnectionStringResolver
{
private readonly IConfigurationRoot _appConfiguration;
public MyConnectionStringResolver(IAbpStartupConfiguration configuration, IHostingEnvironment hostingEnvironment)
: base(configuration)
{
_appConfiguration =
AppConfigurations.Get(hostingEnvironment.ContentRootPath, hostingEnvironment.EnvironmentName);
}
public override string GetNameOrConnectionString(ConnectionStringResolveArgs args)
{
if (args["DbContextConcreteType"] as Type == typeof(CloudDbContextSecond))
{
return "?";
}
return base.GetNameOrConnectionString(args);
}
}
}
Updated CloudEntityFrameworkCoreModule to add the second db context and custom connection string resolver
public override void PreInitialize()
{
if (!SkipDbContextRegistration)
{
Configuration.ReplaceService<IConnectionStringResolver, MyConnectionStringResolver>();
Configuration.Modules.AbpEfCore().AddDbContext<CloudDbContext>(options =>
{
if (options.ExistingConnection != null)
{
CloudDbContextConfigurer.Configure(options.DbContextOptions, options.ExistingConnection);
}
else
{
CloudDbContextConfigurer.Configure(options.DbContextOptions, options.ConnectionString);
}
});
Configuration.Modules.AbpEfCore().AddDbContext<CloudDbContextSecond>(options =>
{
if (options.ExistingConnection != null)
{
CloudDbContextSecondConfigurer.Configure(options.DbContextOptions, options.ExistingConnection);
}
else
{
CloudDbContextSecondConfigurer.Configure(options.DbContextOptions, options.ConnectionString);
}
});
}
When a new tenant is being created, I suply a Connection String and it creates a database with the Main DB Context but creates the users etc in the Host Database, so I am sort of half way there! How do I tell it to create the db with the Second db context for new tenants? I also need to test CRUD on the Second DB context but I cant get the Entity Generator to create entities on the second db context? I have tried changing the config file but when it runs, it just hangs? Also, I am not sure it is actually going to get a connection string to the second db context anyway. Sorry for all the questions, just struggling to get my head around all this!
Thanks ismcagdas, I think that is exactly what I need. So I have followed this guide https://stackoverflow.com/questions/49243891/how-to-use-multiples-databases-in-abp-core-zero which sets up the project like the MultipleDbContextEfCoreDemo. Instead of creating the MyConnectionStringResolver I need have created a DbPerTenantConnectionStringResolver as per your example. Or do I need both? I can see how MyConnectionStringResolver reads the second connection string from the appsettings.json file but I am not sure how to use DbPerTenantConnectionStringResolver? I am assuming that like the MyConnectionStringResolver, I set it up like this In the CoreModule PreInitialize void?
Configuration.ReplaceService<IDbPerTenantConnectionStringResolver, DbPerTenantConnectionStringResolver>();
When I create a new tenant, I only have the choice to tell it to use the host database or supply a connection string? I want it to use the host database for everything EXCEPT the stuff on the Second DbContext I have created. When creating a tenant, it should create a new database for that tenant using the TenantDbContext I have created and set the connectionstring property for that tenant but I am not sure how to achieve this?
Thanks, I had seen that example but it works with two static connection strings but our case is quite complex.
We effectively have THREE types of Tenant: Host, Company and Alliance. Host is pretty self explanitory, company is a “Tenant” in the traditional sense of the ABP framework whereas an Alliance is basically a “Group” of companies. An “Alliance” user can login and view summary data from all of the “Companies” that it is linked with.
All user data for ALL Companies and Alliances should be stored in the main AspNetZero database, allowing us to have a single login page and no need to select a tenant when logging in. Users are unique and identified by their email address. It should be possible for a User to be associated with multiple Tenants. They may belong to a single tenant, e.g. using the TenantId in the users table, but another tenant should be able to give that same user access to their data. If a user is associated with multiple tenants, they will be given a choice of tenants to view after login, with the ability to Switch tenant later.
This all means that I think we need at least 2 dbContext that can be used simultaniously:
AspNetZeroContext This SINGLE database will contain all the standard ABP tables and store users, roles & permissions etc for all tenants. It will also have some custom tables for “Lookups” (Common data that all tenants will need READ ONLY access to) and CRM functionality such as Support Tickets and Invoices etc.
CompanyContext Each Tenant of type “Company” will have its own database that will contain ONLY custom entities, created using ASP.NET Zero Power Tools in visuall studio.e.g. Staff Entity. The Staff entity may have an “EthnicityID” field, this ID will be taken from a “Lookup” table stored in the Main AspNetZero database.
AllianceContext? This type of tenant may not need to be an actual dbContext as it wont have its own database, it will just need to be able to pull data from multiple "Company" databases.
We have written the code to get the second dbContext but I am unsure of the best aproach when we do not have a SINGLE connection string for the second dbContext, it will be tenant specific? Am I missing something here?
I think I have the same issue, I have a multitenant app with unique email addresses for each user. There is no way to select a tenant from the login screen, it gets the tenant id from the username/password entered (Username is synced with Email). I have got a "Login with Azure AD" button but the Azure AD they log into will depend on their email address as each tenant will have their own App registration?
I basically need microsoft to Authenticate them, then I will need to check that we have a tenant setup for them on our system and that they are in a valid Security Group to allow them to login to their tenancy with the relivant roles. I will need to look at the Client ID and Client Secret sent back as part of the login process to see if it matches up with any tenant in our DB.
But how do I know which Azure AD url to use if I dont know what the tenant is? From my research it looks as if I should create my own App Registration in our own Azure AD and set it up to 'Accounts in any organizational directory (Any Azure AD directory - Multitenant)' which will allow my requests for authentication to all be passed to the same place. My customers will then need to setup their own App Registration and I will need to store these details against each tenant in my DB so that once they have been authenticated, I can work out which tenant they belong to and check to make sure they are in the right Role/Security Group to be able to access the software?
Ok so I have now got my Pipeline working correctly, for anyone else that is interested, here is my YAML:
pool:
name: MyBuildPool - Self Hosted
demands:
- msbuild
- visualstudio
- npm
- vstest
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core sdk 6.x'
inputs:
version: 6.x
includePreviewVersions: true
- task: NuGetToolInstaller@1
displayName: 'Use NuGet 6.x'
inputs:
versionSpec: 6.x
- task: NuGetCommand@2
displayName: 'NuGet restore MyProject.Demo'
inputs:
restoreSolution: 'aspnet-core/MyProject.Demo.Web.sln'
- task: VSBuild@1
displayName: 'Build solution MyProject.Demo.Web.Host'
inputs:
solution: 'aspnet-core/src/MyProject.Demo.Web.Host/MyProject.Demo.Web.Host.csproj'
msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactstagingdirectory)\\"'
platform: '$(BuildPlatform)'
configuration: '$(BuildConfiguration)'
- task: DeleteFiles@1
displayName: 'Delete files from .dist/web'
inputs:
SourceFolder: '$(Build.SourcesDirectory)/angular'
Contents: .dist/web
RemoveSourceFolder: true
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3
displayName: 'Yarn Install'
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-task.Yarn@3
displayName: 'Yarn '
inputs:
projectDirectory: angular
- task: Npm@1
displayName: 'npm run publish'
inputs:
command: custom
workingDir: angular
verbose: false
customCommand: 'run publish'
publishRegistry: useFeed
- task: CopyFiles@2
displayName: 'Copy web.config to angular root'
inputs:
SourceFolder: '$(Build.SourcesDirectory)\angular'
Contents: web.config
TargetFolder: '$(Build.SourcesDirectory)\angular\.dist\web'
OverWrite: true
- task: ArchiveFiles@2
displayName: 'Archive $(Build.SourcesDirectory)\angular\.dist\web'
inputs:
rootFolderOrFile: '$(Build.SourcesDirectory)\angular\.dist\web'
archiveFile: '$(build.artifactstagingdirectory)\AngularUI.zip'
- task: VSTest@2
displayName: 'Test Assemblies'
inputs:
testAssemblyVer2: |
**\$(BuildConfiguration)\*test*.dll
!**\obj\**
platform: '$(BuildPlatform)'
configuration: '$(BuildConfiguration)'
- task: PublishSymbols@1
displayName: 'Publish symbols path'
inputs:
SearchPattern: '**\bin\**\*.pdb'
continueOnError: true
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact'
inputs:
PathtoPublish: '$(build.artifactstagingdirectory)'
ArtifactName: '$(Parameters.ArtifactName)'
- task: mspremier.PostBuildCleanup.PostBuildCleanup-task.PostBuildCleanup@3
displayName: 'Clean Agent Directories'
I have also setup my release according to the documentation here https://docs.aspnetzero.com/en/aspnet-core-angular/latest/Step-by-step-publish-to-azure-angular-staticsite#publish-files-to-azure-storage and it deploys successfully but I cannot seem to access the site? Do i need to create any extra DNS records as currently I only have the API and CDN records on the DNS but not an actual link for viewing the site/login screen? Should it just be cdn.mysite.com?
So I have 2 Resource Groups setup in azure SiteMain and SiteDev. Both have the following set of resources: siteangular / siteangulardev - Storage Account with Static Site enabled siteapi / siteapidev - .net 6 App Service sitepublic / sitepublicdev - .net 6 App Service
I want Two releases in Devops, one for the Public project and one for main project (Angular and API). I would like to use variables to switch between building/releasing the Dev Branch and the Master branch to the relevant locations.