421.6 Abp microserices and Keycloak - chempkovsky/CS82ANGULAR GitHub Wiki

Notes

  • Only security aspects will be discussed and implemented.
  • In this article we will rewrite Add Tenant-page

Tools

CustomTenantAppService

  • In the rupbes.tstapp.Auth.Host.csproj-project create TenantManagement-folder
    • In the TenantManagement-folder of the rupbes.tstapp.Auth.Host.csproj-project create CustomTenantAppService-class as follows:
      • User Id is getting from ExtraProperties of the TenantCreateDto-object
Click to show the code
using Microsoft.AspNetCore.Authorization;
using System;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.EventBus.Local;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectExtending;
using Volo.Abp.TenantManagement;

namespace rupbes.tstapp.TenantManagement
{
    [Dependency(ReplaceServices = true)]
    public class CustomTenantAppService : TenantAppService
    {
        public CustomTenantAppService(ITenantRepository tenantRepository, ITenantManager tenantManager, IDataSeeder dataSeeder, IDistributedEventBus distributedEventBus, ILocalEventBus localEventBus) : base(tenantRepository, tenantManager, dataSeeder, distributedEventBus, localEventBus)
        {
        }


        [Authorize(TenantManagementPermissions.Tenants.Create)]
        public override async Task<TenantDto> CreateAsync(TenantCreateDto input)
        {
            var tenant = await TenantManager.CreateAsync(input.Name);
            input.MapExtraPropertiesTo(tenant);

            await TenantRepository.InsertAsync(tenant);

            await CurrentUnitOfWork.SaveChangesAsync();

            Guid id = Guid.Empty;
            if (input.ExtraProperties.TryGetValue("Id", out var idobj))
            {
                if (idobj != null)
                {
                    id = new Guid((string)idobj);
                }
            }
            if (id == Guid.Empty) id = GuidGenerator.Create();


            await DistributedEventBus.PublishAsync(
                new TenantCreatedEto
                {
                    Id = tenant.Id,
                    Name = tenant.Name,
                    Properties =
                    {
                        { "AdminId", id.ToString() },
                        { "AdminEmail", input.AdminEmailAddress },
                        { "AdminPassword", input.AdminPassword }
                    }
                });

            using (CurrentTenant.Change(tenant.Id, tenant.Name))
            {
                //TODO: Handle database creation?
                // TODO: Seeder might be triggered via event handler.
                await DataSeeder.SeedAsync(
                                new DataSeedContext(tenant.Id)
                                    .WithProperty("AdminId", id)
                                    .WithProperty("AdminEmail", input.AdminEmailAddress)
                                    .WithProperty("AdminPassword", input.AdminPassword)
                                );
            }
            

            return ObjectMapper.Map<Tenant, TenantDto>(tenant);
        }
    }
}
  • In the code above we have DataSeeder.SeedAsync( new DataSeedContext(tenant.Id).WithProperty("AdminId", id) ...
    • The description of the new IdentityDataSeeder implementation is here

CustomCreateModal page

  • In the rupbes.tstapp.Web.Host.csproj-project create Pages/Identity/Users-folder
    • In the Pages/TenantManagement/Tenants-folder of the rupbes.tstapp.Auth.Host.csproj-project create CustomCreateModal-razor page
    • Modify CustomCreateModal.cshtml.cs-file as follows
Click to show the code
using Microsoft.AspNetCore.Mvc;
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Volo.Abp.TenantManagement;
using Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants;


namespace rupbes.tstapp.Pages.TenantManagement.Tenants
{
    public class CustomCreateModalModel : CreateModalModel
    {

        [BindProperty]
        public new CustomTenantInfoModel Tenant { get; set; } = null!;

        public CustomCreateModalModel(ITenantAppService tenantAppService) : base(tenantAppService)
        {
        }

        public override Task<IActionResult> OnGetAsync()
        {
            Tenant = new CustomTenantInfoModel();
            return Task.FromResult<IActionResult>(Page());
        }

        public override async Task<IActionResult> OnPostAsync()
        {
            ValidateModel();

            var input = ObjectMapper.Map<TenantInfoModel, TenantCreateDto>(Tenant);
            input.ExtraProperties.Add("Id", Tenant.Id.ToString());
            await TenantAppService.CreateAsync(input);

            return NoContent();
        }

        public class CustomTenantInfoModel : TenantInfoModel
        {
            [Required]
            public Guid Id { get; set; } = Guid.Empty;
        }

    }
}
  • Modify CustomCreateModal.cshtml-file as follows:
Click to show the code
@page
@using Microsoft.AspNetCore.Mvc.Localization
@using Microsoft.Extensions.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@using Volo.Abp.Localization
@using Volo.Abp.ObjectExtending
@using Volo.Abp.TenantManagement.Localization
@using Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants
@using Volo.Abp.Data
@using Volo.Abp.TenantManagement;
@model rupbes.tstapp.Pages.TenantManagement.Tenants.CustomCreateModalModel
@inject IHtmlLocalizer<AbpTenantManagementResource> L
@inject IStringLocalizerFactory StringLocalizerFactory
@{
    Layout = null;
}
<form method="post" asp-page="/TenantManagement/Tenants/CreateModal">
    <abp-modal>
        <abp-modal-header title="@L["NewTenant"].Value"></abp-modal-header>
        <abp-modal-body>
            <abp-input asp-for="Tenant.Name" />
            <abp-input asp-for="Tenant.Id" />
            <abp-input asp-for="Tenant.AdminEmailAddress" />

            <div class="mb-3">
                <label class="form-label">@L["DisplayName:AdminPassword"].Value</label>
                <span> * </span>
                <div class="input-group">
                    <input type="password" class="form-control" maxlength="@TenantConsts.MaxPasswordLength" asp-for="Tenant.AdminPassword" />
                    <button class="btn btn-secondary" type="button" id="PasswordVisibilityButton">
                        <i class="fa fa-eye-slash" aria-hidden="true"></i>
                    </button>
                </div>
                <span asp-validation-for="Tenant.AdminPassword"></span>
            </div>

            @foreach (var propertyInfo in ObjectExtensionManager.Instance.GetProperties<CreateModalModel.TenantInfoModel>())
            {
                if (!propertyInfo.Name.EndsWith("_Text"))
                {
                    if (propertyInfo.Type.IsEnum || !propertyInfo.Lookup.Url.IsNullOrEmpty())
                    {
                        if (propertyInfo.Type.IsEnum)
                        {
                            Model.Tenant.ExtraProperties.ToEnum(propertyInfo.Name, propertyInfo.Type);
                        }
                        <abp-select asp-for="Tenant.ExtraProperties[propertyInfo.Name]"
                                    label="@propertyInfo.GetLocalizedDisplayName(StringLocalizerFactory)"
                                    autocomplete-api-url="@propertyInfo.Lookup.Url"
                                    autocomplete-selected-item-name="@Model.Tenant.GetProperty(propertyInfo.Name+"_Text")"
                                    autocomplete-selected-item-value="@Model.Tenant.GetProperty(propertyInfo.Name)"
                                    autocomplete-filter-param-name="@propertyInfo.Lookup.FilterParamName"
                                    autocomplete-items-property-name="@propertyInfo.Lookup.ResultListPropertyName"
                                    autocomplete-display-property-name="@propertyInfo.Lookup.DisplayPropertyName"
                                    autocomplete-value-property-name="@propertyInfo.Lookup.ValuePropertyName"></abp-select>
                    }
                    else
                    {
                        <abp-input type="@propertyInfo.GetInputType()"
                                   asp-for="Tenant.ExtraProperties[propertyInfo.Name]"
                                   label="@propertyInfo.GetLocalizedDisplayName(StringLocalizerFactory)"
                                   asp-format="@propertyInfo.GetInputFormatOrNull()"
                                   value="@propertyInfo.GetInputValueOrNull(Model.Tenant.GetProperty(propertyInfo.Name))" />
                    }
                }
            }
        </abp-modal-body>
        <abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer>
    </abp-modal>
</form>
  • rename CustomCreateModal-page. The new name is CreateModal.
  • Start rupbes.tstapp.AuthServer.csproj-app and rupbes.tstapp.Web.Host.csproj-app
    • Login as admin
    • Goto Administration/TenantManagement/Tenants-page
    • Click New Tenant-page.
      • Id-field is avalable for editing
⚠️ **GitHub.com Fallback** ⚠️