Implementing Custom Tenant Logo Feature in ABP Framework: A Step-by-Step Guide
Do you want to define logo per tenant? I got you covered, read this step-by-step guide to see how you can implement it!
Introduction
In multi-tenant applications built with ABP Framework, customizing the tenant's branding elements like logos is a common requirement. While ASP.NET Zero provides this feature out of the box, implementing it in a standard ABP application requires some custom development.
In this tutorial, I'll show you how to implement a custom tenant logo feature in an ABP application. We'll take a simple yet effective approach that you can later extend and customize according to your specific needs.
Note: This implementation provides a basic foundation for tenant logo customization. You can enhance it further by adding features like image upload functionality, implementing validation, or improving the UI/UX of the settings page. The code structure is kept simple for demonstration purposes, but you can refactor and optimize it according to your application's requirements.
Step-by-Step Implementation
Let's walk through the process of implementing a custom tenant logo feature in your ABP application. We'll break this down into several manageable steps, starting with project creation and moving through the necessary configurations and implementations.
In this tutorial, we'll implement a custom tenant logo feature by adding a dedicated tab to the settings page. This will allow tenant administrators to easily manage and update their tenant's logo through a user-friendly interface.
You can find the complete code of this tutorial on GitHub.
1. Create a New ABP Application
First, create a new ABP application named AbpCustomTenantLogo
. You can use either the ABP CLI (abp new AbpCustomTenantLogo -d mongodb -csf
) or ABP Studio to create the project. For detailed instructions, refer to the ABP documentation. For this tutorial, I'll be using a layered web application with MVC UI and MongoDB database, but the concepts apply to any UI framework or database provider.
2. Define the Setting
In the domain layer's settings folder, there is a file named AbpCustomTenantLogoSettingDefinitionProvider.cs
. In this file, we'll define a new setting to store the tenant logo URL:
using AbpCustomTenantLogo.Localization; | |
using Volo.Abp.Localization; | |
using Volo.Abp.Settings; | |
namespace AbpCustomTenantLogo.Settings; | |
public class AbpCustomTenantLogoSettingDefinitionProvider : SettingDefinitionProvider | |
{ | |
public override void Define(ISettingDefinitionContext context) | |
{ | |
context.Add(new SettingDefinition( | |
name: "CustomTenantLogo", | |
defaultValue: "", | |
displayName: L("Custom Tenant Logo"), | |
isVisibleToClients: true | |
)); | |
} | |
private static LocalizableString L(string name) | |
{ | |
return LocalizableString.Create<AbpCustomTenantLogoResource>(name); | |
} | |
} |
This setting definition creates a tenant-specific setting that will store the logo URL.
3. Create Application Services
Next, we'll create the application service interface and implementation to manage the tenant logo URL setting. This service will ensure that the logo URL can only be modified for the current tenant:
ICustomTenantLogoAppService.cs (under the *.Application.Contracts
project):
using System; | |
using System.Threading.Tasks; | |
using Volo.Abp.Application.Services; | |
namespace AbpCustomTenantLogo | |
{ | |
public interface ICustomTenantLogoAppService : IApplicationService | |
{ | |
Task UpdateTenantLogoUrlAsync(Guid? tenantId, string tenantLogoUrl); | |
} | |
} |
CustomTenantLogoAppService.cs (under the *.Application
project):
using System; | |
using System.Threading.Tasks; | |
using Volo.Abp; | |
using Volo.Abp.Application.Services; | |
using Volo.Abp.SettingManagement; | |
namespace AbpCustomTenantLogo | |
{ | |
public class CustomTenantLogoAppService : ApplicationService, ICustomTenantLogoAppService | |
{ | |
private readonly ISettingManager _settingManager; | |
public CustomTenantLogoAppService(ISettingManager settingManager) | |
{ | |
_settingManager = settingManager; | |
} | |
public async Task UpdateTenantLogoUrlAsync(Guid? tenantId, string tenantLogoUrl) | |
{ | |
//not host tenant, and if the tenantId is not the current tenant id, throw an exception | |
if(CurrentTenant.Id.HasValue && tenantId != CurrentTenant.Id.Value) | |
{ | |
throw new UserFriendlyException("You are not allowed to update this tenant's logo."); | |
} | |
await _settingManager.SetForCurrentTenantAsync("CustomTenantLogo", tenantLogoUrl); | |
} | |
} | |
} |
The application service handles logo URL updates and includes basic security checks. You can add more security checks here - currently, we check if the tenantId
is not the host tenant and if it's not the current tenant ID, we throw an exception for demonstration purposes.
4. Create the View Component
Add a view component in the .web
project to display and manage the logo settings (under the Components
folder):
CustomTenantLogoViewComponent.cs:
using System.Threading.Tasks; | |
using Microsoft.AspNetCore.Mvc; | |
using Volo.Abp.AspNetCore.Mvc; | |
using Volo.Abp.SettingManagement; | |
namespace AbpCustomTenantLogo.Web.Components | |
{ | |
public class CustomTenantLogoViewComponent : AbpViewComponent | |
{ | |
private readonly ISettingManager _settingManager; | |
public CustomTenantLogoViewComponent(ISettingManager settingManager) | |
{ | |
_settingManager = settingManager; | |
} | |
public virtual async Task<IViewComponentResult> InvokeAsync() | |
{ | |
var customTenantLogoSetting = await _settingManager.GetOrNullForCurrentTenantAsync("CustomTenantLogo"); | |
return View("~/Components/Default.cshtml", new CustomTenantLogoViewModel | |
{ | |
CustomTenantLogoUrl = customTenantLogoSetting | |
}); | |
} | |
} | |
public class CustomTenantLogoViewModel | |
{ | |
public string CustomTenantLogoUrl { get; set; } | |
} | |
} |
5. Create the Razor Page for the View Component
Add the view (Default.cshtml
) and JavaScript code in the .web
project (under the Components
folder):
@using AbpCustomTenantLogo.Web.Components | |
@model CustomTenantLogoViewModel | |
<form id="CustomTenantLogoForm"> | |
<div class="input-group mb-3"> | |
<span class="input-group-text">Tenant Logo URL:</span> | |
<input type="text" class="form-control" id="CustomTenantLogoUrl" required/> | |
</div> | |
<p class="form-text text-muted mb-3">Use relative path or full external URL...</p> | |
<button class="btn btn-primary" type="button" id="CustomTenantLogoSaveButton">Save</button> | |
</form> | |
<script> | |
$('#CustomTenantLogoSaveButton').click(function (e) { | |
e.preventDefault(); | |
$("#CustomTenantLogoForm").submit(); | |
}); | |
$('#CustomTenantLogoForm').submit(function (e) { | |
e.preventDefault(); | |
var customTenantLogoUrl = $('#CustomTenantLogoUrl').val(); | |
abpCustomTenantLogo.customTenantLogo.updateTenantLogoUrl(abp.currentTenant.id, customTenantLogoUrl) | |
.then(function () { | |
alert("Tenant logo updated successfully."); | |
}); | |
}); | |
</script> |
Note: For simplicity, we've included the JavaScript directly in the view. Normally, you should move this to a separate .js file. But for now, we'll keep it here, for the sake of simplicity. Please refer to Widgets document for more information.
6. Create the Settings Page Contributor
Add a settings page contributor to integrate with ABP's settings management (under the *.Web
project):
CustomTenantLogoSettingPageContributor.cs:
using System.Threading.Tasks; | |
using AbpCustomTenantLogo.Web.Components; | |
using Volo.Abp.SettingManagement.Web.Pages.SettingManagement; | |
namespace AbpCustomTenantLogo.Web | |
{ | |
public class CustomTenantLogoSettingPageContributor : ISettingPageContributor | |
{ | |
public Task<bool> CheckPermissionsAsync(SettingPageCreationContext context) | |
{ | |
//we can check a permission in here, but for now just assume the permission is granted. | |
//TODO: check a permission in here & also it should just should be shown by the host admin and admin of a tenant not by everyone | |
return Task.FromResult(true); | |
} | |
public Task ConfigureAsync(SettingPageCreationContext context) | |
{ | |
context.Groups.Add( | |
new SettingPageGroup( | |
id: "CustomTenantLogoWrapper", | |
displayName: "Custom Tenant Logo", | |
typeof(CustomTenantLogoViewComponent) | |
) | |
); | |
return Task.CompletedTask; | |
} | |
} | |
} |
7. Register the Settings Page Contributor
In your AbpCustomTenantLogoWebModule
's ConfigureServices
method, add the following code:
//add the setting page contributor
Configure<SettingManagementPageOptions>(options =>
{
options.Contributors.Add(new CustomTenantLogoSettingPageContributor());
});
8. Update the Custom Branding Provider
Finally, we should update the custom branding provider class (under the *.Web
project) to display the tenant-specific logo:
AbpCustomTenantLogoBrandingProvider.cs:
using System.Threading.Tasks; | |
using Volo.Abp.Ui.Branding; | |
using Volo.Abp.DependencyInjection; | |
using Volo.Abp.MultiTenancy; | |
using Volo.Abp.SettingManagement; | |
using Volo.Abp.Threading; | |
namespace AbpCustomTenantLogo.Web; | |
[Dependency(ReplaceServices = true)] | |
public class AbpCustomTenantLogoBrandingProvider : DefaultBrandingProvider | |
{ | |
private readonly ISettingManager _settingManager; | |
private readonly ICurrentTenant _currentTenant; | |
public AbpCustomTenantLogoBrandingProvider(ISettingManager settingManager, ICurrentTenant currentTenant) | |
{ | |
_settingManager = settingManager; | |
_currentTenant = currentTenant; | |
} | |
public override string? LogoUrl => AsyncHelper.RunSync(GetTenantLogoUrlAsync) ?? base.LogoUrl; | |
public async Task<string?> GetTenantLogoUrlAsync() | |
{ | |
//add caching and other logic here!!! | |
var tenantId = _currentTenant.Id; | |
if(!tenantId.HasValue) | |
{ | |
return null; | |
} | |
var customTenantLogoUrl = await _settingManager.GetOrNullForCurrentTenantAsync("CustomTenantLogo"); | |
return string.IsNullOrWhiteSpace(customTenantLogoUrl) ? null : customTenantLogoUrl; | |
} | |
} |
Here, we're getting the tenant-specific logo URL from the setting and returning it as the logo URL. If the logo URL is not found, we return the default logo URL and also, for the host tenant, we return the default logo URL.
Testing the Implementation
After implementing all the components, run your application and navigate to the settings page. You should see a new "Custom Tenant Logo" section where you can specify the logo URL for your tenant.
Here's how it looks in action:
Conclusion
This implementation provides a basic foundation for managing tenant-specific logos in your ABP application. While this example focuses on simplicity, you can extend it further by:
Adding image upload functionality
Adding validation for logo URLs
Implementing proper permission checks
Adding preview functionality
The code structure provided here gives you a solid starting point for implementing these additional features based on your specific requirements.