417.2‐Multi‐Tenancy of ABP framework applications - chempkovsky/CS82ANGULAR GitHub Wiki
- Please read the article first
- We generated UI for
Volo.Abp.TenantManagement.Tenant-entity - Now we are going to generate code for
Volo.Abp.TenantManagement.TenantConnectionString-entity
- Modify
TenantConnectionString.cs-file of theVolo.Abp.TenantManagement.Domain.csproj-project as follows:
Click to show the code
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Domain.Entities;
namespace Volo.Abp.TenantManagement;
public class TenantConnectionString : Entity
{
#if (!NOTMODELING)
[Description("IsNot_TenantId")]
[Display(Description = "Tenant id", Name = "Id of the Tenant", Prompt = "Enter Tenant Id", ShortName = "Tenant Id")]
#endif
public virtual Guid TenantId { get; protected set; }
#if (!NOTMODELING)
[Display(Description = "Connection String Name", Name = "Name of the Connection String", Prompt = "Enter Connection String Name", ShortName = "Connection String Name")]
[StringLength(64, MinimumLength = 3, ErrorMessage = "Invalid")]
#endif
public virtual string Name { get; protected set; }
#if (!NOTMODELING)
[Display(Description = "Connection String Value", Name = "Value of the Connection String", Prompt = "Enter Connection String Value", ShortName = "Connection String Value")]
[StringLength(1024, MinimumLength = 3, ErrorMessage = "Invalid")]
#endif
public virtual string Value { get; protected set; }
#if (!NOTMODELING)
public Tenant Tnnt { get; set; } = null!;
#endif
protected TenantConnectionString()
{
}
public TenantConnectionString(Guid tenantId, [NotNull] string name, [NotNull] string value)
{
TenantId = tenantId;
Name = Check.NotNullOrWhiteSpace(name, nameof(name), TenantConnectionStringConsts.MaxNameLength);
SetValue(value);
}
public virtual void SetValue([NotNull] string value)
{
Value = Check.NotNullOrWhiteSpace(value, nameof(value), TenantConnectionStringConsts.MaxValueLength);
}
public override object[] GetKeys()
{
return new object[] { TenantId, Name };
}
}
- It is important to note, that at build time (i.e.
<DefineConstants>NOTMODELING</DefineConstants>)TenantConnectionString-class stays unchanged. - It is important to note, that
public Tenant Tnnt { get; set; } = null!;property was added to the class. We make a hint forCS82ANGULARabout one-to-many relation. - It is important to note, that
[Description("IsNot_TenantId")]attribute was added to theTenantId-property of the class- CS82ANGULAR does not analyze the class inheritance, instead it checks the properties with a predefined name like:
-
TenantId,ConcurrencyStampand so on
-
- On the one hand,
TenantConnectionString-class does not implementIMultiTenant-interface, on the other it hasTenantId-property.- using attributes like
[Description("IsNot_TenantId")]or[Description("IsNot_ConcurrencyStamp")](and so on), we make a hint forCS82ANGULARthatTenantIdis not for implementingIMultiTenant-interface
- using attributes like
- CS82ANGULAR does not analyze the class inheritance, instead it checks the properties with a predefined name like:
- Repeat the steps of the 403 Modify DBContext for the first entity (PhoneType).
- With DbContext wizard we define the primary key
Click to show the picture

- With DbContext wizard we define the foreigth key
Click to show the picture

