Activities of "Sturla"

After thinking this over I have decided to go with mulit-tenancy where the user never knows anything about its tenant name. So a unique e-mail accross the SaaS, an email can only be used once!

Context and resoning

  1. The question in the Conclusion of Alper's article helped but also future security/GDPR and future moving of a tenant is a part of it.
  2. Logged in users (email) will be unique so there will only be one email for one tenant! Its not possible to create many tenants with the same email!
  3. Tenant name will be auto generated at registration! The user (a tenant) will never need to know about its tenant name (there will never be a subdomain or custom domain for a tenant) so the tenant name is irrelevant.

| Step | URL | What the user types | What happens internally | | --------------- | ------------------------------ | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Register | https://yoursite.is/register | E-mail + Password | • Host context creates a new tenant with an auto-generated name (GUID (or first part of the e-mail)). <br>• Creates the first user in that tenant and assigns the TenantAdmin role. | | Login | https://yoursite.is/login | E-mail + Password | A custom LoginModel locates the user across all tenants, switches CurrentTenant to the user’s TenantId, then performs normal sign-in. | | After login | /app | – | CurrentUserTenantResolveContributor keeps the tenant id in the auth cookie, so every request is already scoped correctly. |

Highlevel implementation

  1. Hide ABP’s tenant switcher entirely (no tenant selector)
  2. Self-registration service that creates the tenant name (probably a Guid instead of the first part of email)
  3. Enforce global e-mail uniqueness once at sign-up

I think this is a good conclusion. What do you think?

p.s <br> in the table works when you Preview it but not when published so this is a bug!

Are you sure you want to do this?

No not really 🤦‍♂️

I´m trying to figure out if multi-tenancy is the correct way for my SaaS or not.

What I do know is that I do not want the user to have to input all three

  • user name (or email)
  • password
  • and a tenant name

So maybe this isn´t a multi-tenant system at all... I´m really starting to lean towards that...

What is missing in my question is the following:

  • I want the user input to become the tenant

So when a user registers with username/email/password the username becomes the tenant autmatically. When they then login with a username/password that username is connected to the registered tenant.

Context for wanting this to be multi tenant

  • I´m looking for data separation and security
  • There will never be any custom domain url

Ok I´m going to answer this and suggest that this will be added as whole to to the abp.io documentation (you can fix it if its incurrect but it works now fine for me!)

How to change the color pallet

I didn´t really need to create a new template, I just needed to change the default color pallet schema so it works with every default theme.

Step 1

Create this css file Blazor/wwwroot/css/custom-leptonx-overrides.cs

Step 2

Add the following overrides to it (you choose the colors)

/*
 * YourSite Custom LeptonX Theme Overrides
 * Following ABP LeptonX theme customization guidelines
 * High specificity overrides to ensure they load after LeptonX defaults
 */

