如何重写代码以在 Asp Net Web Api 中使用具有依赖注入的 IAuthorizationFilter 而不是具有服务位置的 AuthorizeAttribute?
How to rewrite code to use IAuthorizationFilter with dependency injection instead of AuthorizeAttribute with service location in Asp Net Web Api?
我有自定义 AuthorizeAttribute
,我需要使用其中一种业务层服务来验证数据库中的某些数据,然后再授予用户查看资源的权限。为了能够在我的 AuthorizeAttribute
中分配此服务,我决定使用服务位置 "anti-pattern",这是代码:
internal class AuthorizeGetGroupByIdAttribute : AuthorizeAttribute
{
private readonly IUserGroupService _userGroupService;
public AuthorizeGetGroupByIdAttribute()
{
_userGroupService = ServiceLocator.Instance.Resolve<IUserGroupService>();
}
//In this method I'm validating whether the user is a member of a group.
//If they are not they won't get a permission to view the resource, which is decorated with this attribute.
protected override bool IsAuthorized(HttpActionContext actionContext)
{
Dictionary<string, string> parameters = actionContext.Request.GetQueryNameValuePairs().ToDictionary(x => x.Key, x => x.Value);
int groupId = int.Parse(parameters["groupId"]);
int currentUserId = HttpContext.Current.User.Identity.GetUserId();
return _userGroupService.IsUserInGroup(currentUserId, groupId);
}
protected override void HandleUnauthorizedRequest(HttpActionContext actionContex)
{
if (!HttpContext.Current.User.Identity.IsAuthenticated)
{
base.HandleUnauthorizedRequest(actionContex);
}
else
{
actionContex.Response = new HttpResponseMessage(HttpStatusCode.Forbidden);
}
}
}
我的应用程序中还有其他几个这样的属性。使用服务定位器可能不是一个好方法。在网上搜索了一下之后,我发现有些人建议使用 IAuthorizationFilter
和依赖注入来代替。但是我不知道怎么写这种IAuthorizationFilter
。你能帮我写 IAuthorizationFilter
来做和上面 AuthorizeAttribute
一样的事情吗?
因此,经过一段时间的努力,我认为我设法解决了这个问题。以下是您必须执行的步骤:
1) 首先你必须让 GetGroupByIdAttribute
被动,我所说的被动是指一个没有任何逻辑的空属性(它将严格用于装饰目的)
public class GetGroupByIdAttribute : Attribute
{
}
2) 然后你必须用这个属性标记一个控制器方法,你想为它添加授权。
[HttpPost]
[GetGroupById]
public IHttpActionResult GetGroupById(int groupId)
{
//Some code
}
3) 为了编写你自己的 IAuthorizationFilter
你必须实现它的方法 ExecuteAuthorizationFilterAsync
。这是完整的 class(我包含了指导您完成代码的注释):
public class GetGroupByIdAuthorizationFilter : IAuthorizationFilter
{
public bool AllowMultiple { get; set; }
private readonly IUserGroupService _userGroupService;
//As you can see I'm using a constructor injection here
public GetGroupByIdAuthorizationFilter(IUserGroupService userGroupService)
{
_userGroupService = userGroupService;
}
public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
//First I check whether the method is marked with the attribute, if it is then check whether the current user has a permission to use this method
if (actionContext.ActionDescriptor.GetCustomAttributes<GetGroupByIdAttribute>().SingleOrDefault() != null)
{
Dictionary<string, string> parameters = actionContext.Request.GetQueryNameValuePairs().ToDictionary(x => x.Key, x => x.Value);
int groupId = int.Parse(parameters["groupId"]);
int currentUserId = HttpContext.Current.User.Identity.GetUserId();
//If the user is not allowed to view view the resource, then return 403 status code forbidden
if (!_userGroupService.IsUserInGroup(currentUserId, groupId))
{
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.Forbidden));
}
}
//If this line was reached it means the user is allowed to use this method, so just return continuation() which basically means continue processing
return continuation();
}
}
4) 最后一步是在 WebApiConfig
.
中注册您的过滤器
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Here I am registering Dependency Resolver
config.DependencyResolver = ServiceLocator.Instance.DependencyResolver;
//Then I resolve the service I want to use (which should be fine because this is basically the start of the application)
var userGroupService = ServiceLocator.Instance.Resolve<IUserGroupService>();
//And finally I'm registering the IAuthorizationFilter I created
config.Filters.Add(new GetGroupByIdAuthorizationFilter(userGroupService));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
现在,如果需要,我可以创建额外的 IActionFilters
使用 IUserGroupService
,然后在应用程序启动时注入此服务,从 WebApiConfig
class,进入所有过滤器。
也许像这里显示的那样尝试:
将以下 public 方法添加到您的 class。
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
// gets the dependecies from the serviceProvider
// and creates an instance of the filter
return new GetGroupByIdAuthorizationFilter(
(IUserGroupService )serviceProvider.GetService(typeof(IUserGroupService )));
}
同时将接口 IFilterMetadata
添加到您的 class。
现在,当您的 class 被创建时,DI 注意到有一个 CreateInstance 方法并将使用该方法而不是构造函数。
或者,您可以通过调用
直接从方法中的 DI 获取接口
context.HttpContext.Features.Get<IUserGroupService>()
我有自定义 AuthorizeAttribute
,我需要使用其中一种业务层服务来验证数据库中的某些数据,然后再授予用户查看资源的权限。为了能够在我的 AuthorizeAttribute
中分配此服务,我决定使用服务位置 "anti-pattern",这是代码:
internal class AuthorizeGetGroupByIdAttribute : AuthorizeAttribute
{
private readonly IUserGroupService _userGroupService;
public AuthorizeGetGroupByIdAttribute()
{
_userGroupService = ServiceLocator.Instance.Resolve<IUserGroupService>();
}
//In this method I'm validating whether the user is a member of a group.
//If they are not they won't get a permission to view the resource, which is decorated with this attribute.
protected override bool IsAuthorized(HttpActionContext actionContext)
{
Dictionary<string, string> parameters = actionContext.Request.GetQueryNameValuePairs().ToDictionary(x => x.Key, x => x.Value);
int groupId = int.Parse(parameters["groupId"]);
int currentUserId = HttpContext.Current.User.Identity.GetUserId();
return _userGroupService.IsUserInGroup(currentUserId, groupId);
}
protected override void HandleUnauthorizedRequest(HttpActionContext actionContex)
{
if (!HttpContext.Current.User.Identity.IsAuthenticated)
{
base.HandleUnauthorizedRequest(actionContex);
}
else
{
actionContex.Response = new HttpResponseMessage(HttpStatusCode.Forbidden);
}
}
}
我的应用程序中还有其他几个这样的属性。使用服务定位器可能不是一个好方法。在网上搜索了一下之后,我发现有些人建议使用 IAuthorizationFilter
和依赖注入来代替。但是我不知道怎么写这种IAuthorizationFilter
。你能帮我写 IAuthorizationFilter
来做和上面 AuthorizeAttribute
一样的事情吗?
因此,经过一段时间的努力,我认为我设法解决了这个问题。以下是您必须执行的步骤:
1) 首先你必须让 GetGroupByIdAttribute
被动,我所说的被动是指一个没有任何逻辑的空属性(它将严格用于装饰目的)
public class GetGroupByIdAttribute : Attribute
{
}
2) 然后你必须用这个属性标记一个控制器方法,你想为它添加授权。
[HttpPost]
[GetGroupById]
public IHttpActionResult GetGroupById(int groupId)
{
//Some code
}
3) 为了编写你自己的 IAuthorizationFilter
你必须实现它的方法 ExecuteAuthorizationFilterAsync
。这是完整的 class(我包含了指导您完成代码的注释):
public class GetGroupByIdAuthorizationFilter : IAuthorizationFilter
{
public bool AllowMultiple { get; set; }
private readonly IUserGroupService _userGroupService;
//As you can see I'm using a constructor injection here
public GetGroupByIdAuthorizationFilter(IUserGroupService userGroupService)
{
_userGroupService = userGroupService;
}
public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
//First I check whether the method is marked with the attribute, if it is then check whether the current user has a permission to use this method
if (actionContext.ActionDescriptor.GetCustomAttributes<GetGroupByIdAttribute>().SingleOrDefault() != null)
{
Dictionary<string, string> parameters = actionContext.Request.GetQueryNameValuePairs().ToDictionary(x => x.Key, x => x.Value);
int groupId = int.Parse(parameters["groupId"]);
int currentUserId = HttpContext.Current.User.Identity.GetUserId();
//If the user is not allowed to view view the resource, then return 403 status code forbidden
if (!_userGroupService.IsUserInGroup(currentUserId, groupId))
{
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.Forbidden));
}
}
//If this line was reached it means the user is allowed to use this method, so just return continuation() which basically means continue processing
return continuation();
}
}
4) 最后一步是在 WebApiConfig
.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Here I am registering Dependency Resolver
config.DependencyResolver = ServiceLocator.Instance.DependencyResolver;
//Then I resolve the service I want to use (which should be fine because this is basically the start of the application)
var userGroupService = ServiceLocator.Instance.Resolve<IUserGroupService>();
//And finally I'm registering the IAuthorizationFilter I created
config.Filters.Add(new GetGroupByIdAuthorizationFilter(userGroupService));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
现在,如果需要,我可以创建额外的 IActionFilters
使用 IUserGroupService
,然后在应用程序启动时注入此服务,从 WebApiConfig
class,进入所有过滤器。
也许像这里显示的那样尝试:
将以下 public 方法添加到您的 class。
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
// gets the dependecies from the serviceProvider
// and creates an instance of the filter
return new GetGroupByIdAuthorizationFilter(
(IUserGroupService )serviceProvider.GetService(typeof(IUserGroupService )));
}
同时将接口 IFilterMetadata
添加到您的 class。
现在,当您的 class 被创建时,DI 注意到有一个 CreateInstance 方法并将使用该方法而不是构造函数。
或者,您可以通过调用
直接从方法中的 DI 获取接口context.HttpContext.Features.Get<IUserGroupService>()