C#模式来扩展NuGet接口



我们的许多项目都有一个通用的体系结构,而这个体系结构需要一些对每个项目都通用的样板。我正试图将所有这些样板文件绑定到一个可重复使用的NuGet包中,以使维护更容易,但在让DI与我一起工作时遇到了问题。

具体来说,我正在为服务的概念而挣扎。在NuGet中,我必须定义基本的服务接口,这样我就可以挂接一些管道来使用这些服务。然而,将使用此NuGet的每个应用程序都需要能够使用特定于应用程序的方法来扩展这些服务。

让我们来举一个例子;用户认证流水线";,其应当回答诸如";这个用户是在角色x中吗;以及诸如";该用户是否可以基于其所有者y"来修改z;。

首先,我们的应用层是基于CQRS使用公共接口构建的,该接口由每个查询和命令实现:

public interface IApplicationRequestBase<TRet> : IRequest<TRet> { //IRequest from MediatR
Task<bool> Authorize(IUserServiceBase service, IPersistenceContextBase ctx);
void Validate();
}

IUserServiceBase是一个为当前用户提供访问权限的接口(我跳过了IPersistenceContext Base,它只是一个空接口(:

public interface IUserServiceBase {
string? CurrentUserExternalId { get; }
bool IsUserInRole(params string[] roleNames);
...

在身份验证管道中

public class RequestAuthorizationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IApplicationRequestBase<TResponse> { //MediatR IPipelineBehavior
private readonly IUserServiceBase _userService;
private readonly IPersistenceContextBase _ctx;
public RequestAuthorizationBehaviour(IUserServiceBase userService, IPersistenceContextBase ctx) {
_userService = userService;
_ctx = ctx;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next) {
if (await request.Authorize(_userService, _ctx)) {
return await next();
}
throw new UnauthorizedAccessException();
}
}
}

最后是NuGet-DI的定义:

public static class DependencyInjection {
public static IServiceCollection AddApplicationInfra(this IServiceCollection services) {
...
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestAuthorizationBehaviour<,>));
return services;
}
}

NuGet方面一切都很好,现在是应用程序。这种方法让我尝试直接扩展接口,这是可视化我希望实现的目标的最简单方法。

该应用程序有一系列特定于应用程序的授权检查,因此我们有一个自定义界面:

public interface IUserService : IUserServiceBase {
public string LocalUserIdClaimKey { get; }
Guid CurrentUserLocalId { get; }
/// <summary>
/// Shortcut for checking if the user has any role allowing read access to notifications
/// </summary>
bool CurrentUserCanReadNotifications { get; }
...

UserService类实现了IUserService接口中所需的所有功能,也就是说IUserServiceBase方法。它是在不同于接口(应用程序(的项目(基础设施(中定义的。

public class UserService : IUserService {
private readonly IHttpContextAccessor _contextAccessor;
public UserService(IHttpContextAccessor contextAccessor) {
_contextAccessor = contextAccessor;
}
public string? CurrentUserExternalId {
get {
var user = _contextAccessor.HttpContext.User;
if (user != null) {
return user.FindFirst(JwtClaimTypes.Subject)?.Value;
}
return null;
}
}
...

最后,在我们的司令部,一切都应该汇集在一起:

public class UpdateSubsequentTreatmentFacilitiesCommand : IApplicationRequestBase<int> {
public async Task<bool> Authorize(IUserService service, IPersistenceContext ctx) {
//Application specific authorization check
}
public void Validate() {
}

现在,我们得到一个构建错误,声明'UpdateSubsequentTreatmentFacilitiesCommand' does not implement interface member 'IApplicationRequestBase<int>.Authorize(IUserServiceBase, IPersistenceContextBase)'。这可能就是我在这里遇到的(尽管我仍然不明白为什么…(

因此,重申一下:

  • 目标是将通用项目样板文件打包为单个NuGet
  • 我们需要能够使用特定于应用程序的功能扩展NuGet中定义的服务

IApplicationRequestBaseservice参数的类型定义为IUserServiceBase,但UpdateSubsequentTreatmentFacilitiesCommand尝试使用IUserService。OO编程和继承不允许更改方法签名。

如果你可以更改IApplicationRequestBase,添加一个TService通用参数可以让你绕过它:

public interface IApplicationRequestBase<TRet, TService> : IRequest<TRet>
where TService is IUserServiceBase
{
Task<bool> Authorize(TService service, IPersistenceContextBase ctx);
void Validate();
}
public class UpdateSubsequentTreatmentFacilitiesCommand : IApplicationRequestBase<int, IUserService>
{
public async Task<bool> Authorize(IUserService service, IPersistenceContext ctx)
{
// method body
}
// rest of class
}

然而,考虑到IUserService是一个接口,如果它是唯一扩展/实现IUserServiceBase的东西,那么这听起来就像是一个过度设计的例子。有句话说完美是好的敌人。换句话说,在实际上不需要的地方,试图过于通用、过于可重复使用,只会减缓进度。无论如何,努力拥有一个高质量的代码库,但你也需要务实。

如果其他使用IApplicationRequestBase的应用程序有自己的用户服务,而不是与您的应用程序相同的IUserService,那么您需要找到另一种方法,因为C#是一种强类型语言。您可以在方法体中将IUserServiceBase类型转换为IUserService。您可以使用一个扩展方法,而不是扩展接口。如果你有创造力,你也可以考虑其他方法。

然而,看看IUserService,我的猜测是它的存在只是为了提高检查某些常用角色的性能。如果我错了,这是为了方便而不是性能,那么一个扩展方法就足够了。如果关注的是性能,那么请确保IsUserInRole的实现进行缓存。查找字符串仍然不会像返回房产的支持字段那样快。但是,更改您的软件体系结构以提高某些您尚未分析的东西的性能,从而确认它是性能瓶颈,这就是过早优化的定义。如果IsUserInRole做基本的缓存,您可能会发现性能足够好,而helper/extension方法可以解决您试图解决的可读性/代码质量问题。

最新更新