跳到主要内容

编写和使用可扩展模块

智慧物流系统 基于Abp框架编写,同时自身的模块遵循可扩展模块方式编写,以下给出一些扩展使用的建议。

Module Entity 支持扩展

Domain.Shared

Domain.Shared 应该有 ObjectExtending 的扩展方法。来扩展该模块以及该模块的哪些实体具备扩展的能力。

以Rcs项目为例,ObjectExtending目录下有扩展ConfigureRcs的扩展方法。

public static class RcsModuleExtensionConfigurationDictionaryExtensions
{
public static ModuleExtensionConfigurationDictionary ConfigureRcs(
this ModuleExtensionConfigurationDictionary modules,
Action<RcsModuleExtensionConfiguration> configureAction)
{
return modules.ConfigureModule(
RcsModuleExtensionConsts.ModuleName,
configureAction
);
}
}

RcsModuleExtensionConfiguration提供了Rcs模块哪些Entity可以扩展。

public class RcsModuleExtensionConfiguration : ModuleExtensionConfiguration
{
public RcsModuleExtensionConfiguration ConfigureRobot(
Action<EntityExtensionConfiguration> configureAction)
{
return this.ConfigureEntity(
RcsModuleExtensionConsts.EntityNames.Robot,
configureAction
);
}
}

Domain

Domain 项目应该在PostConfigureServices 方法配置 ApplyEntityConfigurationToEntity 方法。

以下以Rcs模块为例,配置了Robot实体有扩展实体的能力。

public class LogiMasterRcsDomainModule : AbpModule
{
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();

public override void PostConfigureServices(ServiceConfigurationContext context)
{
OneTimeRunner.Run(() =>
{
//robot
ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity(
RcsModuleExtensionConsts.ModuleName,
RcsModuleExtensionConsts.EntityNames.Robot,
typeof(Robot)
);
}
}
}

Application.Contracts

Application.Contracts 项目中 PostConfigureServices 方法配置 ApplyEntityConfigurationToApi。

以下以Rcs模块为例,配置接口有哪些Dto配置了扩展属性。

public class LogiMasterRcsApplicationContractsModule : AbpModule
{
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();

public override void PostConfigureServices(ServiceConfigurationContext context)
{
OneTimeRunner.Run(() =>
{
//robot
ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToApi(
RcsModuleExtensionConsts.ModuleName,
RcsModuleExtensionConsts.EntityNames.Robot,
getApiTypes: new[] { typeof(RobotDto) },
createApiTypes: new[] { typeof(RobotCreateDto) },
updateApiTypes: new[] { typeof(RobotUpdateDto) }
);
}
}
}

Application

Application项目中,MapperProfile中增加MapExtraProperties,使得AutoMapper可以映射配置的扩展属性。

以下以Rcs模块为例,展示了Robot的对象映射时配置扩展属性。

public RcsApplicationAutoMapperProfile()
{
//Robot
CreateMap<RobotUpdateDto, Robot>(MemberList.Source)
.MapExtraProperties();

CreateMap<Robot, RobotDto>()
.ForMember(dest => dest.GroupName, opt => opt.MapFrom(src => src.RobotGroup.Name))
.MapExtraProperties();
}

EntityFrameworkCore

EntityFrameworkCore 项目中 DbContextModelCreatingExtensions 方法中 为每个实体增加 ApplyObjectExtensionMappings 方法,并在最后的增加对Contextext扩展。

builder.Entity<Robot>(b =>
{
//Configure table & schema name
b.ToTable(RcsDbProperties.DbTablePrefix + "Robots", RcsDbProperties.DbSchema);

b.ConfigureByConvention();

//Properties
b.Property(r => r.Ip).IsRequired().HasMaxLength(RobotConsts.MaxIpLength);
b.Property(r => r.Name).IsRequired().HasMaxLength(RobotConsts.MaxLength);


//Relations
b.HasOne(r => r.RobotGroup)
.WithMany(g => g.Robots)
.IsRequired(false)
.HasForeignKey(g => g.RobotGroupId)
.OnDelete(DeleteBehavior.SetNull);

//Indexes
b.HasIndex(q => q.CreationTime);

b.ApplyObjectExtensionMappings();
});

builder.TryConfigureObjectExtensions<RcsDbContext>();

如何使用实体扩展

Domain.Shared

Domain.Shared 项目中 xxxModuleExtensionConfigurator 方法中,对需要扩展模块实体进行扩展。

以下是MiR项目对Rcs项目中的Position实体进行扩展,添加了别名和站点类型id。

public static class MirModuleExtensionConfigurator
{
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();

public static void Configure()
{
OneTimeRunner.Run(() =>
{
ConfigureExistingProperties();
ConfigureExtraProperties();
});
}

private static void ConfigureExtraProperties()
{
//扩展Position 字段 Alias
ObjectExtensionManager.Instance.Modules()
.ConfigureRcs(mir =>
{
mir.ConfigurePosition(position =>
{
position.AddOrUpdateProperty<string?>(PositionExtensionsConsts.AliasPropertyName);
position.AddOrUpdateProperty<int>(PositionExtensionsConsts.TypeIdPropertyName);

});
});
}
}

添加实体的属性往往需要数据验证,Abp也提供了对扩展属性的自定义验证。


//arc path control point extension
ObjectExtensionManager.Instance.Modules()
.ConfigureRcs(rcsModule =>
{
rcsModule.ConfigurePath(path =>
{
path.AddOrUpdateProperty<string>(
PathConsts.ArcControlPointPropertyName,
options =>
{
// 添加自定义验证
options.Validators.Add(context =>
{
var controlPoint = context.Value as string;

if (controlPoint.IsNullOrEmpty())
{
var localizer = context.ServiceProvider.GetRequiredService<IStringLocalizer<RcsResource>>();

context.ValidationErrors.Add(
new ValidationResult(
localizer["Path:ArcControlPointIsRequired"]
)
);
}
});
}
);
});
});

Blazor

在界面如何需要对扩展字段进行验证,需要继承该FluentValueValidator类,并编写该字段的验证逻辑。


@code
{
// The validation rules (overkill, I know, but very fluent):
FluentValueValidator<string> ccValidator = new FluentValueValidator<string?>(x => x
.NotEmpty()
.Length(5, 100));
}

<MudTextField T="string?"
@bind-Value:get="_updatePositionDto.GetProperty<string?>(PositionExtensionsConsts.AliasPropertyName)"
@bind-Value:set="(value) =>
{
_updatePositionDto.SetProperty(PositionExtensionsConsts.AliasPropertyName,value);
AliasChanged();
}"
Label="@L["Position:Alias"]"
Validation="ccValidator.Validation" />

Domain

如果多处需要使用设置,获取扩展字段, 可以在Domain中设置扩展方法。

以下以MiR项目为例,展示了Position实体的扩展方法。

public static class PositionExtensions
{
public static int GetTypeId(this Position position)
{
return position.GetProperty<int>(PositionExtensionsConsts.TypeIdPropertyName);
}

public static void SetTypeId(this Position position, int typeId)
{
position.SetProperty(PositionExtensionsConsts.TypeIdPropertyName, typeId);
}
}

EntityFrameworkCore

如果需要单独数据库字段。 需要在EntityFrameworkCore 项目中, xxxEfCoreEntityExtensionMappings 方法中配置属性信息,并数据迁移。

以下以MiR模块对Rcs模块Position实体进行扩展,将Alias字段作为独立表字段。

public static class MirEfCoreEntityExtensionMappings
{
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();

public static void Configure()
{
OneTimeRunner.Run(() =>
{
ObjectExtensionManager.Instance
.MapEfCoreProperty<Position, string?>(
Rcs.Positions.PositionExtensionsConsts.AliasPropertyName,
(entityBuilder, propertyBuilder) =>
{
propertyBuilder.HasMaxLength(128);
}
);
});
}
}

最终项目应用数据迁移时,还需要增加EfCoreEntityExtension的配置。

/* This class is needed for EF Core console commands
* (like Add-Migration and Update-Database commands) */
public class InnoLightDbContextFactory : IDesignTimeDbContextFactory<InnoLightDbContext>
{
public InnoLightDbContext CreateDbContext(string[] args)
{
InnoLightEfCoreEntityExtensionMappings.Configure();
MirEfCoreEntityExtensionMappings.Configure();
RcsEfCoreEntityExtensionMappings.Configure();

var configuration = BuildConfiguration();

var builder = new DbContextOptionsBuilder<InnoLightDbContext>()
.UseMySql(configuration.GetConnectionString("Default"), MySqlServerVersion.LatestSupportedServerVersion);

return new InnoLightDbContext(builder.Options);
}

//...
}