/* LeptonX and Bootstrap color variable overrides */
:root {
    /* LeptonX variables */
    --lpx-primary: #BB977D !important;
    --lpx-primary-rgb: 72, 81, 88 !important;
    --lpx-secondary: [#485158](https://abp.io/QA/Questions/485158) !important;
    --lpx-secondary-rgb: 187, 151, 125 !important;
    --lpx-success: #EEA646 !important;
    --lpx-success-rgb: 238, 166, 70 !important;
    --lpx-warning: #EEA646 !important;
    --lpx-warning-rgb: 238, 166, 70 !important;
    --lpx-info: #BB977D !important;
    --lpx-info-rgb: 72, 81, 88 !important;
    --lpx-danger: #ef4444 !important;
    --lpx-danger-rgb: 239, 68, 68 !important;
    --lpx-brand: #BB977D !important;
    --lpx-brand-rgb: 72, 81, 88 !important;
    --lpx-brand-contrast: #fff !important;
    --lpx-brand-hover: #3a434a !important;
    --lpx-brand-active: #23272b !important;
    /* Bootstrap variables */
    --bs-primary: #BB977D !important;
    --bs-primary-rgb: 72, 81, 88 !important;
    --bs-secondary: [#485158](https://abp.io/QA/Questions/485158) !important;
    --bs-secondary-rgb: 187, 151, 125 !important;
    --bs-success: #EEA646 !important;
    --bs-success-rgb: 238, 166, 70 !important;
    --bs-warning: #EEA646 !important;
    --bs-warning-rgb: 238, 166, 70 !important;
    --bs-info: #BB977D !important;
    --bs-info-rgb: 72, 81, 88 !important;
    --bs-danger: #ef4444 !important;
    --bs-danger-rgb: 239, 68, 68 !important;
    /* Button backgrounds and borders */
    --bs-btn-primary-bg: #BB977D !important;
    --bs-btn-primary-border-color: #BB977D !important;
    --bs-btn-primary-hover-bg: #3a434a !important;
    --bs-btn-primary-hover-border-color: #3a434a !important;
    --bs-btn-primary-active-bg: #23272b !important;
    --bs-btn-primary-active-border-color: #23272b !important;
    --bs-btn-primary-color: #fff !important;
    --bs-btn-secondary-bg: [#485158](https://abp.io/QA/Questions/485158) !important;
    --bs-btn-secondary-border-color: [#485158](https://abp.io/QA/Questions/485158) !important;
    --bs-btn-secondary-hover-bg: #a07d5f !important;
    --bs-btn-secondary-hover-border-color: #a07d5f !important;
    --bs-btn-secondary-active-bg: #8a6a4d !important;
    --bs-btn-secondary-active-border-color: #8a6a4d !important;
    --bs-btn-secondary-color: #fff !important;
}

/* High specificity for buttons and LeptonX/Bootstrap classes */
.btn-primary, .lpx-btn-primary {
    background-color: var(--bs-primary) !important;
    border-color: var(--bs-primary) !important;
    color: var(--bs-btn-primary-color) !important;
}
.btn-secondary, .lpx-btn-secondary {
    background-color: var(--bs-secondary) !important;
    border-color: var(--bs-secondary) !important;
    color: var(--bs-btn-secondary-color) !important;
}

/* Optionally, override icon and menu item colors */
.lpx-menu-item.selected .lpx-menu-item-icon,
.lpx-menu-item.selected .lpx-menu-item-link,
.lpx-menu-item.selected .lpx-menu-item-link .lpx-menu-item-icon {
    color: var(--lpx-brand) !important;
}

/* Dark mode overrides (optional, extend as needed). 
   This can be done for all the other themes "light","system" etc. */
[data-bs-theme="dark"], [data-theme="dark"] {
    --lpx-primary: #23272b !important;
    --lpx-secondary: #8a6a4d !important;
    --bs-primary: #23272b !important;
    --bs-secondary: #8a6a4d !important;
}

Step 3: Update main.css in Blazor.Client

Add the css above to the www/main.css file.

Step 4: Create a bundle contriputor

Create this class in Blazor root

public class YourSiteThemeBundleContributor : BundleContributor
{
    public override void ConfigureBundle(BundleConfigurationContext context)
    {
        // Add global styles and custom CSS files in the correct order
        context.Files.Add(new BundleFile("/global-styles.css", true));
        context.Files.Add(new BundleFile("/css/custom-leptonx-overrides.css", true));
        context.Files.Add(new BundleFile("/main.css", true));
    }
}

and update the code in ConfigureBundles() in your module class

Configure<AbpBundlingOptions>(options =>
{
    // Blazor Web App
    options.Parameters.InteractiveAuto = true;


    // MVC UI - Use bundle contributor for global styles
    options.StyleBundles.Configure(
        LeptonXThemeBundles.Styles.Global,
        bundle =>
        {
            bundle.AddContributors(typeof(YourSiteThemeBundleContributor));
        }
    );

    options.ScriptBundles.Configure(
        LeptonXThemeBundles.Scripts.Global,
        bundle =>
        {
            bundle.AddFiles("/global-scripts.js");
        }
    );

    // Blazor UI - Use bundle contributor for global styles
    options.StyleBundles.Configure(
        BlazorLeptonXThemeBundles.Styles.Global,
        bundle =>
        {
            bundle.AddContributors(typeof(YourSiteThemeBundleContributor));
        }
    );
});

I hope this will help the next poor soul out there!

Thank you liming but I figured this out finally. It was _Host.cshtml from Elsa that was causing my issue

I hade builder.MapFallbackToPage("/_Host");

and then had to do things arround that.

ABP Suite is creating lot of code with warnings!

then there are still (I have reported this 2x) spaces here and there through out the generated code

and so many other issues like this..

Why isn´t there more love for craftmanship in this product?

There is a bug Blazor razor pages when generating property of nullable Guid with ABP Suite

error CS1503: Argument 1: cannot convert from 'System.Guid?' to 'string'
error CS1503: Argument 2: cannot convert from 'method group' to 'Microsoft.AspNetCore.Components.EventCallback'

So instead of

do something like this

Are there to few tests with ABP Studio?

Just filed this bug in wrong chat https://abp.io/qa/questions/8330/3a1b39f2-f1f5-36a9-5e27-77cf1f14a83e please take a look

[UPDATE: I think I put this in the wrong issue since this is in version 9.2.2]

I get the following error when clicking "Save and Generate"

Error occurred on DB migration step: Build started...
Build succeeded.
System.Reflection.ReflectionTypeLoadException: Unable to load one or more of the requested types.
Method 'get_IsExtension' in type 'Microsoft.CodeAnalysis.CodeGeneration.CodeGenerationArrayTypeSymbol' from assembly 'Microsoft.CodeAnalysis.Workspaces, Version=4.8.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' does not have an implementation.
Method 'get_IsExtension' in type 'Microsoft.CodeAnalysis.CodeGeneration.CodeGenerationConstructedNamedTypeSymbol' from assembly 'Microsoft.CodeAnalysis.Workspaces, Version=4.8.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' does not have an implementation.

.....

Method 'get_PartialDefinitionPart' in type 'Microsoft.CodeAnalysis.CodeGeneration.CodeGenerationPropertySymbol' from assembly 'Microsoft.CodeAnalysis.Workspaces, Version=4.8.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' does not have an implementation.
Method 'get_IsExtension' in type 'Microsoft.CodeAnalysis.CodeGeneration.CodeGenerationTypeParameterSymbol' from assembly 'Microsoft.CodeAnalysis.Workspaces, Version=4.8.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' does not have an implementation.

but when I build in Visual Studio there are no errors!? Why would that be?

Also running the dbMigrator with Debug-Run works without issues..but I need to manually create the migration..

Can it be realeted to this version mitchmatch (its transitive)

I´m running abp 9.2.2 with everything updated.

[UPDATE] I got this working by adding the following snippet into my Blazor.csproj file

<ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.14.0" />
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.14.0" />
    <PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.14.0" />
    <PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="4.14.0" />
   </ItemGroup>

When you have not saved entity (I had 20 properties!!!) and you click a link (e.g the Font Awesome icons one) you get navagated away and you CANT get back with previous data!! Please fix this so that this behaves like a normal browser retaining the information OR open links in another tab...

Showing 11 to 20 of 218 entries
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.1.0-preview. Updated on October 30, 2025, 06:33