{{baseUrl}}/api/feature-management/features?providerName=D&providerKey=
1. 必须给对应的Provider给予Policy,不然会报错
public override void ConfigureServices(ServiceConfigurationContext context) { Configure<FeatureManagementOptions>(options => { options.ProviderPolicies[DefaultValueFeatureValueProvider.ProviderName] = AppNet6Permissions.Products.Default; }); }
2. 新加个FeatureDefinitionProvider,添加Feature值
public class MyFeatureDefinitionProvider : FeatureDefinitionProvider { public override void Define(IFeatureDefinitionContext context) { var myGroup = context.AddGroup("MyApp"); var reportingFeature = myGroup.AddFeature( "MyApp.Reporting", defaultValue: "false", displayName: LocalizableString .Create<AppNet6Resource>("Reporting"), valueType: new ToggleStringValueType() ); reportingFeature.CreateChild( "MyApp.PdfReporting", defaultValue: "false", displayName: LocalizableString .Create<AppNet6Resource>("PdfReporting"), valueType: new ToggleStringValueType() ); reportingFeature.CreateChild( "MyApp.ExcelReporting", defaultValue: "false", displayName: LocalizableString .Create<AppNet6Resource>("ExcelReporting"), valueType: new ToggleStringValueType() ); } }
3. 所用的ProviderName跟最后出来的value有关,DefaultValueFeatureValueProvider.ProviderName 的值是“D"
如果Provider是用别的值,那么API返回的value会是空.
Default的不会查询表AbpFeatureValues,也可以新起个provider从数据库取值
可以查看FeatureManager的代码如下
4.这里的IValueType是dynamic的,现成的有ToggleStringValueType,SelectionStringValueType,FreeTextStringValueType,对于于UI上的CheckBox,DropdownList和InputBox 。如果我想定义多一些属性给SelectionStringValueType,可以继承ISelectionStringValueItem。
public interface IMySelectionStringValueItemSource { ICollection<MySelectionStringValueItem> Items { get; } }
public class MySelectionStringValueItemSource : IMySelectionStringValueItemSource { public ICollection<MySelectionStringValueItem> Items { get; } public MySelectionStringValueItemSource(params MySelectionStringValueItem[] items) { Items = Check.NotNullOrEmpty(items, nameof(items)); } }
[Serializable] [StringValueType("MYSELECTION")] public class MySelectionStringValueType : StringValueTypeBase { public MySelectionStringValueItemSource ItemSource { get; set; } public MySelectionStringValueType() { } public MySelectionStringValueType(IValueValidator validator) : base(validator) { } }
public class MySelectionStringValueItem : ISelectionStringValueItem { public string Value { get; set; } public string Label { get; set; } public string Source { get; set; } public LocalizableStringInfo DisplayText { get; set; } public MySelectionStringValueItem(string value, string label, string source) { Value = value; Label = label; Source = source; DisplayText = null; } }
MyFeatureDefinitionProvider
myGroup.AddFeature( "MyApp.Product", defaultValue: "name2", displayName: LocalizableString .Create<AppNet6Resource>("MaxProductCount"), // valueType: new FreeTextStringValueType( // new NumericValueValidator(0, 1000000)), valueType : new MySelectionStringValueType() { ItemSource = new MySelectionStringValueItemSource( new MySelectionStringValueItem("name", "label","source") , new MySelectionStringValueItem("name2", "label", "source") , new MySelectionStringValueItem("name3", "label", "source")) } );
还不清楚 properties 怎么用
5.UI Selection 用Enum类型会更适合,利用 DisplayAttribute,可以定义Name,Description,GroupName,ShortName给前端使用
MySelectionStringValueItem
public class MySelectionStringValueItem : ISelectionStringValueItem { public int? Order { get; set; } public string Name { get; set; } public string Description { get; set; } public string GroupName { get; set; } public string ShortName { get; set; } public string Value { get; set; } public LocalizableStringInfo DisplayText { get; set; } public MySelectionStringValueItem() { } public MySelectionStringValueItem(int? order, string name, string value = null, string description = null, string groupName = null, string shortName = null, LocalizableStringInfo displayText = null) { Order = order; Name = name; Value = value ?? Name; //Description = description ?? Name; //GroupName = groupName ?? Name; //ShortName = shortName ?? Name; //DisplayText = displayText ?? new LocalizableStringInfo(null, Name); Description = description; GroupName = groupName ; ShortName = shortName ; DisplayText = displayText; } }
接着将Enum Type转换成 MySelectionStringValueItem
EnumHelper
public static class EnumHelper { public static MySelectionStringValueItem[] ConvertEnumToArray(Type enumType) { MySelectionStringValueItem[] items = new MySelectionStringValueItem[Enum.GetNames(enumType).Length]; FieldInfo[] fieldInfos = enumType.GetFields(); int i = 0; foreach (FieldInfo fieldInfo in fieldInfos) { if (fieldInfo.FieldType != enumType) continue; DisplayAttribute[] attributes = (DisplayAttribute[])fieldInfo.GetCustomAttributes(typeof(DisplayAttribute), false); if (attributes.Length > 0) { var attribute = attributes[0]; int order = attribute.GetOrder() == null ? 0 : attribute.GetOrder().Value; items[i] = new MySelectionStringValueItem(order: attribute.GetOrder() , name: attribute.Name ?? fieldInfo.Name , value: fieldInfo.GetValue(null).GetHashCode().ToString() , description: attribute.Description , groupName: attribute.GroupName , shortName: attribute.ShortName , displayText: null); } else { items[i] = new MySelectionStringValueItem(order: null , name: fieldInfo.Name , value: fieldInfo.GetValue(null).GetHashCode().ToString() , description: null , groupName: null , shortName: null , displayText: null); } i++; } return items; } }
MyFeatureDefinitionProvider
public class MyFeatureDefinitionProvider : FeatureDefinitionProvider { public override void Define(IFeatureDefinitionContext context) { var myGroup = context.AddGroup("MyApp"); myGroup.AddFeature( "MyApp.Product", defaultValue: "name2", displayName: LocalizableString .Create<AppNet6Resource>("MaxProductCount"), // valueType: new FreeTextStringValueType( // new NumericValueValidator(0, 1000000)), valueType: new MySelectionStringValueType() { ItemSource = new MySelectionStringValueItemSource( // new MySelectionStringValueItem("name", "label", "source") // , new MySelectionStringValueItem("name2", "label", "source") // , new MySelectionStringValueItem("name3", "label", "source") EnumHelper.ConvertEnumToArray(typeof(TestEnum)) ) } ); } public enum TestEnum { [Display(Name = "TestEnum1", Order = 1, Description = "TestEnum1 Description")] TestEnumOne = -1, [Display(Order = 2, Description = "TestEnum2 Description")] TestEnumFour = 4, [Display(Name = "TestEnum15", Order = 3, Description = "TestEnum3 Description")] TestEnumFive = 5 } }
6.既然 Feature里的data可以利用Attribute转换成 UI 上的组件,那Service上的也可以做到这一点。
从 api/abp/api-definition 可以得到各个api parameter的meta data,不过默认返回 PropertyApiDescriptionModel 的内容没有包含长度之类。
需要进一步改造,由于 PropertyApiDescriptionModel 和 TypeApiDescriptionModel 都不是接口类,想另外加field就要起多个新的Api
MyAbpApiDefinitionController
下面代码用的是abp5的版本
[Area("abp")] [RemoteService(Name = "abp")] [Route("api/abp/abp-api-definition")] public class MyAbpApiDefinitionController : AbpController, IRemoteService { private readonly IMyApiDescriptionModelProvider _modelProvider; private readonly IDistributedCache<MyApplicationApiDescriptionModel> _cache; public MyAbpApiDefinitionController(IMyApiDescriptionModelProvider modelProvider , IDistributedCache<MyApplicationApiDescriptionModel> cache) { _modelProvider = modelProvider; _cache = cache; } //[HttpGet] //public ApplicationApiDescriptionModel Get(ApplicationApiDescriptionModelRequestDto model) //{ // return _modelProvider.CreateApiModel(model); //} [HttpGet] public MyApplicationApiDescriptionModel GetMyApplicationApiDescriptionModel(ApplicationApiDescriptionModelRequestDto model) { return _cache.GetOrAdd( "CacheApplicationApiDescriptionModel", //Cache key () => GetCacheApplicationApiDescriptionModel(model), () => new DistributedCacheEntryOptions { AbsoluteExpiration = DateTimeOffset.Now.AddHours(24) } ); } protected MyApplicationApiDescriptionModel GetCacheApplicationApiDescriptionModel(ApplicationApiDescriptionModelRequestDto model) { return _modelProvider.CreateMyApiModel(model); } }
IMyApiDescriptionModelProvider
public interface IMyApiDescriptionModelProvider { ApplicationApiDescriptionModel CreateApiModel(ApplicationApiDescriptionModelRequestDto input); MyApplicationApiDescriptionModel CreateMyApiModel(ApplicationApiDescriptionModelRequestDto input); }
MyApplicationApiDescriptionModel
可以只返回customer的service
MyAspNetCoreApiDescriptionModelProvider
public class MyAspNetCoreApiDescriptionModelProvider : IApiDescriptionModelProvider, ITransientDependency { public ILogger<AspNetCoreApiDescriptionModelProvider> Logger { get; set; } private readonly AspNetCoreApiDescriptionModelProviderOptions _options; private readonly IApiDescriptionGroupCollectionProvider _descriptionProvider; private readonly AbpAspNetCoreMvcOptions _abpAspNetCoreMvcOptions; private readonly AbpApiDescriptionModelOptions _modelOptions; public MyAspNetCoreApiDescriptionModelProvider( IOptions<AspNetCoreApiDescriptionModelProviderOptions> options, IApiDescriptionGroupCollectionProvider descriptionProvider, IOptions<AbpAspNetCoreMvcOptions> abpAspNetCoreMvcOptions, IOptions<AbpApiDescriptionModelOptions> modelOptions) { _options = options.Value; _descriptionProvider = descriptionProvider; _abpAspNetCoreMvcOptions = abpAspNetCoreMvcOptions.Value; _modelOptions = modelOptions.Value; Logger = NullLogger<AspNetCoreApiDescriptionModelProvider>.Instance; } public MyApplicationApiDescriptionModel CreateMyApiModel(ApplicationApiDescriptionModelRequestDto input) { //TODO: Can cache the model? var model = MyApplicationApiDescriptionModel.Create(); var apiItems = _descriptionProvider.ApiDescriptionGroups.Items.Where(o => o.Items.Any(p => p.RelativePath.StartsWith("api/app"))).ToList(); foreach (var descriptionGroupItem in apiItems) { foreach (var apiDescription in descriptionGroupItem.Items) { if (!apiDescription.ActionDescriptor.IsControllerAction()) { continue; } AddApiDescriptionToModel(apiDescription, model, input); } } return model; }
MyPropertyApiDescriptionModel
[Serializable] public class MyPropertyApiDescriptionModel { public string Name { get; set; } public string JsonName { get; set; } public string Type { get; set; } public string TypeSimple { get; set; } public bool IsRequired { get; set; } public string DisplayName { get; set; } public int Min { get; set; } public int Max { get; set; } //TODO: Validation rules for this property public static MyPropertyApiDescriptionModel Create(PropertyInfo propertyInfo) { MyPropertyApiDescriptionModel propertyModel = new MyPropertyApiDescriptionModel { Name = propertyInfo.Name, JsonName = AbpApiProxyScriptingConfiguration.PropertyNameGenerator.Invoke(propertyInfo), Type = ApiTypeNameHelper.GetTypeName(propertyInfo.PropertyType), TypeSimple = ApiTypeNameHelper.GetSimpleTypeName(propertyInfo.PropertyType), IsRequired = propertyInfo.IsDefined(typeof(RequiredAttribute), true) }; var attributes = propertyInfo.GetCustomAttributes(true); if (propertyInfo.IsDefined(typeof(StringLengthAttribute), true)) { var attr = attributes.OfType<StringLengthAttribute>().FirstOrDefault(); propertyModel.Min = attr.MinimumLength; propertyModel.Max = attr.MaximumLength; } if (propertyInfo.IsDefined(typeof(RangeAttribute), true)) { var attr = attributes.OfType<RangeAttribute>().FirstOrDefault(); propertyModel.Min = Convert.ToInt32(attr.Minimum); propertyModel.Max = Convert.ToInt32(attr.Maximum); } if (!propertyInfo.PropertyType.IsEnum && propertyInfo.IsDefined(typeof(DisplayAttribute), true)) { var attr = attributes.OfType<DisplayAttribute>().FirstOrDefault(); propertyModel.DisplayName = attr.GetName(); } else propertyModel.DisplayName = propertyModel.Name; return propertyModel; } }
MyTypeApiDescriptionModel
[Serializable] public class MyTypeApiDescriptionModel { public string BaseType { get; set; } public bool IsEnum { get; set; } //public string[] EnumNames { get; set; } //public object[] EnumValues { get; set; } public string[] GenericArguments { get; set; } public MyPropertyApiDescriptionModel[] Properties { get; set; } public MySelectionStringValueItem[] EnumProperties { get; set; } public MyTypeApiDescriptionModel() { } public static MyTypeApiDescriptionModel Create(Type type) { var baseType = type.BaseType; if (baseType == typeof(object)) { baseType = null; } var typeModel = new MyTypeApiDescriptionModel { IsEnum = type.IsEnum, BaseType = baseType != null ? TypeHelper.GetFullNameHandlingNullableAndGenerics(baseType) : null }; if (typeModel.IsEnum) { //typeModel.EnumNames = type.GetEnumNames(); //typeModel.EnumValues = type.GetEnumValues().Cast<object>().ToArray(); typeModel.EnumProperties = EnumHelper.ConvertEnumToArray(type); } else { typeModel.Properties = type .GetProperties() .Where(p => p.DeclaringType == type) .Select(MyPropertyApiDescriptionModel.Create) .ToArray(); if (type.IsGenericTypeDefinition) { typeModel.GenericArguments = type.GetGenericArguments().Select(a => a.Name).ToArray(); } } return typeModel; } }
ProductDto
public class ProductDto : FullAuditedEntityDto<Guid> { [Required] [Display(Name = "Product Name")] [StringLength(50, MinimumLength = 5,ErrorMessage = "Invalid Product Name.")] public string Name { get; set; } [JsonProperty("testvalue")] public Guid StoreId { get; set; } [Range(0, 15, ErrorMessage = "Can only be between 0 .. 15")] public int Quantity { get; set; } public ProductType ProductType { get; set; } } public enum ProductType { [Display(Name = "Product Type 1")] ProductType1 = 0, ProductType2 = 1, ProductType3 = 2, }
调用 api/abp/abp-api-definition 结果如下,这样返回的内容就可以给UI进行validation了