committed by
GitHub
164 changed files with 4683 additions and 187 deletions
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,20 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.Authorization.Abstractions" Version="$(VoloAbpPackageVersion)" /> |
|||
<PackageReference Include="Volo.Abp.Ddd.Application.Contracts" Version="$(VoloAbpPackageVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.Saas.Domain.Shared\LINGYUN.Abp.Saas.Domain.Shared.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,12 @@ |
|||
using Volo.Abp.Application; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.Authorization; |
|||
|
|||
namespace LINGYUN.Abp.Saas; |
|||
|
|||
[DependsOn(typeof(AbpSaasDomainSharedModule))] |
|||
[DependsOn(typeof(AbpAuthorizationAbstractionsModule))] |
|||
[DependsOn(typeof(AbpDddApplicationContractsModule))] |
|||
public class AbpSaasApplicationContractsModule : AbpModule |
|||
{ |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
namespace LINGYUN.Abp.Saas; |
|||
|
|||
public class AbpSaasRemoteServiceConsts |
|||
{ |
|||
public const string RemoteServiceName = "AbpSaas"; |
|||
|
|||
public const string ModuleName = "saas"; |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
namespace LINGYUN.Abp.Saas.Editions; |
|||
|
|||
public class EditionCreateDto : EditionCreateOrUpdateBase |
|||
{ |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Volo.Abp.ObjectExtending; |
|||
using Volo.Abp.Validation; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Editions; |
|||
|
|||
public abstract class EditionCreateOrUpdateBase : ExtensibleObject |
|||
{ |
|||
[Required] |
|||
[DynamicStringLength(typeof(EditionConsts), nameof(EditionConsts.MaxDisplayNameLength))] |
|||
[Display(Name = "EditionName")] |
|||
public string DisplayName { get; set; } |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Editions; |
|||
|
|||
public class EditionDto : ExtensibleFullAuditedEntityDto<Guid> |
|||
{ |
|||
public string DisplayName { get; set; } |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Editions; |
|||
|
|||
public class EditionGetListInput : PagedAndSortedResultRequestDto |
|||
{ |
|||
public string Filter { get; set; } |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using Volo.Abp.Domain.Entities; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Editions; |
|||
|
|||
public class EditionUpdateDto : EditionCreateOrUpdateBase, IHasConcurrencyStamp |
|||
{ |
|||
public string ConcurrencyStamp { get; set; } |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Editions; |
|||
|
|||
public interface IEditionAppService : |
|||
ICrudAppService< |
|||
EditionDto, |
|||
Guid, |
|||
EditionGetListInput, |
|||
EditionCreateDto, |
|||
EditionUpdateDto> |
|||
{ |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
using LINGYUN.Abp.Saas.Localization; |
|||
using Volo.Abp.Authorization.Permissions; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.Saas; |
|||
|
|||
public class AbpSaasPermissionDefinitionProvider : PermissionDefinitionProvider |
|||
{ |
|||
public override void Define(IPermissionDefinitionContext context) |
|||
{ |
|||
var saasGroup = context.AddGroup(AbpSaasPermissions.GroupName, L("Permission:Saas")); |
|||
|
|||
var editionsPermission = saasGroup.AddPermission(AbpSaasPermissions.Editions.Default, L("Permission:EditionManagement"), multiTenancySide: MultiTenancySides.Host); |
|||
editionsPermission.AddChild(AbpSaasPermissions.Editions.Create, L("Permission:Create"), multiTenancySide: MultiTenancySides.Host); |
|||
editionsPermission.AddChild(AbpSaasPermissions.Editions.Update, L("Permission:Edit"), multiTenancySide: MultiTenancySides.Host); |
|||
editionsPermission.AddChild(AbpSaasPermissions.Editions.Delete, L("Permission:Delete"), multiTenancySide: MultiTenancySides.Host); |
|||
editionsPermission.AddChild(AbpSaasPermissions.Editions.ManageFeatures, L("Permission:ManageFeatures"), multiTenancySide: MultiTenancySides.Host); |
|||
|
|||
var tenantsPermission = saasGroup.AddPermission(AbpSaasPermissions.Tenants.Default, L("Permission:TenantManagement"), multiTenancySide: MultiTenancySides.Host); |
|||
tenantsPermission.AddChild(AbpSaasPermissions.Tenants.Create, L("Permission:Create"), multiTenancySide: MultiTenancySides.Host); |
|||
tenantsPermission.AddChild(AbpSaasPermissions.Tenants.Update, L("Permission:Edit"), multiTenancySide: MultiTenancySides.Host); |
|||
tenantsPermission.AddChild(AbpSaasPermissions.Tenants.Delete, L("Permission:Delete"), multiTenancySide: MultiTenancySides.Host); |
|||
tenantsPermission.AddChild(AbpSaasPermissions.Tenants.ManageFeatures, L("Permission:ManageFeatures"), multiTenancySide: MultiTenancySides.Host); |
|||
tenantsPermission.AddChild(AbpSaasPermissions.Tenants.ManageConnectionStrings, L("Permission:ManageConnectionStrings"), multiTenancySide: MultiTenancySides.Host); |
|||
} |
|||
|
|||
private static LocalizableString L(string name) |
|||
{ |
|||
return LocalizableString.Create<AbpSaasResource>(name); |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
using Volo.Abp.Reflection; |
|||
|
|||
namespace LINGYUN.Abp.Saas; |
|||
|
|||
public static class AbpSaasPermissions |
|||
{ |
|||
public const string GroupName = "AbpSaas"; |
|||
|
|||
public static class Editions |
|||
{ |
|||
public const string Default = GroupName + ".Editions"; |
|||
public const string Create = Default + ".Create"; |
|||
public const string Update = Default + ".Update"; |
|||
public const string Delete = Default + ".Delete"; |
|||
public const string ManageFeatures = Default + ".ManageFeatures"; |
|||
} |
|||
|
|||
public static class Tenants |
|||
{ |
|||
public const string Default = GroupName + ".Tenants"; |
|||
public const string Create = Default + ".Create"; |
|||
public const string Update = Default + ".Update"; |
|||
public const string Delete = Default + ".Delete"; |
|||
public const string ManageFeatures = Default + ".ManageFeatures"; |
|||
public const string ManageConnectionStrings = Default + ".ManageConnectionStrings"; |
|||
} |
|||
|
|||
public static string[] GetAll() |
|||
{ |
|||
return ReflectionHelper.GetPublicConstantsRecursively(typeof(AbpSaasPermissions)); |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Volo.Abp.Validation; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Tenants; |
|||
|
|||
public class TenantConnectionGetByNameInput |
|||
{ |
|||
[Required] |
|||
public Guid Id { get; set; } |
|||
|
|||
[Required] |
|||
[DynamicStringLength(typeof(TenantConnectionStringConsts), nameof(TenantConnectionStringConsts.MaxNameLength))] |
|||
public string Name { get; set; } |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Volo.Abp.Validation; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Tenants; |
|||
|
|||
public class TenantConnectionStringCreateOrUpdate |
|||
{ |
|||
[Required] |
|||
[DynamicStringLength(typeof(TenantConnectionStringConsts), nameof(TenantConnectionStringConsts.MaxNameLength))] |
|||
public string Name { get; set; } |
|||
|
|||
[Required] |
|||
[DynamicStringLength(typeof(TenantConnectionStringConsts), nameof(TenantConnectionStringConsts.MaxValueLength))] |
|||
public string Value { get; set; } |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
namespace LINGYUN.Abp.Saas.Tenants; |
|||
|
|||
public class TenantConnectionStringDto |
|||
{ |
|||
public string Name { get; set; } |
|||
|
|||
public string Value { get; set; } |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Tenants; |
|||
|
|||
public class TenantCreateDto : TenantCreateOrUpdateBase |
|||
{ |
|||
[Required] |
|||
[EmailAddress] |
|||
[MaxLength(256)] |
|||
public string AdminEmailAddress { get; set; } |
|||
|
|||
[Required] |
|||
[MaxLength(128)] |
|||
public string AdminPassword { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 使用共享数据库
|
|||
/// </summary>
|
|||
public bool UseSharedDatabase { get; set; } = true; |
|||
|
|||
/// <summary>
|
|||
/// 默认数据库连接字符串
|
|||
/// </summary>
|
|||
public string DefaultConnectionString { get; set; } |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using System; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Volo.Abp.ObjectExtending; |
|||
using Volo.Abp.Validation; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Tenants; |
|||
|
|||
public abstract class TenantCreateOrUpdateBase : ExtensibleObject |
|||
{ |
|||
[Required] |
|||
[DynamicStringLength(typeof(TenantConsts), nameof(TenantConsts.MaxNameLength))] |
|||
|
|||
public string Name { get; set; } |
|||
|
|||
public bool IsActive { get; set; } = true; |
|||
|
|||
public Guid? EditionId { get; set; } |
|||
|
|||
public DateTime? EnableTime { get; set; } |
|||
|
|||
public DateTime? DisableTime { get; set; } |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Tenants; |
|||
|
|||
public class TenantDto : ExtensibleFullAuditedEntityDto<Guid> |
|||
{ |
|||
public string Name { get; set; } |
|||
|
|||
public Guid? EditionId { get; set; } |
|||
|
|||
public string EditionName { get; set; } |
|||
|
|||
public bool IsActive { get; set; } |
|||
|
|||
public DateTime? EnableTime { get; set; } |
|||
|
|||
public DateTime? DisableTime { get; set; } |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Volo.Abp.Validation; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Tenants; |
|||
|
|||
public class TenantGetByNameInput |
|||
{ |
|||
[Required] |
|||
[DynamicStringLength(typeof(TenantConsts), nameof(TenantConsts.MaxNameLength))] |
|||
public string Name { get; set; } |
|||
|
|||
public TenantGetByNameInput() { } |
|||
public TenantGetByNameInput(string name) |
|||
{ |
|||
Name = name; |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Tenants; |
|||
|
|||
public class TenantGetListInput : PagedAndSortedResultRequestDto |
|||
{ |
|||
public string Filter { get; set; } |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
using Volo.Abp.Domain.Entities; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Tenants; |
|||
public class TenantUpdateDto : TenantCreateOrUpdateBase, IHasConcurrencyStamp |
|||
{ |
|||
public string ConcurrencyStamp { get; set; } |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
using System; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Tenants; |
|||
|
|||
public interface ITenantAppService : |
|||
ICrudAppService< |
|||
TenantDto, |
|||
Guid, |
|||
TenantGetListInput, |
|||
TenantCreateDto, |
|||
TenantUpdateDto> |
|||
{ |
|||
Task<TenantDto> GetAsync( |
|||
[Required] string name); |
|||
|
|||
Task<TenantConnectionStringDto> GetConnectionStringAsync( |
|||
Guid id, |
|||
[Required] string connectionName); |
|||
|
|||
Task<ListResultDto<TenantConnectionStringDto>> GetConnectionStringAsync( |
|||
Guid id); |
|||
|
|||
Task<TenantConnectionStringDto> SetConnectionStringAsync( |
|||
Guid id, |
|||
TenantConnectionStringCreateOrUpdate input); |
|||
|
|||
Task DeleteConnectionStringAsync( |
|||
Guid id, |
|||
[Required] string connectionName); |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,20 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.Ddd.Application" Version="$(VoloAbpPackageVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.Saas.Application.Contracts\LINGYUN.Abp.Saas.Application.Contracts.csproj" /> |
|||
<ProjectReference Include="..\LINGYUN.Abp.Saas.Domain\LINGYUN.Abp.Saas.Domain.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,12 @@ |
|||
using LINGYUN.Abp.Saas.Localization; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace LINGYUN.Abp.Saas; |
|||
public abstract class AbpSaasAppServiceBase : ApplicationService |
|||
{ |
|||
protected AbpSaasAppServiceBase() |
|||
{ |
|||
ObjectMapperContext = typeof(AbpSaasApplicationModule); |
|||
LocalizationResource = typeof(AbpSaasResource); |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
using AutoMapper; |
|||
using LINGYUN.Abp.Saas.Editions; |
|||
using LINGYUN.Abp.Saas.Tenants; |
|||
|
|||
namespace LINGYUN.Abp.Saas; |
|||
|
|||
public class AbpSaasApplicationAutoMapperProfile : Profile |
|||
{ |
|||
public AbpSaasApplicationAutoMapperProfile() |
|||
{ |
|||
CreateMap<TenantConnectionString, TenantConnectionStringDto>(); |
|||
|
|||
CreateMap<Tenant, TenantDto>() |
|||
.ForMember(dto => dto.EditionId, map => |
|||
{ |
|||
map.MapFrom((tenant, dto) => |
|||
{ |
|||
return tenant.Edition?.Id; |
|||
}); |
|||
}) |
|||
.ForMember(dto => dto.EditionName, map => |
|||
{ |
|||
map.MapFrom((tenant, dto) => |
|||
{ |
|||
return tenant.Edition?.DisplayName; |
|||
}); |
|||
}) |
|||
.MapExtraProperties(); |
|||
|
|||
CreateMap<Edition, EditionDto>() |
|||
.MapExtraProperties(); |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.AutoMapper; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.Saas; |
|||
|
|||
[DependsOn(typeof(AbpSaasDomainModule))] |
|||
[DependsOn(typeof(AbpSaasApplicationContractsModule))] |
|||
public class AbpSaasApplicationModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddAutoMapperObjectMapper<AbpSaasApplicationModule>(); |
|||
Configure<AbpAutoMapperOptions>(options => |
|||
{ |
|||
options.AddProfile<AbpSaasApplicationAutoMapperProfile>(validate: true); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,87 @@ |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.ObjectExtending; |
|||
using Volo.Abp.Data; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Editions; |
|||
|
|||
[Authorize(AbpSaasPermissions.Editions.Default)] |
|||
public class EditionAppService : AbpSaasAppServiceBase, IEditionAppService |
|||
{ |
|||
protected EditionManager EditionManager { get; } |
|||
protected IEditionRepository EditionRepository { get; } |
|||
|
|||
public EditionAppService( |
|||
EditionManager editionManager, |
|||
IEditionRepository editionRepository) |
|||
{ |
|||
EditionManager = editionManager; |
|||
EditionRepository = editionRepository; |
|||
} |
|||
|
|||
[Authorize(AbpSaasPermissions.Editions.Create)] |
|||
public async virtual Task<EditionDto> CreateAsync(EditionCreateDto input) |
|||
{ |
|||
var edition = await EditionManager.CreateAsync(input.DisplayName); |
|||
input.MapExtraPropertiesTo(edition); |
|||
|
|||
await EditionRepository.InsertAsync(edition); |
|||
|
|||
await CurrentUnitOfWork.SaveChangesAsync(); |
|||
|
|||
return ObjectMapper.Map<Edition, EditionDto>(edition); |
|||
} |
|||
|
|||
[Authorize(AbpSaasPermissions.Editions.Delete)] |
|||
public async virtual Task DeleteAsync(Guid id) |
|||
{ |
|||
var edition = await EditionRepository.GetAsync(id); |
|||
|
|||
await EditionManager.DeleteAsync(edition); |
|||
} |
|||
|
|||
public async virtual Task<EditionDto> GetAsync(Guid id) |
|||
{ |
|||
var edition = await EditionRepository.GetAsync(id, false); |
|||
|
|||
return ObjectMapper.Map<Edition, EditionDto>(edition); |
|||
} |
|||
|
|||
public async virtual Task<PagedResultDto<EditionDto>> GetListAsync(EditionGetListInput input) |
|||
{ |
|||
var totalCount = await EditionRepository.GetCountAsync(input.Filter); |
|||
var editions = await EditionRepository.GetListAsync( |
|||
input.Sorting, |
|||
input.MaxResultCount, |
|||
input.SkipCount, |
|||
input.Filter |
|||
); |
|||
|
|||
return new PagedResultDto<EditionDto>( |
|||
totalCount, |
|||
ObjectMapper.Map<List<Edition>, List<EditionDto>>(editions) |
|||
); |
|||
} |
|||
|
|||
[Authorize(AbpSaasPermissions.Editions.Update)] |
|||
public async virtual Task<EditionDto> UpdateAsync(Guid id, EditionUpdateDto input) |
|||
{ |
|||
var edition = await EditionRepository.GetAsync(id, false); |
|||
|
|||
if (!string.Equals(edition.DisplayName, input.DisplayName)) |
|||
{ |
|||
await EditionManager.ChangeDisplayNameAsync(edition, input.DisplayName); |
|||
} |
|||
edition.SetConcurrencyStampIfNotNull(input.ConcurrencyStamp); |
|||
input.MapExtraPropertiesTo(edition); |
|||
|
|||
await EditionRepository.UpdateAsync(edition); |
|||
|
|||
await CurrentUnitOfWork.SaveChangesAsync(); |
|||
|
|||
return ObjectMapper.Map<Edition, EditionDto>(edition); |
|||
} |
|||
} |
|||
@ -0,0 +1,208 @@ |
|||
using LINGYUN.Abp.MultiTenancy; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
using Volo.Abp.ObjectExtending; |
|||
using Volo.Abp.Domain.Entities; |
|||
using Volo.Abp.Data; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Tenants; |
|||
|
|||
[Authorize(AbpSaasPermissions.Tenants.Default)] |
|||
public class TenantAppService : AbpSaasAppServiceBase, ITenantAppService |
|||
{ |
|||
protected IDistributedEventBus EventBus { get; } |
|||
protected ITenantRepository TenantRepository { get; } |
|||
protected ITenantManager TenantManager { get; } |
|||
|
|||
public TenantAppService( |
|||
ITenantRepository tenantRepository, |
|||
ITenantManager tenantManager, |
|||
IDistributedEventBus eventBus) |
|||
{ |
|||
EventBus = eventBus; |
|||
TenantRepository = tenantRepository; |
|||
TenantManager = tenantManager; |
|||
} |
|||
|
|||
public virtual async Task<TenantDto> GetAsync(Guid id) |
|||
{ |
|||
var tenant = await TenantRepository.FindAsync(id, false); |
|||
if (tenant == null) |
|||
{ |
|||
throw new UserFriendlyException(L["TenantNotFoundById", id]); |
|||
} |
|||
|
|||
return ObjectMapper.Map<Tenant, TenantDto>(tenant); |
|||
} |
|||
|
|||
public virtual async Task<TenantDto> GetAsync(string name) |
|||
{ |
|||
var tenant = await TenantRepository.FindByNameAsync(name, false); |
|||
if (tenant == null) |
|||
{ |
|||
throw new UserFriendlyException(L["TenantNotFoundByName", name]); |
|||
} |
|||
return ObjectMapper.Map<Tenant, TenantDto>(tenant); |
|||
} |
|||
|
|||
public virtual async Task<PagedResultDto<TenantDto>> GetListAsync(TenantGetListInput input) |
|||
{ |
|||
var count = await TenantRepository.GetCountAsync(input.Filter); |
|||
var list = await TenantRepository.GetListAsync( |
|||
input.Sorting, |
|||
input.MaxResultCount, |
|||
input.SkipCount, |
|||
input.Filter |
|||
); |
|||
|
|||
return new PagedResultDto<TenantDto>( |
|||
count, |
|||
ObjectMapper.Map<List<Tenant>, List<TenantDto>>(list) |
|||
); |
|||
} |
|||
|
|||
[Authorize(AbpSaasPermissions.Tenants.Create)] |
|||
public virtual async Task<TenantDto> CreateAsync(TenantCreateDto input) |
|||
{ |
|||
var tenant = await TenantManager.CreateAsync(input.Name); |
|||
tenant.IsActive = input.IsActive; |
|||
tenant.EditionId = input.EditionId; |
|||
tenant.SetEnableTime(input.EnableTime); |
|||
tenant.SetDisableTime(input.DisableTime); |
|||
input.MapExtraPropertiesTo(tenant); |
|||
|
|||
if (!input.UseSharedDatabase && !input.DefaultConnectionString.IsNullOrWhiteSpace()) |
|||
{ |
|||
tenant.SetDefaultConnectionString(input.DefaultConnectionString); |
|||
} |
|||
|
|||
await TenantRepository.InsertAsync(tenant); |
|||
|
|||
CurrentUnitOfWork.OnCompleted(async () => |
|||
{ |
|||
var createEventData = new CreateEventData |
|||
{ |
|||
Id = tenant.Id, |
|||
Name = tenant.Name, |
|||
AdminUserId = GuidGenerator.Create(), |
|||
AdminEmailAddress = input.AdminEmailAddress, |
|||
AdminPassword = input.AdminPassword |
|||
}; |
|||
// 因为项目各自独立,租户增加时添加管理用户必须通过事件总线
|
|||
// 而 TenantEto 对象没有包含所需的用户名密码,需要独立发布事件
|
|||
await EventBus.PublishAsync(createEventData); |
|||
}); |
|||
|
|||
return ObjectMapper.Map<Tenant, TenantDto>(tenant); |
|||
} |
|||
|
|||
[Authorize(AbpSaasPermissions.Tenants.Update)] |
|||
public virtual async Task<TenantDto> UpdateAsync(Guid id, TenantUpdateDto input) |
|||
{ |
|||
var tenant = await TenantRepository.GetAsync(id, false); |
|||
|
|||
if (!string.Equals(tenant.Name, input.Name)) |
|||
{ |
|||
await TenantManager.ChangeNameAsync(tenant, input.Name); |
|||
} |
|||
|
|||
tenant.IsActive = input.IsActive; |
|||
tenant.EditionId = input.EditionId; |
|||
tenant.SetEnableTime(input.EnableTime); |
|||
tenant.SetDisableTime(input.DisableTime); |
|||
tenant.SetConcurrencyStampIfNotNull(input.ConcurrencyStamp); |
|||
input.MapExtraPropertiesTo(tenant); |
|||
await TenantRepository.UpdateAsync(tenant); |
|||
|
|||
return ObjectMapper.Map<Tenant, TenantDto>(tenant); |
|||
} |
|||
|
|||
[Authorize(AbpSaasPermissions.Tenants.Delete)] |
|||
public virtual async Task DeleteAsync(Guid id) |
|||
{ |
|||
var tenant = await TenantRepository.FindAsync(id); |
|||
if (tenant == null) |
|||
{ |
|||
return; |
|||
} |
|||
await TenantRepository.DeleteAsync(tenant); |
|||
} |
|||
|
|||
[Authorize(AbpSaasPermissions.Tenants.ManageConnectionStrings)] |
|||
public virtual async Task<TenantConnectionStringDto> GetConnectionStringAsync(Guid id, string name) |
|||
{ |
|||
var tenant = await TenantRepository.GetAsync(id); |
|||
|
|||
var tenantConnectionString = tenant.FindConnectionString(name); |
|||
|
|||
return new TenantConnectionStringDto |
|||
{ |
|||
Name = name, |
|||
Value = tenantConnectionString |
|||
}; |
|||
} |
|||
|
|||
[Authorize(AbpSaasPermissions.Tenants.ManageConnectionStrings)] |
|||
public virtual async Task<ListResultDto<TenantConnectionStringDto>> GetConnectionStringAsync(Guid id) |
|||
{ |
|||
var tenant = await TenantRepository.GetAsync(id); |
|||
|
|||
return new ListResultDto<TenantConnectionStringDto>( |
|||
ObjectMapper.Map<List<TenantConnectionString>, List<TenantConnectionStringDto>>(tenant.ConnectionStrings.ToList())); |
|||
} |
|||
|
|||
[Authorize(AbpSaasPermissions.Tenants.ManageConnectionStrings)] |
|||
public virtual async Task<TenantConnectionStringDto> SetConnectionStringAsync(Guid id, TenantConnectionStringCreateOrUpdate input) |
|||
{ |
|||
var tenant = await TenantRepository.GetAsync(id); |
|||
if (tenant.FindConnectionString(input.Name) == null) |
|||
{ |
|||
CurrentUnitOfWork.OnCompleted(async () => |
|||
{ |
|||
var eventData = new ConnectionStringCreatedEventData |
|||
{ |
|||
TenantId = tenant.Id, |
|||
TenantName = tenant.Name, |
|||
Name = input.Name |
|||
}; |
|||
|
|||
await EventBus.PublishAsync(eventData); |
|||
}); |
|||
} |
|||
tenant.SetConnectionString(input.Name, input.Value); |
|||
|
|||
return new TenantConnectionStringDto |
|||
{ |
|||
Name = input.Name, |
|||
Value = input.Value |
|||
}; |
|||
} |
|||
|
|||
[Authorize(AbpSaasPermissions.Tenants.ManageConnectionStrings)] |
|||
public virtual async Task DeleteConnectionStringAsync(Guid id, string name) |
|||
{ |
|||
var tenant = await TenantRepository.GetAsync(id); |
|||
|
|||
tenant.RemoveConnectionString(name); |
|||
|
|||
CurrentUnitOfWork.OnCompleted(async () => |
|||
{ |
|||
var eventData = new ConnectionStringDeletedEventData |
|||
{ |
|||
TenantId = tenant.Id, |
|||
TenantName = tenant.Name, |
|||
Name = name |
|||
}; |
|||
|
|||
await EventBus.PublishAsync(eventData); |
|||
}); |
|||
|
|||
await TenantRepository.UpdateAsync(tenant); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,23 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<None Remove="LINGYUN\Abp\Saas\Localization\Resources\*.json" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<EmbeddedResource Include="LINGYUN\Abp\Saas\Localization\Resources\*.json" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.Validation" Version="$(VoloAbpPackageVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,34 @@ |
|||
using LINGYUN.Abp.Saas.Localization; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Localization.ExceptionHandling; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.Validation; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace LINGYUN.Abp.Saas; |
|||
|
|||
[DependsOn(typeof(AbpValidationModule))] |
|||
public class AbpSaasDomainSharedModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<AbpSaasDomainSharedModule>(); |
|||
}); |
|||
|
|||
Configure<AbpLocalizationOptions>(options => |
|||
{ |
|||
options.Resources |
|||
.Add<AbpSaasResource>("en") |
|||
.AddVirtualJson("/LINGYUN/Abp/Saas/Localization/Resources"); |
|||
}); |
|||
|
|||
Configure<AbpExceptionLocalizationOptions>(options => |
|||
{ |
|||
options.MapCodeNamespace(AbpSaasErrorCodes.Namespace, typeof(AbpSaasResource)); |
|||
// 见租户管理模块
|
|||
options.MapCodeNamespace("Volo.AbpIo.MultiTenancy", typeof(AbpSaasResource)); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
namespace LINGYUN.Abp.Saas; |
|||
|
|||
public static class AbpSaasErrorCodes |
|||
{ |
|||
public const string Namespace = "Saas"; |
|||
|
|||
public const string DuplicateEditionDisplayName = Namespace + ":010001"; |
|||
public const string DeleteUsedEdition = Namespace + ":010002"; |
|||
public const string DuplicateTenantName = Namespace + ":020001"; |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
namespace LINGYUN.Abp.Saas.Editions; |
|||
|
|||
public static class EditionConsts |
|||
{ |
|||
public static int MaxDisplayNameLength { get; set; } = 64; |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Editions; |
|||
|
|||
[Serializable] |
|||
public class EditionEto |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
public string DisplayName { get; set; } |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Localization; |
|||
|
|||
[LocalizationResourceName("AbpSaas")] |
|||
public class AbpSaasResource |
|||
{ |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
{ |
|||
"culture": "en", |
|||
"texts": { |
|||
"Saas:010001": "Unable to create duplicate editions {DisplayName}!", |
|||
"Saas:010002": "Tried to delete the edition in use: {DisplayName}!", |
|||
"Saas:020001": "Unable to create duplicate tenants {Name}!", |
|||
"Saas:020002": "The database string that cannot be connected!", |
|||
"Volo.AbpIo.MultiTenancy:010001": "The tenant is unavailable or restricted!", |
|||
"Volo.AbpIo.MultiTenancy:010002": "Tenant unavailable!", |
|||
"Menu:Saas": "Saas", |
|||
"Editions": "Editions", |
|||
"NewEdition": "New edition", |
|||
"EditionName": "Edition name", |
|||
"DisplayName:EditionName": "Edition name", |
|||
"EditionDeletionConfirmationMessage": "Edition '{0}' will be deleted. Do you confirm that?", |
|||
"Tenants": "Tenants", |
|||
"NewTenant": "New tenant", |
|||
"TenantName": "Tenant name", |
|||
"DisplayName:TenantName": "Tenant name", |
|||
"TenantDeletionConfirmationMessage": "Tenant '{0}' will be deleted. Do you confirm that?", |
|||
"ConnectionStrings": "Connection Strings", |
|||
"DisplayName:DefaultConnectionString": "Default Connection String", |
|||
"DisplayName:UseSharedDatabase": "Use the Shared Database", |
|||
"DisplayName:Name": "Name", |
|||
"DisplayName:Value": "Value", |
|||
"DisplayName:IsActive": "Active", |
|||
"DisplayName:EnableTime": "Enable Time", |
|||
"DisplayName:DisableTime": "Disable Time", |
|||
"ManageHostFeatures": "Manage Host features", |
|||
"Permission:Saas": "Saas", |
|||
"Permission:EditionManagement": "Edition management", |
|||
"Permission:TenantManagement": "Tenant management", |
|||
"Permission:Create": "Create", |
|||
"Permission:Edit": "Edit", |
|||
"Permission:Delete": "Delete", |
|||
"Permission:ManageConnectionStrings": "Manage connection strings", |
|||
"Permission:ManageFeatures": "Manage features", |
|||
"DisplayName:AdminEmailAddress": "Admin Email Address", |
|||
"DisplayName:AdminPassword": "Admin Password", |
|||
"TenantNotFoundById": "Tenant: {0} not found!", |
|||
"TenantNotFoundByName": "Tenant: {0} not found!" |
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
{ |
|||
"culture": "zh-Hans", |
|||
"texts": { |
|||
"Saas:010001": "已经存在名为 {DisplayName} 的版本!", |
|||
"Saas:010002": "试图删除正在使用的版本: {DisplayName}!", |
|||
"Saas:020001": "已经存在名为 {Name} 的租户!", |
|||
"Saas:020002": "无法连接的数据库字符串!", |
|||
"Volo.AbpIo.MultiTenancy:010001": "租户不可用或受限制!", |
|||
"Volo.AbpIo.MultiTenancy:010002": "租户不可用!", |
|||
"Menu:Saas": "Saas", |
|||
"Editions": "版本", |
|||
"NewEdition": "新版本", |
|||
"EditionName": "版本名称", |
|||
"DisplayName:EditionName": "版本名称", |
|||
"EditionDeletionConfirmationMessage": "版本 '{0}' 将被删除. 你确定吗?", |
|||
"Tenants": "租户", |
|||
"NewTenant": "新租户", |
|||
"TenantName": "租户名称", |
|||
"DisplayName:TenantName": "租户名称", |
|||
"TenantDeletionConfirmationMessage": "租户 '{0}' 将被删除. 你确定吗?", |
|||
"ConnectionStrings": "连接字符串", |
|||
"DisplayName:DefaultConnectionString": "默认连接字符串", |
|||
"DisplayName:UseSharedDatabase": "使用共享数据库", |
|||
"DisplayName:Name": "名称", |
|||
"DisplayName:Value": "值", |
|||
"DisplayName:IsActive": "启用", |
|||
"DisplayName:EnableTime": "启用时间", |
|||
"DisplayName:DisableTime": "禁用时间", |
|||
"ManageHostFeatures": "管理Host特性", |
|||
"Permission:Saas": "Saas", |
|||
"Permission:EditionManagement": "版本管理", |
|||
"Permission:TenantManagement": "租户管理", |
|||
"Permission:Create": "创建", |
|||
"Permission:Edit": "编辑", |
|||
"Permission:Delete": "删除", |
|||
"Permission:ManageConnectionStrings": "管理连接字符串", |
|||
"Permission:ManageFeatures": "管理功能", |
|||
"DisplayName:AdminEmailAddress": "管理员电子邮件地址", |
|||
"DisplayName:AdminPassword": "管理员密码", |
|||
"TenantNotFoundById": "租户: {0} 不存在!", |
|||
"TenantNotFoundByName": "租户: {0} 不存在!" |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
using System; |
|||
using Volo.Abp.ObjectExtending.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.Saas.ObjectExtending; |
|||
|
|||
public class SaasModuleExtensionConfiguration : ModuleExtensionConfiguration |
|||
{ |
|||
public SaasModuleExtensionConfiguration ConfigureTenant( |
|||
Action<EntityExtensionConfiguration> configureAction) |
|||
{ |
|||
return this.ConfigureEntity( |
|||
SaasModuleExtensionConsts.EntityNames.Edition, |
|||
configureAction |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System; |
|||
using Volo.Abp.ObjectExtending.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.Saas.ObjectExtending; |
|||
|
|||
public static class SaasModuleExtensionConfigurationDictionaryExtensions |
|||
{ |
|||
public static ModuleExtensionConfigurationDictionary ConfigureTenantManagement( |
|||
this ModuleExtensionConfigurationDictionary modules, |
|||
Action<SaasModuleExtensionConfiguration> configureAction) |
|||
{ |
|||
return modules.ConfigureModule( |
|||
SaasModuleExtensionConsts.ModuleName, |
|||
configureAction |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
namespace LINGYUN.Abp.Saas.ObjectExtending; |
|||
|
|||
public class SaasModuleExtensionConsts |
|||
{ |
|||
public const string ModuleName = "Saas"; |
|||
public static class EntityNames |
|||
{ |
|||
public const string Edition = "Edition"; |
|||
public const string Tenant = "Tenant"; |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace LINGYUN.Abp.Saas.Tenants; |
|||
|
|||
public static class TenantConnectionStringConsts |
|||
{ |
|||
public static int MaxNameLength { get; set; } = 64; |
|||
public static int MaxValueLength { get; set; } = 1024; |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
namespace LINGYUN.Abp.Saas.Tenants; |
|||
|
|||
public static class TenantConsts |
|||
{ |
|||
public static int MaxNameLength { get; set; } = 64; |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Tenants; |
|||
|
|||
[Serializable] |
|||
public class TenantEto |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
public string Name { get; set; } |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,23 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.AutoMapper" Version="$(VoloAbpPackageVersion)" /> |
|||
<PackageReference Include="Volo.Abp.Caching" Version="$(VoloAbpPackageVersion)" /> |
|||
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="$(VoloAbpPackageVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\tenants\LINGYUN.Abp.MultiTenancy.Editions\LINGYUN.Abp.MultiTenancy.Editions.csproj" /> |
|||
<ProjectReference Include="..\..\tenants\LINGYUN.Abp.MultiTenancy\LINGYUN.Abp.MultiTenancy.csproj" /> |
|||
<ProjectReference Include="..\LINGYUN.Abp.Saas.Domain.Shared\LINGYUN.Abp.Saas.Domain.Shared.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,12 @@ |
|||
using Volo.Abp.Data; |
|||
|
|||
namespace LINGYUN.Abp.Saas; |
|||
|
|||
public class AbpSaasDbProperties |
|||
{ |
|||
public static string DbTablePrefix { get; set; } = AbpCommonDbProperties.DbTablePrefix; |
|||
|
|||
public static string DbSchema { get; set; } = AbpCommonDbProperties.DbSchema; |
|||
|
|||
public const string ConnectionStringName = "AbpSaas"; |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
using AutoMapper; |
|||
using LINGYUN.Abp.MultiTenancy.Editions; |
|||
using LINGYUN.Abp.Saas.Editions; |
|||
using LINGYUN.Abp.Saas.Tenants; |
|||
using System; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.Saas; |
|||
|
|||
public class AbpSaasDomainMappingProfile : Profile |
|||
{ |
|||
public AbpSaasDomainMappingProfile() |
|||
{ |
|||
CreateMap<Edition, EditionInfo>(); |
|||
CreateMap<Edition, EditionEto>(); |
|||
|
|||
CreateMap<Tenant, TenantConfiguration>() |
|||
.ForMember(ti => ti.ConnectionStrings, opts => |
|||
{ |
|||
opts.MapFrom((tenant, ti) => |
|||
{ |
|||
var connStrings = new ConnectionStrings(); |
|||
|
|||
foreach (var connectionString in tenant.ConnectionStrings) |
|||
{ |
|||
connStrings[connectionString.Name] = connectionString.Value; |
|||
} |
|||
|
|||
return connStrings; |
|||
}); |
|||
}) |
|||
.ForMember(ti => ti.IsActive, opts => |
|||
{ |
|||
opts.MapFrom((tenant, ti) => |
|||
{ |
|||
if (!tenant.IsActive) |
|||
{ |
|||
return false; |
|||
} |
|||
// Injection IClock ?
|
|||
if (tenant.EnableTime.HasValue && DateTime.Now < tenant.EnableTime) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if(tenant.DisableTime.HasValue && DateTime.Now > tenant.DisableTime) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
}); |
|||
}); |
|||
|
|||
CreateMap<Tenant, TenantEto>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
using LINGYUN.Abp.MultiTenancy.Editions; |
|||
using LINGYUN.Abp.Saas.Editions; |
|||
using LINGYUN.Abp.Saas.ObjectExtending; |
|||
using LINGYUN.Abp.Saas.Tenants; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.AutoMapper; |
|||
using Volo.Abp.Domain; |
|||
using Volo.Abp.Domain.Entities.Events.Distributed; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.ObjectExtending.Modularity; |
|||
using Volo.Abp.Threading; |
|||
|
|||
namespace LINGYUN.Abp.Saas; |
|||
|
|||
[DependsOn(typeof(AbpSaasDomainSharedModule))] |
|||
[DependsOn(typeof(AbpAutoMapperModule))] |
|||
[DependsOn(typeof(AbpDddDomainModule))] |
|||
[DependsOn(typeof(AbpMultiTenancyEditionsModule))] |
|||
public class AbpSaasDomainModule : AbpModule |
|||
{ |
|||
private static readonly OneTimeRunner OneTimeRunner = new(); |
|||
|
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddAutoMapperObjectMapper<AbpSaasDomainModule>(); |
|||
|
|||
Configure<AbpAutoMapperOptions>(options => |
|||
{ |
|||
options.AddProfile<AbpSaasDomainMappingProfile>(validate: true); |
|||
}); |
|||
|
|||
Configure<AbpDistributedEntityEventOptions>(options => |
|||
{ |
|||
options.EtoMappings.Add<Edition, EditionEto>(); |
|||
options.EtoMappings.Add<Tenant, TenantEto>(); |
|||
|
|||
options.AutoEventSelectors.Add<Edition>(); |
|||
options.AutoEventSelectors.Add<Tenant>(); |
|||
}); |
|||
} |
|||
|
|||
public override void PostConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
OneTimeRunner.Run(() => |
|||
{ |
|||
ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity( |
|||
SaasModuleExtensionConsts.ModuleName, |
|||
SaasModuleExtensionConsts.EntityNames.Edition, |
|||
typeof(Edition) |
|||
); |
|||
ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity( |
|||
SaasModuleExtensionConsts.ModuleName, |
|||
SaasModuleExtensionConsts.EntityNames.Tenant, |
|||
typeof(Tenant) |
|||
); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
using JetBrains.Annotations; |
|||
using System; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Domain.Entities.Auditing; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Editions; |
|||
|
|||
public class Edition : FullAuditedAggregateRoot<Guid> |
|||
{ |
|||
public virtual string DisplayName { get; protected set; } |
|||
|
|||
protected Edition() |
|||
{ |
|||
} |
|||
|
|||
protected internal Edition(Guid id, [NotNull] string displayName) |
|||
: base(id) |
|||
{ |
|||
SetDisplayName(displayName); |
|||
} |
|||
|
|||
protected internal virtual void SetDisplayName([NotNull] string displayName) |
|||
{ |
|||
DisplayName = Check.NotNullOrWhiteSpace(displayName, nameof(displayName), EditionConsts.MaxDisplayNameLength); |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
using LINGYUN.Abp.MultiTenancy.Editions; |
|||
using System; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Editions; |
|||
|
|||
[Serializable] |
|||
[IgnoreMultiTenancy] |
|||
public class EditionCacheItem |
|||
{ |
|||
private const string CacheKeyFormat = "t:{0}"; |
|||
|
|||
public EditionInfo Value { get; set; } |
|||
|
|||
public EditionCacheItem() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public EditionCacheItem(EditionInfo value) |
|||
{ |
|||
Value = value; |
|||
} |
|||
|
|||
public static string CalculateCacheKey(Guid tenantId) |
|||
{ |
|||
return string.Format(CacheKeyFormat, tenantId.ToString()); |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Guids; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Editions; |
|||
|
|||
public class EditionDataSeeder : IEditionDataSeeder, ITransientDependency |
|||
{ |
|||
protected IGuidGenerator GuidGenerator { get; } |
|||
protected IEditionRepository EditionRepository { get; } |
|||
|
|||
public EditionDataSeeder( |
|||
IGuidGenerator guidGenerator, |
|||
IEditionRepository editionRepository) |
|||
{ |
|||
GuidGenerator = guidGenerator; |
|||
EditionRepository = editionRepository; |
|||
} |
|||
|
|||
public async virtual Task SeedDefaultEditionsAsync() |
|||
{ |
|||
await AddEditionIfNotExistsAsync("Free"); |
|||
await AddEditionIfNotExistsAsync("Standard"); |
|||
await AddEditionIfNotExistsAsync("Professional"); |
|||
await AddEditionIfNotExistsAsync("Enterprise"); |
|||
} |
|||
|
|||
protected async virtual Task AddEditionIfNotExistsAsync(string displayName) |
|||
{ |
|||
if (await EditionRepository.FindByDisplayNameAsync(displayName) != null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
await EditionRepository.InsertAsync( |
|||
new Edition( |
|||
GuidGenerator.Create(), |
|||
displayName |
|||
) |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Domain.Services; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Editions; |
|||
|
|||
public class EditionManager : DomainService |
|||
{ |
|||
protected IEditionRepository EditionRepository { get; } |
|||
|
|||
public EditionManager(IEditionRepository editionRepository) |
|||
{ |
|||
EditionRepository = editionRepository; |
|||
} |
|||
|
|||
public async virtual Task DeleteAsync(Edition edition) |
|||
{ |
|||
if (await EditionRepository.CheckUsedByTenantAsync(edition.Id)) |
|||
{ |
|||
throw new BusinessException(AbpSaasErrorCodes.DeleteUsedEdition) |
|||
.WithData(nameof(Edition.DisplayName), edition.DisplayName); |
|||
} |
|||
await EditionRepository.DeleteAsync(edition); |
|||
} |
|||
|
|||
public async virtual Task<Edition> CreateAsync(string displayName) |
|||
{ |
|||
Check.NotNull(displayName, nameof(displayName)); |
|||
|
|||
await ValidateDisplayNameAsync(displayName); |
|||
return new Edition(GuidGenerator.Create(), displayName); |
|||
} |
|||
|
|||
public virtual async Task ChangeDisplayNameAsync(Edition edition, string displayName) |
|||
{ |
|||
Check.NotNull(edition, nameof(edition)); |
|||
Check.NotNull(displayName, nameof(displayName)); |
|||
|
|||
await ValidateDisplayNameAsync(displayName, edition.Id); |
|||
|
|||
edition.SetDisplayName(displayName); |
|||
} |
|||
|
|||
protected virtual async Task ValidateDisplayNameAsync(string displayName, Guid? expectedId = null) |
|||
{ |
|||
var edition = await EditionRepository.FindByDisplayNameAsync(displayName); |
|||
if (edition != null && edition.Id != expectedId) |
|||
{ |
|||
throw new BusinessException(AbpSaasErrorCodes.DuplicateEditionDisplayName) |
|||
.WithData(nameof(Edition.DisplayName), displayName); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
using JetBrains.Annotations; |
|||
using LINGYUN.Abp.MultiTenancy.Editions; |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.ObjectMapping; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Editions; |
|||
|
|||
public class EditionStore : IEditionStore, ITransientDependency |
|||
{ |
|||
protected IEditionRepository EditionRepository { get; } |
|||
protected IObjectMapper<AbpSaasDomainModule> ObjectMapper { get; } |
|||
protected ICurrentTenant CurrentTenant { get; } |
|||
protected IDistributedCache<EditionCacheItem> Cache { get; } |
|||
|
|||
public EditionStore( |
|||
IEditionRepository editionRepository, |
|||
IObjectMapper<AbpSaasDomainModule> objectMapper, |
|||
ICurrentTenant currentTenant, |
|||
IDistributedCache<EditionCacheItem> cache) |
|||
{ |
|||
EditionRepository = editionRepository; |
|||
ObjectMapper = objectMapper; |
|||
CurrentTenant = currentTenant; |
|||
Cache = cache; |
|||
} |
|||
|
|||
public async virtual Task<EditionInfo> FindByTenantAsync(Guid tenantId) |
|||
{ |
|||
return (await GetCacheItemAsync(tenantId)).Value; |
|||
} |
|||
|
|||
protected async virtual Task<EditionCacheItem> GetCacheItemAsync(Guid tenantId) |
|||
{ |
|||
var cacheKey = CalculateCacheKey(tenantId); |
|||
|
|||
var cacheItem = await Cache.GetAsync(cacheKey, considerUow: true); |
|||
if (cacheItem != null) |
|||
{ |
|||
return cacheItem; |
|||
} |
|||
|
|||
using (CurrentTenant.Change(null)) |
|||
{ |
|||
var edition = await EditionRepository.FindByTenantIdAsync(tenantId); |
|||
return await SetCacheAsync(cacheKey, edition); |
|||
} |
|||
} |
|||
|
|||
protected async virtual Task<EditionCacheItem> SetCacheAsync(string cacheKey, [CanBeNull] Edition edition) |
|||
{ |
|||
var editionInfo = edition != null ? ObjectMapper.Map<Edition, EditionInfo>(edition) : null; |
|||
var cacheItem = new EditionCacheItem(editionInfo); |
|||
await Cache.SetAsync(cacheKey, cacheItem, considerUow: true); |
|||
return cacheItem; |
|||
} |
|||
|
|||
protected virtual string CalculateCacheKey(Guid tenantId) |
|||
{ |
|||
return EditionCacheItem.CalculateCacheKey(tenantId); |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Editions; |
|||
|
|||
public interface IEditionDataSeeder |
|||
{ |
|||
Task SeedDefaultEditionsAsync(); |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Editions; |
|||
|
|||
public interface IEditionRepository : IBasicRepository<Edition, Guid> |
|||
{ |
|||
Task<bool> CheckUsedByTenantAsync( |
|||
Guid id, |
|||
CancellationToken cancellationToken = default); |
|||
|
|||
Task<Edition> FindByDisplayNameAsync( |
|||
string displayName, |
|||
CancellationToken cancellationToken = default); |
|||
|
|||
Task<Edition> FindByTenantIdAsync( |
|||
Guid tenantId, |
|||
CancellationToken cancellationToken = default); |
|||
|
|||
Task<List<Edition>> GetListAsync( |
|||
string sorting = null, |
|||
int maxResultCount = 10, |
|||
int skipCount = 0, |
|||
string filter = null, |
|||
CancellationToken cancellationToken = default); |
|||
|
|||
Task<long> GetCountAsync( |
|||
string filter = null, |
|||
CancellationToken cancellationToken = default); |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp.Domain.Services; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Tenants; |
|||
|
|||
public interface ITenantManager : IDomainService |
|||
{ |
|||
[NotNull] |
|||
Task<Tenant> CreateAsync([NotNull] string name); |
|||
|
|||
Task ChangeNameAsync([NotNull] Tenant tenant, [NotNull] string name); |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Tenants; |
|||
|
|||
public interface ITenantRepository : IBasicRepository<Tenant, Guid> |
|||
{ |
|||
[Obsolete("Use FindByNameAsync method.")] |
|||
Tenant FindByName( |
|||
string name, |
|||
bool includeDetails = true |
|||
); |
|||
|
|||
[Obsolete("Use FindAsync method.")] |
|||
Tenant FindById( |
|||
Guid id, |
|||
bool includeDetails = true |
|||
); |
|||
|
|||
Task<Tenant> FindByNameAsync( |
|||
string name, |
|||
bool includeDetails = true, |
|||
CancellationToken cancellationToken = default); |
|||
|
|||
Task<List<Tenant>> GetListAsync( |
|||
string sorting = null, |
|||
int maxResultCount = int.MaxValue, |
|||
int skipCount = 0, |
|||
string filter = null, |
|||
bool includeDetails = false, |
|||
CancellationToken cancellationToken = default); |
|||
|
|||
Task<long> GetCountAsync( |
|||
string filter = null, |
|||
CancellationToken cancellationToken = default); |
|||
} |
|||
@ -0,0 +1,102 @@ |
|||
using JetBrains.Annotations; |
|||
using LINGYUN.Abp.Saas.Editions; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Collections.ObjectModel; |
|||
using System.Linq; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Domain.Entities.Auditing; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Tenants; |
|||
|
|||
public class Tenant : FullAuditedAggregateRoot<Guid> |
|||
{ |
|||
protected const string DefaultConnectionStringName = Volo.Abp.Data.ConnectionStrings.DefaultConnectionStringName; |
|||
|
|||
public virtual string Name { get; protected set; } |
|||
|
|||
public virtual bool IsActive { get; set; } |
|||
|
|||
public virtual DateTime? EnableTime { get; protected set; } |
|||
|
|||
public virtual DateTime? DisableTime { get; protected set; } |
|||
|
|||
public virtual Guid? EditionId { get; set; } |
|||
public virtual Edition Edition { get; set; } |
|||
|
|||
public virtual ICollection<TenantConnectionString> ConnectionStrings { get; protected set; } |
|||
|
|||
protected Tenant() |
|||
{ |
|||
ConnectionStrings = new Collection<TenantConnectionString>(); |
|||
} |
|||
|
|||
protected internal Tenant(Guid id, [NotNull] string name) |
|||
: base(id) |
|||
{ |
|||
SetName(name); |
|||
|
|||
ConnectionStrings = new Collection<TenantConnectionString>(); |
|||
} |
|||
|
|||
public virtual void SetEnableTime(DateTime? enableTime = null) |
|||
{ |
|||
EnableTime = enableTime; |
|||
} |
|||
|
|||
public virtual void SetDisableTime(DateTime? disableTime = null) |
|||
{ |
|||
DisableTime = disableTime; |
|||
} |
|||
|
|||
[CanBeNull] |
|||
public virtual string FindDefaultConnectionString() |
|||
{ |
|||
return FindConnectionString(DefaultConnectionStringName); |
|||
} |
|||
|
|||
[CanBeNull] |
|||
public virtual string FindConnectionString(string name) |
|||
{ |
|||
return ConnectionStrings.FirstOrDefault(c => c.Name == name)?.Value; |
|||
} |
|||
|
|||
public virtual void SetDefaultConnectionString(string connectionString) |
|||
{ |
|||
SetConnectionString(DefaultConnectionStringName, connectionString); |
|||
} |
|||
|
|||
public virtual void SetConnectionString(string name, string connectionString) |
|||
{ |
|||
var tenantConnectionString = ConnectionStrings.FirstOrDefault(x => x.Name == name); |
|||
|
|||
if (tenantConnectionString != null) |
|||
{ |
|||
tenantConnectionString.SetValue(connectionString); |
|||
} |
|||
else |
|||
{ |
|||
ConnectionStrings.Add(new TenantConnectionString(Id, name, connectionString)); |
|||
} |
|||
} |
|||
|
|||
public virtual void RemoveDefaultConnectionString() |
|||
{ |
|||
RemoveConnectionString(DefaultConnectionStringName); |
|||
} |
|||
|
|||
public virtual void RemoveConnectionString(string name) |
|||
{ |
|||
var tenantConnectionString = ConnectionStrings.FirstOrDefault(x => x.Name == name); |
|||
|
|||
if (tenantConnectionString != null) |
|||
{ |
|||
ConnectionStrings.Remove(tenantConnectionString); |
|||
} |
|||
} |
|||
|
|||
protected internal virtual void SetName([NotNull] string name) |
|||
{ |
|||
Name = Check.NotNullOrWhiteSpace(name, nameof(name), TenantConsts.MaxNameLength); |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
using System; |
|||
using Volo.Abp; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Tenants; |
|||
|
|||
[Serializable] |
|||
[IgnoreMultiTenancy] |
|||
public class TenantCacheItem |
|||
{ |
|||
private const string CacheKeyFormat = "i:{0},n:{1}"; |
|||
|
|||
public TenantConfiguration Value { get; set; } |
|||
|
|||
public TenantCacheItem() |
|||
{ |
|||
} |
|||
|
|||
public TenantCacheItem(TenantConfiguration value) |
|||
{ |
|||
Value = value; |
|||
} |
|||
|
|||
public static string CalculateCacheKey(Guid? id, string name) |
|||
{ |
|||
if (id == null && name.IsNullOrWhiteSpace()) |
|||
{ |
|||
throw new AbpException("Both id and name can't be invalid."); |
|||
} |
|||
|
|||
return string.Format(CacheKeyFormat, |
|||
id?.ToString() ?? "null", |
|||
(name.IsNullOrWhiteSpace() ? "null" : name)); |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using LINGYUN.Abp.Saas.Editions; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Domain.Entities.Events; |
|||
using Volo.Abp.EventBus; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Tenants; |
|||
|
|||
public class TenantCacheItemInvalidator : ILocalEventHandler<EntityChangedEventData<Tenant>>, ITransientDependency |
|||
{ |
|||
protected IDistributedCache<TenantCacheItem> Cache { get; } |
|||
|
|||
public TenantCacheItemInvalidator(IDistributedCache<TenantCacheItem> cache) |
|||
{ |
|||
Cache = cache; |
|||
} |
|||
|
|||
public virtual async Task HandleEventAsync(EntityChangedEventData<Tenant> eventData) |
|||
{ |
|||
await Cache.RemoveAsync(TenantCacheItem.CalculateCacheKey(eventData.Entity.Id, eventData.Entity.Name), considerUow: true); |
|||
// 同时移除租户版本缓存
|
|||
await Cache.RemoveAsync(EditionCacheItem.CalculateCacheKey(eventData.Entity.Id), considerUow: true); |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
using JetBrains.Annotations; |
|||
using System; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Domain.Entities; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Tenants; |
|||
|
|||
public class TenantConnectionString : Entity |
|||
{ |
|||
public virtual Guid TenantId { get; protected set; } |
|||
|
|||
public virtual string Name { get; protected set; } |
|||
|
|||
public virtual string Value { get; protected set; } |
|||
|
|||
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 }; |
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Domain.Services; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Tenants; |
|||
|
|||
public class TenantManager : DomainService, ITenantManager |
|||
{ |
|||
protected ITenantRepository TenantRepository { get; } |
|||
|
|||
public TenantManager(ITenantRepository tenantRepository) |
|||
{ |
|||
TenantRepository = tenantRepository; |
|||
|
|||
} |
|||
|
|||
public virtual async Task<Tenant> CreateAsync(string name) |
|||
{ |
|||
Check.NotNull(name, nameof(name)); |
|||
|
|||
await ValidateNameAsync(name); |
|||
return new Tenant(GuidGenerator.Create(), name); |
|||
} |
|||
|
|||
public virtual async Task ChangeNameAsync(Tenant tenant, string name) |
|||
{ |
|||
Check.NotNull(tenant, nameof(tenant)); |
|||
Check.NotNull(name, nameof(name)); |
|||
|
|||
await ValidateNameAsync(name, tenant.Id); |
|||
tenant.SetName(name); |
|||
} |
|||
|
|||
protected virtual async Task ValidateNameAsync(string name, Guid? expectedId = null) |
|||
{ |
|||
var tenant = await TenantRepository.FindByNameAsync(name); |
|||
if (tenant != null && tenant.Id != expectedId) |
|||
{ |
|||
throw new BusinessException(AbpSaasErrorCodes.DuplicateTenantName) |
|||
.WithData(nameof(Tenant.Name), name); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,137 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.ObjectMapping; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Tenants; |
|||
|
|||
public class TenantStore : ITenantStore, ITransientDependency |
|||
{ |
|||
protected ITenantRepository TenantRepository { get; } |
|||
protected IObjectMapper<AbpSaasDomainModule> ObjectMapper { get; } |
|||
protected ICurrentTenant CurrentTenant { get; } |
|||
protected IDistributedCache<TenantCacheItem> Cache { get; } |
|||
|
|||
public TenantStore( |
|||
ITenantRepository tenantRepository, |
|||
IObjectMapper<AbpSaasDomainModule> objectMapper, |
|||
ICurrentTenant currentTenant, |
|||
IDistributedCache<TenantCacheItem> cache) |
|||
{ |
|||
TenantRepository = tenantRepository; |
|||
ObjectMapper = objectMapper; |
|||
CurrentTenant = currentTenant; |
|||
Cache = cache; |
|||
} |
|||
|
|||
public virtual async Task<TenantConfiguration> FindAsync(string name) |
|||
{ |
|||
return (await GetCacheItemAsync(null, name)).Value; |
|||
} |
|||
|
|||
public virtual async Task<TenantConfiguration> FindAsync(Guid id) |
|||
{ |
|||
return (await GetCacheItemAsync(id, null)).Value; |
|||
} |
|||
|
|||
[Obsolete("Use FindAsync method.")] |
|||
public virtual TenantConfiguration Find(string name) |
|||
{ |
|||
return (GetCacheItem(null, name)).Value; |
|||
} |
|||
|
|||
[Obsolete("Use FindAsync method.")] |
|||
public virtual TenantConfiguration Find(Guid id) |
|||
{ |
|||
return (GetCacheItem(id, null)).Value; |
|||
} |
|||
|
|||
protected virtual async Task<TenantCacheItem> GetCacheItemAsync(Guid? id, string name) |
|||
{ |
|||
var cacheKey = CalculateCacheKey(id, name); |
|||
|
|||
var cacheItem = await Cache.GetAsync(cacheKey, considerUow: true); |
|||
if (cacheItem != null) |
|||
{ |
|||
return cacheItem; |
|||
} |
|||
|
|||
if (id.HasValue) |
|||
{ |
|||
using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
|
|||
{ |
|||
var tenant = await TenantRepository.FindAsync(id.Value); |
|||
return await SetCacheAsync(cacheKey, tenant); |
|||
} |
|||
} |
|||
|
|||
if (!name.IsNullOrWhiteSpace()) |
|||
{ |
|||
using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
|
|||
{ |
|||
var tenant = await TenantRepository.FindByNameAsync(name); |
|||
return await SetCacheAsync(cacheKey, tenant); |
|||
} |
|||
} |
|||
|
|||
throw new AbpException("Both id and name can't be invalid."); |
|||
} |
|||
|
|||
protected virtual async Task<TenantCacheItem> SetCacheAsync(string cacheKey, [CanBeNull] Tenant tenant) |
|||
{ |
|||
var tenantConfiguration = tenant != null ? ObjectMapper.Map<Tenant, TenantConfiguration>(tenant) : null; |
|||
var cacheItem = new TenantCacheItem(tenantConfiguration); |
|||
await Cache.SetAsync(cacheKey, cacheItem, considerUow: true); |
|||
return cacheItem; |
|||
} |
|||
|
|||
[Obsolete("Use GetCacheItemAsync method.")] |
|||
protected virtual TenantCacheItem GetCacheItem(Guid? id, string name) |
|||
{ |
|||
var cacheKey = CalculateCacheKey(id, name); |
|||
|
|||
var cacheItem = Cache.Get(cacheKey, considerUow: true); |
|||
if (cacheItem != null) |
|||
{ |
|||
return cacheItem; |
|||
} |
|||
|
|||
if (id.HasValue) |
|||
{ |
|||
using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
|
|||
{ |
|||
var tenant = TenantRepository.FindById(id.Value); |
|||
return SetCache(cacheKey, tenant); |
|||
} |
|||
} |
|||
|
|||
if (!name.IsNullOrWhiteSpace()) |
|||
{ |
|||
using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
|
|||
{ |
|||
var tenant = TenantRepository.FindByName(name); |
|||
return SetCache(cacheKey, tenant); |
|||
} |
|||
} |
|||
|
|||
throw new AbpException("Both id and name can't be invalid."); |
|||
} |
|||
|
|||
[Obsolete("Use SetCacheAsync method.")] |
|||
protected virtual TenantCacheItem SetCache(string cacheKey, [CanBeNull] Tenant tenant) |
|||
{ |
|||
var tenantConfiguration = tenant != null ? ObjectMapper.Map<Tenant, TenantConfiguration>(tenant) : null; |
|||
var cacheItem = new TenantCacheItem(tenantConfiguration); |
|||
Cache.Set(cacheKey, cacheItem, considerUow: true); |
|||
return cacheItem; |
|||
} |
|||
|
|||
protected virtual string CalculateCacheKey(Guid? id, string name) |
|||
{ |
|||
return TenantCacheItem.CalculateCacheKey(id, name); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,19 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.EntityFrameworkCore" Version="$(VoloAbpPackageVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.Saas.Domain\LINGYUN.Abp.Saas.Domain.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,67 @@ |
|||
using LINGYUN.Abp.Saas.Editions; |
|||
using LINGYUN.Abp.Saas.Tenants; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Volo.Abp; |
|||
using Volo.Abp.EntityFrameworkCore.Modeling; |
|||
|
|||
namespace LINGYUN.Abp.Saas.EntityFrameworkCore; |
|||
|
|||
public static class AbpSaasDbContextModelCreatingExtensions |
|||
{ |
|||
public static void ConfigureSaas( |
|||
this ModelBuilder builder) |
|||
{ |
|||
Check.NotNull(builder, nameof(builder)); |
|||
|
|||
if (builder.IsTenantOnlyDatabase()) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
builder.Entity<Edition>(b => |
|||
{ |
|||
b.ToTable(AbpSaasDbProperties.DbTablePrefix + "Editions", AbpSaasDbProperties.DbSchema); |
|||
|
|||
b.ConfigureByConvention(); |
|||
|
|||
b.Property(t => t.DisplayName) |
|||
.HasMaxLength(EditionConsts.MaxDisplayNameLength) |
|||
.IsRequired(); |
|||
|
|||
b.HasIndex(u => u.DisplayName); |
|||
|
|||
b.ApplyObjectExtensionMappings(); |
|||
}); |
|||
|
|||
builder.Entity<Tenant>(b => |
|||
{ |
|||
b.ToTable(AbpSaasDbProperties.DbTablePrefix + "Tenants", AbpSaasDbProperties.DbSchema); |
|||
|
|||
b.ConfigureByConvention(); |
|||
|
|||
b.Property(t => t.Name).IsRequired().HasMaxLength(TenantConsts.MaxNameLength); |
|||
|
|||
b.HasMany(u => u.ConnectionStrings).WithOne().HasForeignKey(uc => uc.TenantId).IsRequired(); |
|||
|
|||
b.HasIndex(u => u.Name); |
|||
|
|||
b.ApplyObjectExtensionMappings(); |
|||
}); |
|||
|
|||
builder.Entity<TenantConnectionString>(b => |
|||
{ |
|||
b.ToTable(AbpSaasDbProperties.DbTablePrefix + "TenantConnectionStrings", AbpSaasDbProperties.DbSchema); |
|||
|
|||
b.ConfigureByConvention(); |
|||
|
|||
b.HasKey(x => new { x.TenantId, x.Name }); |
|||
|
|||
b.Property(cs => cs.Name).IsRequired().HasMaxLength(TenantConnectionStringConsts.MaxNameLength); |
|||
b.Property(cs => cs.Value).IsRequired().HasMaxLength(TenantConnectionStringConsts.MaxValueLength); |
|||
|
|||
b.ApplyObjectExtensionMappings(); |
|||
}); |
|||
|
|||
builder.TryConfigureObjectExtensions<SaasDbContext>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.EntityFrameworkCore; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.Saas.EntityFrameworkCore; |
|||
|
|||
[DependsOn(typeof(AbpSaasDomainModule))] |
|||
[DependsOn(typeof(AbpEntityFrameworkCoreModule))] |
|||
public class AbpSaasEntityFrameworkCoreModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddAbpDbContext<SaasDbContext>(options => |
|||
{ |
|||
options.AddDefaultRepositories<ISaasDbContext>(); |
|||
}); |
|||
|
|||
Configure<AbpDbConnectionOptions>(options => |
|||
{ |
|||
// 将租户管理连接字符串聚合到 Saas 模块中
|
|||
options.Databases.Configure(AbpSaasDbProperties.ConnectionStringName, |
|||
database => |
|||
{ |
|||
database.MappedConnections.Add("AbpTenantManagement"); |
|||
database.IsUsedByTenants = false; |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,84 @@ |
|||
using LINGYUN.Abp.Saas.Editions; |
|||
using LINGYUN.Abp.Saas.Tenants; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Linq.Dynamic.Core; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; |
|||
using Volo.Abp.EntityFrameworkCore; |
|||
|
|||
namespace LINGYUN.Abp.Saas.EntityFrameworkCore; |
|||
|
|||
public class EfCoreEditionRepository : EfCoreRepository<ISaasDbContext, Edition, Guid>, IEditionRepository |
|||
{ |
|||
public EfCoreEditionRepository( |
|||
IDbContextProvider<ISaasDbContext> dbContextProvider) |
|||
: base(dbContextProvider) |
|||
{ |
|||
} |
|||
|
|||
public async virtual Task<bool> CheckUsedByTenantAsync( |
|||
Guid id, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
var dbContext = await GetDbContextAsync(); |
|||
var tenantDbSet = dbContext.Set<Tenant>(); |
|||
|
|||
return await tenantDbSet |
|||
.AnyAsync(x => x.EditionId == id, GetCancellationToken(cancellationToken)); |
|||
} |
|||
|
|||
public async virtual Task<Edition> FindByDisplayNameAsync( |
|||
string displayName, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
return await (await GetDbSetAsync()) |
|||
.OrderBy(t => t.Id) |
|||
.FirstOrDefaultAsync(t => t.DisplayName == displayName, GetCancellationToken(cancellationToken)); |
|||
} |
|||
|
|||
public async virtual Task<Edition> FindByTenantIdAsync( |
|||
Guid tenantId, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
var dbContext = await GetDbContextAsync(); |
|||
var editionDbSet = dbContext.Set<Edition>(); |
|||
var tenantDbSet = dbContext.Set<Tenant>(); |
|||
|
|||
var queryable = from tenant in tenantDbSet |
|||
join edition in editionDbSet |
|||
on tenant.EditionId equals edition.Id |
|||
where tenant.Id == tenantId |
|||
select edition; |
|||
|
|||
return await queryable |
|||
.OrderBy(t => t.Id) |
|||
.FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); |
|||
} |
|||
|
|||
public async virtual Task<long> GetCountAsync( |
|||
string filter = null, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
return await (await GetDbSetAsync()) |
|||
.WhereIf(!filter.IsNullOrWhiteSpace(), x => x.DisplayName.Contains(filter)) |
|||
.CountAsync(GetCancellationToken(cancellationToken)); |
|||
} |
|||
|
|||
public async virtual Task<List<Edition>> GetListAsync( |
|||
string sorting = null, |
|||
int maxResultCount = 10, |
|||
int skipCount = 0, |
|||
string filter = null, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
return await (await GetDbSetAsync()) |
|||
.WhereIf(!filter.IsNullOrWhiteSpace(), x => x.DisplayName.Contains(filter)) |
|||
.OrderBy(sorting.IsNullOrEmpty() ? nameof(Edition.DisplayName) : sorting) |
|||
.PageBy(skipCount, maxResultCount) |
|||
.ToListAsync(GetCancellationToken(cancellationToken)); |
|||
} |
|||
} |
|||
@ -0,0 +1,226 @@ |
|||
using LINGYUN.Abp.Saas.Editions; |
|||
using LINGYUN.Abp.Saas.Tenants; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Linq.Dynamic.Core; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; |
|||
using Volo.Abp.EntityFrameworkCore; |
|||
|
|||
namespace LINGYUN.Abp.Saas.EntityFrameworkCore; |
|||
|
|||
public class EfCoreTenantRepository : EfCoreRepository<ISaasDbContext, Tenant, Guid>, ITenantRepository |
|||
{ |
|||
public EfCoreTenantRepository(IDbContextProvider<ISaasDbContext> dbContextProvider) |
|||
: base(dbContextProvider) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public async override Task<Tenant> FindAsync(Guid id, bool includeDetails = true, CancellationToken cancellationToken = default) |
|||
{ |
|||
var dbContext = await GetDbContextAsync(); |
|||
var tenantDbSet = dbContext.Set<Tenant>() |
|||
.IncludeDetails(includeDetails); |
|||
|
|||
if (includeDetails) |
|||
{ |
|||
var editionDbSet = dbContext.Set<Edition>(); |
|||
var queryable = from tenant in tenantDbSet |
|||
join edition in editionDbSet on tenant.EditionId equals edition.Id into eg |
|||
from e in eg.DefaultIfEmpty() |
|||
where tenant.Id.Equals(id) |
|||
orderby tenant.Id |
|||
select new |
|||
{ |
|||
Tenant = tenant, |
|||
Edition = e, |
|||
}; |
|||
var result = await queryable |
|||
.FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); |
|||
|
|||
if (result != null && result.Tenant != null) |
|||
{ |
|||
result.Tenant.Edition = result.Edition; |
|||
} |
|||
|
|||
return result?.Tenant; |
|||
} |
|||
|
|||
return await tenantDbSet |
|||
.OrderBy(t => t.Id) |
|||
.FirstOrDefaultAsync(t => t.Id == id, GetCancellationToken(cancellationToken)); |
|||
} |
|||
|
|||
public virtual async Task<Tenant> FindByNameAsync( |
|||
string name, |
|||
bool includeDetails = true, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
var dbContext = await GetDbContextAsync(); |
|||
var tenantDbSet = dbContext.Set<Tenant>() |
|||
.IncludeDetails(includeDetails); |
|||
|
|||
if (includeDetails) |
|||
{ |
|||
var editionDbSet = dbContext.Set<Edition>(); |
|||
var queryable = from tenant in tenantDbSet |
|||
join edition in editionDbSet on tenant.EditionId equals edition.Id into eg |
|||
from e in eg.DefaultIfEmpty() |
|||
where tenant.Name.Equals(name) |
|||
orderby tenant.Id |
|||
select new |
|||
{ |
|||
Tenant = tenant, |
|||
Edition = e, |
|||
}; |
|||
var result = await queryable |
|||
.FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); |
|||
if (result != null && result.Tenant != null) |
|||
{ |
|||
result.Tenant.Edition = result.Edition; |
|||
} |
|||
|
|||
return result?.Tenant; |
|||
} |
|||
|
|||
return await tenantDbSet |
|||
.OrderBy(t => t.Id) |
|||
.FirstOrDefaultAsync(t => t.Name == name, GetCancellationToken(cancellationToken)); |
|||
} |
|||
|
|||
[Obsolete("Use FindByNameAsync method.")] |
|||
public virtual Tenant FindByName(string name, bool includeDetails = true) |
|||
{ |
|||
var tenantDbSet = DbContext.Set<Tenant>() |
|||
.IncludeDetails(includeDetails); |
|||
|
|||
if (includeDetails) |
|||
{ |
|||
var editionDbSet = DbContext.Set<Edition>(); |
|||
var queryable = from tenant in tenantDbSet |
|||
join edition in editionDbSet on tenant.EditionId equals edition.Id into eg |
|||
from e in eg.DefaultIfEmpty() |
|||
where tenant.Name.Equals(name) |
|||
orderby tenant.Id |
|||
select new |
|||
{ |
|||
Tenant = tenant, |
|||
Edition = e, |
|||
}; |
|||
var result = queryable |
|||
.FirstOrDefault(); |
|||
if (result != null && result.Tenant != null) |
|||
{ |
|||
result.Tenant.Edition = result.Edition; |
|||
} |
|||
|
|||
return result?.Tenant; |
|||
} |
|||
|
|||
return tenantDbSet |
|||
.OrderBy(t => t.Id) |
|||
.FirstOrDefault(t => t.Name == name); |
|||
} |
|||
|
|||
[Obsolete("Use FindAsync method.")] |
|||
public virtual Tenant FindById(Guid id, bool includeDetails = true) |
|||
{ |
|||
var tenantDbSet = DbContext.Set<Tenant>() |
|||
.IncludeDetails(includeDetails); |
|||
|
|||
if (includeDetails) |
|||
{ |
|||
var editionDbSet = DbContext.Set<Edition>(); |
|||
var queryable = from tenant in tenantDbSet |
|||
join edition in editionDbSet on tenant.EditionId equals edition.Id into eg |
|||
from e in eg.DefaultIfEmpty() |
|||
where tenant.Id.Equals(id) |
|||
orderby tenant.Id |
|||
select new |
|||
{ |
|||
Tenant = tenant, |
|||
Edition = e, |
|||
}; |
|||
var result = queryable |
|||
.FirstOrDefault(); |
|||
if (result != null && result.Tenant != null) |
|||
{ |
|||
result.Tenant.Edition = result.Edition; |
|||
} |
|||
|
|||
return result?.Tenant; |
|||
} |
|||
|
|||
return tenantDbSet |
|||
.OrderBy(t => t.Id) |
|||
.FirstOrDefault(t => t.Id == id); |
|||
} |
|||
|
|||
public virtual async Task<List<Tenant>> GetListAsync( |
|||
string sorting = null, |
|||
int maxResultCount = int.MaxValue, |
|||
int skipCount = 0, |
|||
string filter = null, |
|||
bool includeDetails = false, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
if (includeDetails) |
|||
{ |
|||
var dbContext = await GetDbContextAsync(); |
|||
var editionDbSet = dbContext.Set<Edition>(); |
|||
var tenantDbSet = dbContext.Set<Tenant>() |
|||
.IncludeDetails(includeDetails); |
|||
|
|||
var queryable = tenantDbSet |
|||
.WhereIf(!filter.IsNullOrWhiteSpace(), u => u.Name.Contains(filter)) |
|||
.OrderBy(sorting.IsNullOrEmpty() ? nameof(Tenant.Name) : sorting); |
|||
|
|||
var combinedResult = await queryable |
|||
.Join( |
|||
editionDbSet, |
|||
o => o.EditionId, |
|||
i => i.Id, |
|||
(tenant, edition) => new { tenant, edition }) |
|||
.Skip(skipCount) |
|||
.Take(maxResultCount) |
|||
.ToListAsync(GetCancellationToken(cancellationToken)); |
|||
|
|||
return combinedResult.Select(s => |
|||
{ |
|||
s.tenant.Edition = s.edition; |
|||
return s.tenant; |
|||
}).ToList(); |
|||
} |
|||
|
|||
return await (await GetDbSetAsync()) |
|||
.WhereIf(!filter.IsNullOrWhiteSpace(), u => u.Name.Contains(filter)) |
|||
.OrderBy(sorting.IsNullOrEmpty() ? nameof(Tenant.Name) : sorting) |
|||
.PageBy(skipCount, maxResultCount) |
|||
.ToListAsync(GetCancellationToken(cancellationToken)); |
|||
} |
|||
|
|||
public virtual async Task<long> GetCountAsync(string filter = null, CancellationToken cancellationToken = default) |
|||
{ |
|||
return await (await GetQueryableAsync()) |
|||
.WhereIf( |
|||
!filter.IsNullOrWhiteSpace(), |
|||
u => |
|||
u.Name.Contains(filter) |
|||
).CountAsync(cancellationToken: cancellationToken); |
|||
} |
|||
|
|||
[Obsolete("Use WithDetailsAsync method.")] |
|||
public override IQueryable<Tenant> WithDetails() |
|||
{ |
|||
return GetQueryable().IncludeDetails(); |
|||
} |
|||
|
|||
public override async Task<IQueryable<Tenant>> WithDetailsAsync() |
|||
{ |
|||
return (await GetQueryableAsync()).IncludeDetails(); |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using LINGYUN.Abp.Saas.Editions; |
|||
using LINGYUN.Abp.Saas.Tenants; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.EntityFrameworkCore; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.Saas.EntityFrameworkCore; |
|||
|
|||
[IgnoreMultiTenancy] |
|||
[ConnectionStringName(AbpSaasDbProperties.ConnectionStringName)] |
|||
public interface ISaasDbContext : IEfCoreDbContext |
|||
{ |
|||
DbSet<Edition> Editions { get; } |
|||
DbSet<Tenant> Tenants { get; } |
|||
DbSet<TenantConnectionString> TenantConnectionStrings { get; } |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
using LINGYUN.Abp.Saas.Editions; |
|||
using LINGYUN.Abp.Saas.Tenants; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.EntityFrameworkCore; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.Saas.EntityFrameworkCore; |
|||
|
|||
[IgnoreMultiTenancy] |
|||
[ConnectionStringName(AbpSaasDbProperties.ConnectionStringName)] |
|||
public class SaasDbContext : AbpDbContext<SaasDbContext>, ISaasDbContext |
|||
{ |
|||
public DbSet<Edition> Editions { get; set; } |
|||
|
|||
public DbSet<Tenant> Tenants { get; set; } |
|||
|
|||
public DbSet<TenantConnectionString> TenantConnectionStrings { get; set; } |
|||
|
|||
public SaasDbContext(DbContextOptions<SaasDbContext> options) |
|||
: base(options) |
|||
{ |
|||
} |
|||
|
|||
protected override void OnModelCreating(ModelBuilder builder) |
|||
{ |
|||
base.OnModelCreating(builder); |
|||
|
|||
builder.ConfigureSaas(); |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using LINGYUN.Abp.Saas.Tenants; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using System.Linq; |
|||
|
|||
namespace LINGYUN.Abp.Saas.EntityFrameworkCore; |
|||
|
|||
public static class SaasEfCoreQueryableExtensions |
|||
{ |
|||
public static IQueryable<Tenant> IncludeDetails(this IQueryable<Tenant> queryable, bool include = true) |
|||
{ |
|||
if (!include) |
|||
{ |
|||
return queryable; |
|||
} |
|||
|
|||
return queryable |
|||
.Include(x => x.ConnectionStrings); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,19 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.Http.Client" Version="$(VoloAbpPackageVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.Saas.Application.Contracts\LINGYUN.Abp.Saas.Application.Contracts.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,18 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.Http.Client; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.Saas; |
|||
|
|||
[DependsOn(typeof(AbpSaasApplicationContractsModule))] |
|||
[DependsOn(typeof(AbpHttpClientModule))] |
|||
public class AbpSaasHttpApiClientModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddHttpClientProxies( |
|||
typeof(AbpSaasApplicationContractsModule).Assembly, |
|||
AbpSaasRemoteServiceConsts.RemoteServiceName |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,19 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="$(VoloAbpPackageVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.Saas.Application.Contracts\LINGYUN.Abp.Saas.Application.Contracts.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,11 @@ |
|||
using LINGYUN.Abp.Saas.Localization; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
|
|||
namespace LINGYUN.Abp.Saas; |
|||
public abstract class AbpSaasControllerBase : AbpControllerBase |
|||
{ |
|||
protected AbpSaasControllerBase() |
|||
{ |
|||
LocalizationResource = typeof(AbpSaasResource); |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
using LINGYUN.Abp.Saas.Localization; |
|||
using Localization.Resources.AbpUi; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.Localization; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.Saas; |
|||
|
|||
[DependsOn(typeof(AbpSaasApplicationContractsModule))] |
|||
[DependsOn(typeof(AbpAspNetCoreMvcModule))] |
|||
public class AbpSaasHttpApiModule : AbpModule |
|||
{ |
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
PreConfigure<IMvcBuilder>(mvcBuilder => |
|||
{ |
|||
mvcBuilder.AddApplicationPartIfNotExists(typeof(AbpSaasHttpApiModule).Assembly); |
|||
}); |
|||
|
|||
PreConfigure<AbpMvcDataAnnotationsLocalizationOptions>(options => |
|||
{ |
|||
options.AddAssemblyResource( |
|||
typeof(AbpSaasResource), |
|||
typeof(AbpSaasApplicationContractsModule).Assembly); |
|||
}); |
|||
} |
|||
|
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpLocalizationOptions>(options => |
|||
{ |
|||
options.Resources |
|||
.Get<AbpSaasResource>() |
|||
.AddBaseTypes(typeof(AbpUiResource)); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Editions; |
|||
|
|||
[Controller] |
|||
[Authorize(AbpSaasPermissions.Editions.Default)] |
|||
[RemoteService(Name = AbpSaasRemoteServiceConsts.RemoteServiceName)] |
|||
[Area(AbpSaasRemoteServiceConsts.ModuleName)] |
|||
[Route("api/saas/editions")] |
|||
public class EditionController : AbpSaasControllerBase, IEditionAppService |
|||
{ |
|||
protected IEditionAppService EditionAppService { get; } |
|||
|
|||
public EditionController(IEditionAppService editionAppService) |
|||
{ |
|||
EditionAppService = editionAppService; |
|||
} |
|||
|
|||
[HttpPost] |
|||
[Authorize(AbpSaasPermissions.Editions.Create)] |
|||
public virtual Task<EditionDto> CreateAsync(EditionCreateDto input) |
|||
{ |
|||
return EditionAppService.CreateAsync(input); |
|||
} |
|||
|
|||
[HttpDelete] |
|||
[Route("{id}")] |
|||
[Authorize(AbpSaasPermissions.Editions.Delete)] |
|||
public virtual Task DeleteAsync(Guid id) |
|||
{ |
|||
return EditionAppService.DeleteAsync(id); |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("{id}")] |
|||
public virtual Task<EditionDto> GetAsync(Guid id) |
|||
{ |
|||
return EditionAppService.GetAsync(id); |
|||
} |
|||
|
|||
[HttpGet] |
|||
public virtual Task<PagedResultDto<EditionDto>> GetListAsync(EditionGetListInput input) |
|||
{ |
|||
return EditionAppService.GetListAsync(input); |
|||
} |
|||
|
|||
[HttpPut] |
|||
[Route("{id}")] |
|||
[Authorize(AbpSaasPermissions.Editions.Update)] |
|||
public virtual Task<EditionDto> UpdateAsync(Guid id, EditionUpdateDto input) |
|||
{ |
|||
return EditionAppService.UpdateAsync(id, input); |
|||
} |
|||
} |
|||
@ -0,0 +1,98 @@ |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Tenants; |
|||
|
|||
[Controller] |
|||
[Authorize(AbpSaasPermissions.Tenants.Default)] |
|||
[RemoteService(Name = AbpSaasRemoteServiceConsts.RemoteServiceName)] |
|||
[Area(AbpSaasRemoteServiceConsts.ModuleName)] |
|||
[Route("api/saas/tenants")] |
|||
public class TenantController : AbpSaasControllerBase, ITenantAppService |
|||
{ |
|||
protected ITenantAppService TenantAppService { get; } |
|||
|
|||
public TenantController(ITenantAppService tenantAppService) |
|||
{ |
|||
TenantAppService = tenantAppService; |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("{id}")] |
|||
public virtual Task<TenantDto> GetAsync(Guid id) |
|||
{ |
|||
return TenantAppService.GetAsync(id); |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("by-name/{name}")] |
|||
public virtual Task<TenantDto> GetAsync(string name) |
|||
{ |
|||
return TenantAppService.GetAsync(name); |
|||
} |
|||
|
|||
[HttpGet] |
|||
public virtual Task<PagedResultDto<TenantDto>> GetListAsync(TenantGetListInput input) |
|||
{ |
|||
return TenantAppService.GetListAsync(input); |
|||
} |
|||
|
|||
[HttpPost] |
|||
[Authorize(AbpSaasPermissions.Tenants.Create)] |
|||
public virtual Task<TenantDto> CreateAsync(TenantCreateDto input) |
|||
{ |
|||
return TenantAppService.CreateAsync(input); |
|||
} |
|||
|
|||
[HttpPut] |
|||
[Route("{id}")] |
|||
[Authorize(AbpSaasPermissions.Tenants.Update)] |
|||
public virtual Task<TenantDto> UpdateAsync(Guid id, TenantUpdateDto input) |
|||
{ |
|||
return TenantAppService.UpdateAsync(id, input); |
|||
} |
|||
|
|||
[HttpDelete] |
|||
[Route("{id}")] |
|||
[Authorize(AbpSaasPermissions.Tenants.Delete)] |
|||
public virtual Task DeleteAsync(Guid id) |
|||
{ |
|||
return TenantAppService.DeleteAsync(id); |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("{id}/connection-string/{name}")] |
|||
[Authorize(AbpSaasPermissions.Tenants.ManageConnectionStrings)] |
|||
public virtual Task<TenantConnectionStringDto> GetConnectionStringAsync(Guid id, string name) |
|||
{ |
|||
return TenantAppService.GetConnectionStringAsync(id, name); |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("{id}/connection-string")] |
|||
[Authorize(AbpSaasPermissions.Tenants.ManageConnectionStrings)] |
|||
public virtual Task<ListResultDto<TenantConnectionStringDto>> GetConnectionStringAsync(Guid id) |
|||
{ |
|||
return TenantAppService.GetConnectionStringAsync(id); |
|||
} |
|||
|
|||
[HttpPut] |
|||
[Route("{id}/connection-string")] |
|||
[Authorize(AbpSaasPermissions.Tenants.ManageConnectionStrings)] |
|||
public virtual Task<TenantConnectionStringDto> SetConnectionStringAsync(Guid id, TenantConnectionStringCreateOrUpdate input) |
|||
{ |
|||
return TenantAppService.SetConnectionStringAsync(id, input); |
|||
} |
|||
|
|||
[HttpDelete] |
|||
[Route("{id}/connection-string/{name}")] |
|||
[Authorize(AbpSaasPermissions.Tenants.ManageConnectionStrings)] |
|||
public virtual Task DeleteConnectionStringAsync(Guid id, string name) |
|||
{ |
|||
return TenantAppService.DeleteConnectionStringAsync(id, name); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue