場景
該設計多適用於MES,ERP,WMS 等管理型別的專案。
在做管理型別專案的時候,前端經常會使用到下拉框,比如:裝置,工廠等等。下拉元件中一般只需要他們的ID,Name
屬性,而這些資料都建立於其他模組並儲存在資料庫中。
如圖:
寫一個裝置的下拉元件的資料需要通過請求後端去獲取,如:localhost:5555/api/services/app/Resource/GetNdoCombox
,然後攜帶引數filterText
。
寫一個工廠的下拉元件也是同樣的要去請求,如:localhost:5555/api/services/app/Factory/GetNdoCombox
,
如果你的後端程式碼足夠規範,那麼就會像我寫的這樣,每個需要有下拉需求的實體建模都有一個GetNdoCombox
的介面。
問題點
為了程式碼的複用性,你一定不會每個用到裝置下拉的地方都去寫select
標籤,然後請求資料再渲染,而是會封裝一個裝置下拉框和一個工廠下拉框。於是便出現了一些問題:
前端下拉元件除了請求的介面不一樣,placeholder
不一樣,其他的程式碼都是盡數相同的。如果有下拉需求的地方多了,就會出現很多個xx下拉元件,如何優化?如果你有此類相同需求的問題時值得參考這個方案。
方案
思路
在後端寫一個介面做統一查詢,前端做一個統一的下拉元件,請求後端的統一查詢介面,前端傳遞標識性的引數給後端,後端通過引數自動匹配並查詢前端所需要的值。那麼重點就在後端如何實現這樣的匹配邏輯呢?
核心實現
我的實踐架構:.NET CORE + VUE
前端
前端就是寫個元件去請求介面,傳遞引數,這裡就不細說了
後端
先粗淺的介紹需要準備的東西:
- 自定義服務描述類,包含:服務介面,服務實現,泛型實體型別,生命週期
- 定義單例儲存器:定義Ndo字典,用於儲存對應的服務描述,以實體完全限定名為key,定義存和取的方法
- 通過反射獲取指定程式集中實現了IComboxQuery介面並且必須實現了IComboxQuery<>的下拉服務的服務描述,再將其注入到IOC容器中,並同時在儲存器的字典中新增對映關係。
- 統一獲取服務的Hub:從儲存器中根據實體名稱獲取對應的服務描述,再根據自定義服務描述類中的服務介面型別從IOC容器中獲取實現的IComboxQuery
詳細說明
Ndo:其實就是普通的實體的意思。
首先通過提供的IComboxQuery介面和IComboxQueryController
或Service
必須實現GetNdoCombox
方法。也就是說所有需要下拉的實體的服務都要實現IComboxQuery
程式啟動時利用反射將實現了IComboxQuery並且實現了IComboxQuery
定義統一獲取服務的Hub,從儲存器中根據實體名稱獲取對應的服務描述,再根據自定義服務描述類中的服務介面型別從IOC容器中獲取實現IComboxQuery的Controller
或Service
,然後呼叫GetNdoCombox
方法
定義統一的Controller
或Service
,隨便定義一個方法定義需要的引數為EntityName
和filterText
,方法中使用統一獲取服務的Hub,通過引數EntityName
獲取實際實現IComboxQuery的物件,然後呼叫GetNdoCombox
返回資料。
核心的查詢邏輯仍然是由服務自己實現的,因為不同的實體,過濾條件的欄位名不一樣,Hub只負責呼叫方法得到結果,不關心具體實現。
程式碼
返回的資料NdoDto
public class NdoDto
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual DateTime CreationTime { get; set; }
}
公共介面查詢的引數類
/// <summary>
/// 下拉框查詢的模糊搜尋輸入
/// </summary>
public class GetQueryFilterInput
{
/// <summary>
/// 型別全名稱,不涉及業務,用於區分本次請求是哪個實體的介面
/// </summary>
public virtual string Name { get; set; }
/// <summary>
/// 模糊查詢
/// </summary>
public virtual string FilterText { get; set; }
}
統一規範的公共介面
/// <summary>
/// 下拉查詢
/// </summary>
public interface IComboxQuery
{
Task<List<NdoDto>> GetCombox(GetQueryFilterInput input);
}
/// <summary>
/// 下拉查詢
/// </summary>
public interface IComboxQuery<TEntity> : IComboxQuery
{
}
自定義的服務對映描述類
/// <summary>
/// 服務對映描述
/// </summary>
public class SampleServiceDescriptor
{
/// <summary>
/// 瞬時依賴注入服務介面
/// </summary>
public static Type TransientInterface { get; } = typeof(ITransientDependency);
/// <summary>
/// 單例依賴注入服務介面
/// </summary>
public static Type SingletonInterface { get; } = typeof(ISingletonDependency);
/// <summary>
/// 服務型別 介面
/// </summary>
public virtual Type ServiceType { get; }
/// <summary>
/// 實現型別
/// </summary>
public virtual Type ImplementationType { get; }
/// <summary>
/// 建模實體型別
/// </summary>
public virtual Type EntityType { get; }
/// <summary>
/// 服務依賴注入生命週期
/// </summary>
public virtual ServiceLifetime ServiceLifetime { get; }
/// <summary>
/// 依賴注入服務
/// </summary>
/// <param name="serviceType">服務型別</param>
/// <param name="implementationType">實現型別</param>
public SampleServiceDescriptor(Type serviceType, Type implementationType)
{
this.ServiceType = serviceType;
this.ImplementationType = implementationType;
if (serviceType != null && serviceType.GenericTypeArguments.Length > 0)
{
// 獲取IComboxQuery<>中的泛型引數TEntity
this.EntityType = serviceType.GenericTypeArguments[0];
}
if (SingletonInterface.IsAssignableFrom(this.ImplementationType))
{
this.ServiceLifetime = ServiceLifetime.Singleton;
}
else
{
this.ServiceLifetime = ServiceLifetime.Transient;
}
}
/// <summary>
/// 轉換為 <see cref="ServiceDescriptor"/>
/// </summary>
/// <returns></returns>
public ServiceDescriptor ToServiceDescriptor()
{
return new ServiceDescriptor(this.ServiceType, this.ImplementationType, this.ServiceLifetime);
}
}
程式啟動時的掃描器(反射獲取實現了介面的服務)
/// <summary>
/// 依賴注入服務描述器
/// </summary>
public static class SampleServiceDescriptorHelper
{
/// <summary>
/// 掃描程式集中的某個介面的實現
/// </summary>
/// <param name="interfaceType">介面</param>
/// <param name="genericInterfaceTypes">介面泛型實現</param>
/// <param name="assemblies">程式集列表</param>
/// <returns></returns>
public static IEnumerable<SampleServiceDescriptor> ScanAssembliesServices
(Type interfaceType, IEnumerable<Type> genericInterfaceTypes, params Assembly[] assemblies)
{
// 泛型介面轉字典
var genericInterfaceTypeDict = new Dictionary<Type, bool>();
foreach (var item in genericInterfaceTypes)
{
genericInterfaceTypeDict[item] = true;
}
// 遍歷程式集中所有的符合條件的型別
foreach (var assembly in assemblies)
{
var services = assembly.GetTypes()
.Where(o => interfaceType.IsAssignableFrom(o)
&& o.IsPublic
&& !o.IsInterface
&& !o.IsAbstract
)
.Select(o =>
{
// 篩選某個介面
var entityInterfaceType = o.GetInterfaces()
.Where(x =>
{
if (!x.IsGenericType)
{
return false;
}
var genericTypeDefinition = x.GetGenericTypeDefinition();
return genericInterfaceTypeDict.ContainsKey(genericTypeDefinition);
})
.FirstOrDefault();
// entityInterfaceType = IComboxQuery<> 目前只有一種
return new SampleServiceDescriptor(entityInterfaceType, o);
})
.Where(o => o != null && o.ServiceType != null);
foreach (var service in services)
{
yield return service;
}
}
}
// interfaceType用於獲取所有實現了IComboxQuery的型別
// genericInterfaceTypes用於篩選,必須要實現了IComboxQuery<>的型別,因為需要獲取其TEntity的型別
// 如果只是實現了IComboxQuery的型別,是沒有TEntity的,會導致ComboxQueryInfoStorage中無法新增對映關係
}
單例的儲存器
public class ComboxQueryInfoStorage : IComboxQueryInfoStorage
{
/// <summary>
/// ModelingComboxQueryInfo 儲存器例項
/// </summary>
public static IComboxQueryInfoStorage Instace { get; set; } = new ComboxQueryInfoStorage();
/// <summary>
/// 資料儲存器
/// </summary>
protected readonly Dictionary<string, SampleServiceDescriptor> _Dict;
protected ComboxQueryInfoStorage()
{
this._Dict = new Dictionary<string, SampleServiceDescriptor>();
}
public void Add(params SampleServiceDescriptor[] comboxQueryInfos)
{
foreach (var item in comboxQueryInfos)
{
this._Dict[item.EntityType.FullName] = item;
}
}
public SampleServiceDescriptor Get(string name)
{
if (this._Dict.TryGetValue(name,out var comboxQueryInfo))
{
return comboxQueryInfo;
}
throw new Exception($"found Ndo type: {name}");
}
}
統一獲取服務的Hub
public class ComboxQueryHub : IComboxQueryHub
{
/// <summary>
/// 依賴注入容器
/// </summary>
protected readonly IServiceProvider _serviceProvider;
/// <summary>
/// 建構函式
/// </summary>
/// <param name="serviceProvider"></param>
public ComboxQueryHub(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task<List<NdoDto>> GetCombox(GetQueryFilterInput input)
{
var comboxQuery = this.GetComboxQuery(input.Name);
return await comboxQuery.GetCombox(input);
}
public IComboxQuery GetComboxQuery(string name)
{
var comboxQueryInfo = ComboxQueryInfoStorage.Instace.Get(name);
var comboxQuery = _serviceProvider.GetService(comboxQueryInfo.ServiceType) as IComboxQuery;
return comboxQuery;
}
}
用於將服務註冊到IOC和儲存器的擴充套件類
public static class ComboxQueryExtensions
{
/// <summary>
/// ComboxQuery 介面型別
/// </summary>
public static Type InterfaceType { get; } = typeof(IComboxQuery);
/// <summary>
/// IComboxQuery 介面型別
/// </summary>
public static List<Type> GenericInterfaceTypes { get; } = new List<Type>()
{
typeof(IComboxQuery<>)
};
/// <summary>
/// 註冊程式集中的 ComboxQuery
/// </summary>
/// <returns></returns>
public static void RegistrarComboxQuery(this IServiceCollection services, params Assembly[] assemblies)
{
// query hub
if (!services.Any(x=>x.ServiceType == typeof(IComboxQueryHub)))
{
services.AddTransient<IComboxQueryHub, ComboxQueryHub>();
}
// querys
var sampleServiceDescriptors = ScanComboxQuerys(assemblies);
foreach (var sampleServiceDescriptor in sampleServiceDescriptors)
{
if (services.Any(x => x.ServiceType == sampleServiceDescriptor.ServiceType))
{
continue;
}
ComboxQueryInfoStorage.Instace.Add(sampleServiceDescriptor);
if (sampleServiceDescriptor.ServiceLifetime == ServiceLifetime.Singleton)
{
services.AddSingleton(sampleServiceDescriptor.ServiceType,sampleServiceDescriptor.ImplementationType);
}
else
{
services.AddTransient(sampleServiceDescriptor.ServiceType, sampleServiceDescriptor.ImplementationType);
}
}
}
/// <summary>
/// 掃描程式集中的 ComboxQuery 實現
/// </summary>
/// <param name="assemblies"></param>
/// <returns></returns>
public static IEnumerable<SampleServiceDescriptor> ScanComboxQuerys(params Assembly[] assemblies)
{
return SampleServiceDescriptorHelper.ScanAssembliesServices(
InterfaceType,
GenericInterfaceTypes,
assemblies
);
}
}
使用
在啟動類中註冊服務
builder.Services.RegistrarComboxQuery(typeof(Program).Assembly);
人員建模:PersonController
public class PersonController : ApiControllerBase, IComboxQuery<Person>
{
[HttpPost]
public Task<List<NdoDto>> GetCombox(GetQueryFilterInput input)
{
var persons = Person.GetPeoples();
var ndos = persons.Select(x => new NdoDto
{
Id = x.Id,
Name = x.PersonName,
}).ToList();
return Task.FromResult(ndos);
}
}
裝置建模:ResourceController
public class ResourceController : ApiControllerBase, IComboxQuery<Resource>
{
[HttpPost]
public Task<List<NdoDto>> GetCombox(GetQueryFilterInput input)
{
var resources = Resource.GetResources();
var ndos = resources.Select(x => new NdoDto
{
Id = x.Id,
Name = x.ResourceName
}).ToList();
return Task.FromResult(ndos);
}
}
統一查詢介面:CommonBoxController
public class CommonBoxController : ApiControllerBase
{
/// <summary>
/// ioc容器
/// </summary>
protected readonly IServiceProvider _serviceProvider;
public CommonBoxController(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
[HttpPost]
public virtual async Task<List<NdoDto>> GetNdoCombox(GetQueryFilterInput input)
{
var queryHub = this._serviceProvider.GetService<IComboxQueryHub>();
return await queryHub.GetCombox(input);
}
}
效果
單獨請求PersonController
單獨請求ResourceController
請求公共介面CommonBoxController
程式碼倉庫
地址:https://gitee.com/huang-yuxiang/common-combox/tree/main/
版權宣告
作者:不想只會CURD的猿某人
更多原著文章請參考:https://www.cnblogs.com/hyx1229/