Hi, I've deployed boilerplate projects (HttpApi.Host and IdentityServer) to Azure VM.
Both are placed under "Default Web Site" as applications. While I was able to resolve a path issues for HttpApi.Host application and it is displayed properly in browser, I still get a broken markup for IdentityServer application (as shown in the screenshot below): probably, it has to do with incorrect application paths for bundle creation - they are considered for web root folder, not for application folder. Could you please help me to fix this? Are there other places in the solution I might need to check and make the changes for setting correct root folder (i.e. application folder is a root folder, not a web site root folder).
I have not tried to deploy to local IIS server yet - in fact, it's even not set up, because we supposed to use remote DEV machine for running our backend. BTW, when I run this app via IIS Express - I can't see the 'Lepton.Global' bundle loading at all. I've tried to figure out what this bundle was about, playing around AbpBundlingOptions in my IdentityServerModule and hoping to override LeptonThemeBundles.Scripts.Global / LeptonThemeBundles.Styles.Global bundles - but seems like those are not bundle names...
I hope it will be easy... :) Step 1) Create a boilerplate project, as described in the abp doc; Step 2) Create the folder on a remote machine for publishing IdentityServer project, set it up as an IIS application under Default Web Site and assign a separate app pool to it: Step 3) Publish IdentityServer project to that remote machine using the publish profile as below (please replace XXX / YYY / ZZZ and some other dummy values with relevant values):
IdentityServer_to_RemoteMachine.pubxml
<?xml version="1.0" encoding="utf-8"?>
<!--
This file is used by the publish/package process of your Web project. You can customize the behavior of this process
by editing this MSBuild file. In order to learn more about this please visit https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<WebPublishMethod>MSDeploy</WebPublishMethod>
<PublishProvider>AzureVirtualMachine</PublishProvider>
<LastUsedBuildConfiguration>Debug</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<SiteUrlToLaunchAfterPublish>http://XXX.cloudapp.azure.com/YYY.IdentityServer</SiteUrlToLaunchAfterPublish>
<LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
<ExcludeApp_Data>False</ExcludeApp_Data>
<ProjectGuid>073c361e-b8f4-49f5-93cc-72a3ff49c026</ProjectGuid>
<MSDeployServiceURL>XXX.cloudapp.azure.com:8172</MSDeployServiceURL>
<DeployIisAppPath>Default Web Site\YYY.IdentityServer</DeployIisAppPath>
<RemoteSitePhysicalPath />
<SkipExtraFilesOnServer>False</SkipExtraFilesOnServer>
<MSDeployPublishMethod>WMSVC</MSDeployPublishMethod>
<EnableMSDeployBackup>True</EnableMSDeployBackup>
<UserName>YOUR_USERNAME</UserName>
<_SavePWD>True</_SavePWD>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>false</SelfContained>
</PropertyGroup>
</Project>
IdentityServer_to_RemoteMachine.pubxml.user
<?xml version="1.0" encoding="utf-8"?>
<!--
This file is used by the publish/package process of your Web project. You can customize the behavior of this process
by editing this MSBuild file. In order to learn more about this please visit https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<TimeStampOfAssociatedLegacyPublishXmlFile />
<EncryptedPassword>YOUR_ENCRYPTEDPASSWORD</EncryptedPassword>
</PropertyGroup>
<ItemGroup>
<DestinationConnectionStrings Include="Default">
<Value>YOUR_CONNECTIONSTRING</Value>
</DestinationConnectionStrings>
</ItemGroup>
</Project>
Step 3) When the published IdentityServer website is being run, you (hopefully) will see the same broken UI as reported by me earlier;
Hi,
we are not planning to use DB migration for our project tables - we have predefined DB tables structure, which will be changed by applying SQL scripts and the code just must be in-sync with it. At the same time, we understand DB migration mechanism is used in ABP Framework solution to create default tables (ABP[XXX], IDENTITYSERVER[XXX]) - so when ABP Framework gets updated, these tables might be updated accordingly.
Could you please suggest the best approach to use in the solution? Is it possible to go without DbMigration-related projects at all? Or it needs to be some selective migration? How to set it up?
I've tried that (I used EntityFrameworkCore project as a base and added the Microsoft.EntityFrameworkCore.Design reference as asked by the tool). After running migration tool, I'm getting
Unable to create an object of type 'XXXXDbContext' //this is the class inherited from AbpDbContext<XXXXDbContext>
I see now... But is dotnet-ef tool the same thing as Microsoft.EntityFrameworkCore.Tools package? So basically I can use PM commands add-migration / script-migration -from 0 and so on instead? I just need to leave only ABP-related tables inside CentralToolsMigrationsDbContext.OnModelCreating method.
I wonder why UpdateAsync returns INPUT data, even if the input data has been changed? For instance, I have the space truncating rule for my entity:
b.Property(x => x.Domain)
.HasConversion(new ValueConverter<string, string>(v => v.Trim(), v => v.Trim()))
.HasColumnName("C_DOMAIN").HasMaxLength(DbConsts.DomainMaxLength);
Despite this fact, the method returns INPUT data which does not have truncation:
public async Task<ModuleDto> UpdateAsync(ModuleKeyDto id, UpdateModuleDto input)
{
var module = await _moduleRepository.GetAsync(m => m.ApplicationId == id.ApplicationId && m.ModuleId == id.ModuleId);
if (module == null)
{
throw new UserFriendlyException("Module not found", ErrorCodes.NotFound);
}
ObjectMapper.Map(input, module);
var updatedModule = await _moduleRepository.UpdateAsync(module); // updatedModule container non-truncated value that was present in module!!!
await CurrentUnitOfWork.SaveChangesAsync();
return ObjectMapper.Map<Module, ModuleDto>(updatedModule);
}
Looks like a bug? I can re-read the entity and it then will look allright, but don't want to make an extra trip to DB.
OK, thanks, now I know the property values not changed, but parameter values instead. But what do you mean by DTO normalization? Carry on some operations on DTOs? But if so - it's easier just to re-read the entity to get correct up-to-date values.
Saying honestly, not a very good solution, imho. Usually DTO is just a bunch of fields and should not contain some functionality. Handling all the logic in OnModelCreating looks fine and logical. So - since it's not possible to get new data straight from update object - I think I will have to re-read the entity.
Hi. To extend the existing ABP tenants / users functionality in the system, we had to create corresponding tables / entities which relate 1:1 to ABP one:
//Our Tenant class
using AbpTenant = Volo.Saas.Tenant;
namespace XXX.Tenants {
public class Tenant : LogEntity {
...
public Guid AbpId { get; set; }
public AbpTenant AbpTenant { get; set; }`
...
//OnModelCreating
...
builder.Entity<Tenant>()
.HasOne(x => x.AbpTenant)
.WithOne()
.HasPrincipalKey<Volo.Saas.Tenant>(x => x.Id)
.HasForeignKey<Tenant>(x => x.AbpId);
...
builder.Entity<Tenant>(b => {
b.ToTable("OUR_TENANT");
b.ConfigureByConvention();
b.HasKey(x => x.Id);
b.Property(x => x.Id).HasColumnName("C_TENANT").IsRequired().ValueGeneratedNever();
...
b.Property(x => x.AbpId).HasColumnName("C_ABP_TENANT").IsRequired();
...
We had to create own client-side infrastructure in Angular app as well, to process these composite entities:
//Angular Tenants model
export interface State {
tenants: Response;
tenantsLookup: Common.LookupResponse<number>;
}
export interface Response {
items: Tenants.TenantWithNavigationProperties[];
totalCount: number;
}
export interface TenantsQueryParams extends ABP.PageQueryParams {
filterText?: string;
idMin?: number;
idMax?: number;
shortName?: string;
fullName?: string;
companyId?: number;
masterId?: number;
abpId?: string;
isMaster?: boolean;
}
export interface AbpTenant {
id: string;
name: string;
editionId: string;
}
export interface AbpTenantCreateDto {
name: string;
editionId: string;
adminEmailAddress: string;
adminPassword: string;
}
export interface AbpTenantUpdateDto {
name: string;
editionId: string;
}
export interface TenantWithNavigationProperties {
id: number;
shortName: string;
fullName: string;
comment: string;
companyId: number;
masterId: number;
abpId: string;
isMaster: boolean;
abpTenant: AbpTenant;
}
export interface TenantCreateDto {
id: number;
shortName: string;
fullName: string;
comment: string;
companyId: number;
masterId?: number;
abpId?: string;
isMaster: boolean;
abpTenant: AbpTenantCreateDto;
}
export interface TenantUpdateDto {
id: number;
shortName: string;
fullName: string;
comment: string;
companyId: number;
masterId?: number;
abpId: string;
isMaster: boolean;
abpTenant: AbpTenantUpdateDto;
}
To circumvent potential issues with compatibility, we have decided to handle CRUD operations using two repositories - ABP ITenantRepository
and IIdentityUserRepository
. Unfortunately, it raised a major transaction issue: row lock when trying to create (not tested thouroughly on other operations, but sure the issue exists there as well) a new tenant. We have tried different approaches (including using ITenantAppService
directly instead of ITenantRepository
) to resolve it, but none of them worked:
//using ITenantAppService
using var uow = _unitOfWorkManager.Begin(requiresNew: true, isTransactional: true);
var abpTenantDto = await _abpTenantAppService.CreateAsync(input.AbpTenant);
var tenant = ObjectMapper.Map<CreateTenantDto, Tenant>(input);
tenant.AbpId = abpTenantDto.Id; //causes row lock SOMETIMES
var newTenant = await _tenantRepository.InsertAsync(tenant);
await uow.CompleteAsync(); //this operations hangs SOMETIMES because of row lock
return ObjectMapper.Map<Tenant, TenantDto>(newTenant);
//using ITenantRepository
using var uow = _unitOfWorkManager.Begin(requiresNew: true, isTransactional: true);
var abpTenant = await _abpTenantManager.CreateAsync(input.AbpTenant.Name, input.AbpTenant.EditionId);
input.AbpTenant.MapExtraPropertiesTo(abpTenant);
var newAbpTenant = await _abpTenantRepository.InsertAsync(abpTenant);
var tenant = ObjectMapper.Map<CreateTenantDto, Tenant>(input);
tenant.AbpId = abpTenant.Id; //causes row lock ALWAYS
var newTenant = await _tenantRepository.InsertAsync(tenant);
await uow.CompleteAsync(); //this operations hangs ALWAYS because of row lock
return ObjectMapper.Map<Tenant, TenantDto>(newTenant);
Now we use two separate commits (ABP tenant, then - our tenant) as a workaround (deleting the first entry if first commit failed), which of course is not good at all and is just a temporary solution:
#region ABP tenant commit
using var abpTenantUow = _unitOfWorkManager.Begin(requiresNew: true);
Tenant newTenant = null;
var abpTenant = await _abpTenantManager.CreateAsync(input.AbpTenant.Name, input.AbpTenant.EditionId);
input.AbpTenant.MapExtraPropertiesTo(abpTenant);
var newAbpTenant = await _abpTenantRepository.InsertAsync(abpTenant);
await abpTenantUow.CompleteAsync();
#endregion ABP tenant commit
#region Tenant commit
using var tenantUow = _unitOfWorkManager.Begin(requiresNew: true);
var tenant = ObjectMapper.Map<CreateTenantDto, Tenant>(input);
tenant.AbpId = abpTenant.Id;
newTenant = await _tenantRepository.InsertAsync(tenant);
tenantUow.Failed += async (sender, args) =>
{
using var abpTenantDeleteUow = _unitOfWorkManager.Begin(requiresNew: true);
await _abpTenantRepository.HardDeleteAsync(abpTenant);
await abpTenantDeleteUow.CompleteAsync();
};
await tenantUow.CompleteAsync();
#endregion Tenant commit
Here is the sessions screenshot displaying the row lock when trying to use one transaction:
Could you please help us to resolve transaction issue in the first place and also suggest how to handle two-tenant-approach in the most correct way on both back-end and front-end side?