-
OnModelCreatingmust be as follows:
Click to show the code
protected override void OnModelCreating(ModelBuilder builder)
{
#if (!NOTMODELING)
builder.Entity<TenantConnectionString>().HasKey(p => new { p.TenantId, p.Name });
builder.Entity<Tenant>().HasKey(p => p.Id);
builder.Entity<PhbkFile>().HasKey(p => new { p.FileId, p.FlNm });
builder.Entity<PhbkPhone>().HasKey(p => p.Id);
builder.Entity<PhbkEmployee>().HasKey(p => p.Id);
builder.Entity<PhbkDivision>().HasKey(p => p.Id);
builder.Entity<PhbkPhoneType>().HasKey(p => p.Id);
builder.Entity<PhbkEnterprise>().HasKey(p => p.Id);
builder.Entity<PhbkPhoneMtm>().HasKey(p => p.Id);
builder.Entity<PhbkEmpToPhn>().HasKey(p => new { p.EmpIdRef, p.PhnIdRef });
#endif
base.OnModelCreating(builder);
/* Include modules to your migration db context */
builder.ConfigurePermissionManagement();
builder.ConfigureSettingManagement();
builder.ConfigureBackgroundJobs();
builder.ConfigureAuditLogging();
builder.ConfigureFeatureManagement();
builder.ConfigureIdentity();
builder.ConfigureOpenIddict();
builder.ConfigureTenantManagement();
builder.ConfigureBlobStoring();
/* Configure your own tables/entities inside here */
//builder.Entity<YourEntity>(b =>
//{
// b.ToTable(firstappConsts.DbTablePrefix + "YourEntities", firstappConsts.DbSchema);
// b.ConfigureByConvention(); //auto configure for the base class props
// //...
//});
builder.ConfigurefirstappDbContext();
#if (!NOTMODELING)
builder.Entity<PhbkDivision>().HasOne(d => d.Enterprise)
.WithMany(m => m.Divisions)
.HasForeignKey(d => d.EntrprsIdRef)
.HasPrincipalKey(p => p.Id)
.IsRequired(true)
.OnDelete(DeleteBehavior.NoAction);
builder.Entity<PhbkEmployee>().HasOne(d => d.Division)
.WithMany(m => m.Employees)
.HasForeignKey(d => d.DivisionIdRef)
.HasPrincipalKey(p => p.Id)
.IsRequired(true)
.OnDelete(DeleteBehavior.NoAction);
builder.Entity<PhbkPhone>().HasOne(d => d.PhoneType)
.WithMany(m => m.Phones)
.HasForeignKey(d => d.PhoneTypeIdRef)
.HasPrincipalKey(p => p.Id)
.IsRequired(true)
.OnDelete(DeleteBehavior.NoAction);
builder.Entity<PhbkPhone>().HasOne(d => d.Employee)
.WithMany(m => m.Phones)
.HasForeignKey(d => d.EmployeeIdRef)
.HasPrincipalKey(p => p.Id)
.IsRequired(true)
.OnDelete(DeleteBehavior.NoAction);
builder.Entity<PhbkPhoneMtm>().HasOne(d => d.PhoneType)
.WithMany(m => m.PhoneMtms)
.HasForeignKey(d => d.PhoneTypeIdRef)
.HasPrincipalKey(p => p.Id)
.IsRequired(true)
.OnDelete(DeleteBehavior.NoAction);
builder.Entity<PhbkEmpToPhn>().HasOne(d => d.PhoneMtm)
.WithMany(m => m.EmpToPhns)
.HasForeignKey(d => d.PhnIdRef)
.HasPrincipalKey(p => p.Id)
.IsRequired(true)
.OnDelete(DeleteBehavior.NoAction);
builder.Entity<PhbkEmpToPhn>().HasOne(d => d.Employee)
.WithMany(m => m.EmpToPhns)
.HasForeignKey(d => d.EmpIdRef)
.HasPrincipalKey(p => p.Id)
.IsRequired(true)
.OnDelete(DeleteBehavior.NoAction);
builder.Entity<TenantConnectionString>().HasOne(d => d.Tnnt)
.WithMany(m => m.ConnectionStrings)
.HasForeignKey(d => d.TenantId)
.HasPrincipalKey(p => p.Id)
.IsRequired(true)
.OnDelete(DeleteBehavior.NoAction);
#endif
}- Repeat the steps of the 404 First View (PhoneType). Wizard repository
- It is important to note that using default DTO-naming is not the case for
TenantConnectionString-entity. The wizard generatesTenantConnectionStringDto-name. But the calss with the same name is declared in theVolo.Abp.TenantManagement.Application.Contracts.csproj-prject. Even though the class created by our wizard has a different namespace, it is much better if different Dto classes have different names. We changeTenantConnectionStringDtotoTenantConnectionStringExDto. - In the
rupbes.firstapp.Application.Contracts.csproj-rpoject we create theTm-folder and define Dto as follows: - The second page of the wizard will be as follows:
Click to show the picture

- The fourth page of the wizard will be as follows:
Click to show the picture

- We define the foreign key:
Click to show the picture

- Repeat the steps of the 405 First Web Api Service (PhoneType)
- Please note that
-
Use only root props for select method-checkbox is ON. - Even though the TenantManagement module may have a web service for
TenantConnectionString, we generate our own copy of the classes and interfaces: [ITenantConnectionStringExDtoRepo.cs, TenantConnectionStringExDtoRepo.cs, ITenantConnectionStringExDtoService, TenantConnectionStringExDtoService.cs, TenantConnectionStringExDtoWebApiController.cs]
-
Click to show the picture

- It is important to note that in the
updateone-method of theTenantConnectionStringExDtoService-class we had to replace one line of code:- we commented the code
resultEntity.Value = viewToUpdate.Value; - we added
resultEntity.SetValue(viewToUpdate.Value);- in the article we introduce
ChangeVals-method which used to change properties. - Abp developers use
SetXXX()-methods to modify properties - as we mentioned above, we leave Abp-classes to be unchanged at build time
- in the article we introduce
- you do not need to remember of which code you must change. Visual Studio shows it.
- we commented the code
Click to show the code
[Authorize(firstappPermissions.TenantConnectionStringExDto.Edit)]
public async Task<TenantConnectionStringExDto?> updateone(TenantConnectionStringExDto viewToUpdate)
{
// using(repo.EnableTracking())
{
var appQr = (await repo.GetQueryableAsync()) // .TenantConnectionStrings
.Where(p => p.TenantId == viewToUpdate.TenantId)
.Where(p => p.Name == viewToUpdate.Name)
;
TenantConnectionString resultEntity = await AsyncExecuter.FirstOrDefaultAsync(appQr);
if(resultEntity == null) {
return null;
}
//resultEntity.Value = viewToUpdate.Value;
resultEntity.SetValue(viewToUpdate.Value);
await repo.UpdateAsync(resultEntity);
await UnitOfWorkManager.Current.SaveChangesAsync();
var appQrEx = (await repo.GetQueryableAsync()) // .TenantConnectionStrings
.Where(p => p.TenantId == viewToUpdate.TenantId)
.Where(p => p.Name == viewToUpdate.Name)
.Select(itm => new TenantConnectionStringExDto() {
TenantId = itm.TenantId
, Name = itm.Name
, Value = itm.Value
});
return await AsyncExecuter.FirstOrDefaultAsync(appQrEx);
} // using(repo.EnableTracking()) { ... }
}-
Repeat the steps of the 408 Typescript Classes for the First View of ABP framework.
-
Repeat the steps of the 409 Navigation aware typescript components for the First View of ABP framework.
-
UI List properties is as follows:
Click to show the picture

- UI Form properties is as follows:
Click to show the picture

- After generating typescript classes for
TenantConnectionStringwe must to regenerate the following classes forTenant:- services (014400-AbpWebApiService.json-batch)
- rv-form
- rlist-form
- rdlist-form
- this is a scipt (not a batch) 01980-R-lazy.routes.ts/abp.r-lazy.routes.ts.t4
- this is a scipt (not a batch) 02060-Rdl-lazy.routes.ts/abp.rdl-lazy.routes.ts.t4
Here is a result:
Click to show the picture
