Ref*_*eft 9 asp.net-mvc dependency-injection unity-container action-filter asp.net-mvc-4
我正在使用Unity.MVC4依赖注入来访问我的服务.注入到我的Controller构造函数中时,一切都正常,但我现在要做的是在我的过滤器类中使用属性注入,这样我就可以从内部访问我的数据库了.
在我开始这个问题之前,我用Google搜索并尝试了不同的示例,但我找不到适合我的解决方案..
Bootstrapper.cs
public static class Bootstrapper
{
public static IUnityContainer Initialise()
{
var container = BuildUnityContainer();
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
return container;
}
private static IUnityContainer BuildUnityContainer()
{
var container = new UnityContainer();
container.RegisterType<IAccountRepository, AccountRepository>();
container.RegisterType<IAdministrationRepository, AdministrationRepository>();
container.RegisterType<IUploadDirectlyRepository, UploadDirectlyRepository>();
container.RegisterType<IUserRepository, UserRepository>();
container.RegisterType<INewsRepository, NewsRepository>();
container.RegisterType<IContactRepository, ContactRepository>();
// register all your components with the container here
// it is NOT necessary to register your controllers
// e.g. container.RegisterType<ITestService, TestService>();
RegisterTypes(container);
return container;
}
public static void RegisterTypes(IUnityContainer container)
{
}
}
Run Code Online (Sandbox Code Playgroud)
的Application_Start
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
Bootstrapper.Initialise();
}
}
Run Code Online (Sandbox Code Playgroud)
工作实例
public class UserController : Controller
{
private readonly IUserRepository _userRepository;
public UserController(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public ActionResult GetUser(int userID)
{
var user = _userRepository.GetUser(userID)
return View(user);
}
}
Run Code Online (Sandbox Code Playgroud)
我要向您展示的以下代码是我想要在我的操作中使用的filter属性.我想传入一个字符串数组类型的参数,以便我可以验证当前用户是否可以访问该操作.
在我的应用程序中,有两种类型的用户,帐户所有者和访客.对于帐户所有者而言,所有操作都是完全开放的,但对于访客而言,操作因操作而异.例如,某个操作可能要求您拥有至少三个权限之一(读取,写入和编辑).
过滤:
public class ClaimsAuthorizeAccountAccess : AuthorizeAttribute
{
private IAccountRepository _accountRepository { get; set; }
private String[] _permissions { get; set; }
public ClaimsAuthorizeAccountAccess(IAccountRepository accountRepository, params String[] permissions)
{
_permissions = permissions;
_accountRepository = accountRepository;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (HttpContext.Current.User.IsInRole("Account Owner"))
{
base.OnAuthorization(filterContext);
}
else
{
ClaimsIdentity claimsIdentity = (ClaimsIdentity)HttpContext.Current.User.Identity;
List<AccountLinkPermissionDTO> accountLinkPermissions = new List<AccountLinkPermissionDTO>();
int accountOwnerID = 0;
Int32.TryParse(claimsIdentity.Claims.Where(c => c.Type == "AccountOwnerID").Select(c => c.Value).SingleOrDefault(), out accountOwnerID);
int guestID = 0;
Int32.TryParse(claimsIdentity.Claims.Where(c => c.Type == ClaimTypes.Sid).Select(c => c.Value).SingleOrDefault(), out guestID);
//NULL
accountLinkPermissions = _accountRepository.GetAccountLinkPermissions(accountOwnerID, guestID);
if (accountLinkPermissions != null)
{
List<string> accountLinkPermissionsToString = accountLinkPermissions.Select(m => m.Permission.Name).ToList();
int hits = accountLinkPermissionsToString.Where(m => _permissions.Contains(m)).Count();
if (hits > 0)
{
base.OnAuthorization(filterContext);
}
}
else
{
//Guest doesnt have right permissions
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary {
{ "action", "AccessDenied" },
{ "controller", "Account" }});
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果我使用这个过滤器,它看起来像..
[ClaimsAuthorizeAccountAccess("File read", "File write, File edit")]
public ActionResult Files()
{
return View();
}
Run Code Online (Sandbox Code Playgroud)
但是这不起作用,因为过滤器需要两个参数(IRepository和string []).显然,这里也不可能使用构造函数注入.
然后我尝试实现可在此处找到的John Allers解决方案.它看起来很有希望,但它给了我这个错误:
Microsoft.Practices.Unity.dll中出现"Microsoft.Practices.Unity.ResolutionFailedException"类型的异常,但未在用户代码中处理
附加信息:依赖项的解析失败,type ="Fildela.ClaimsAuthorizeAccountAccess",name ="(none)".
在解决时发生异常:
异常是:InvalidOperationException - Fildela.ClaimsAuthorizeAccountAccess类型的属性_accountRepository不可设置.
在例外时,容器是:
解决Fildela.ClaimsAuthorizeAccountAccess,(无)
关于如何解决这个坏男孩的任何建议?
谢谢!
Nig*_*888 14
根据帖子被动属性,DI友好的解决方案是AuthorizeAttribute分为两部分:
出于我们的目的,我们只是继承AuthorizeAttribute以利用它的一些内置功能.
请注意,如果采用此方法,则对数据库依赖项使用属性注入没有多大意义.无论如何,构造函数注入始终是更好的选择.
首先,我们的属性没有行为来标记我们的控制器和操作.我们添加一些智能来将权限解析为数组,这样就不必在每次授权检查时完成.
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class ClaimsAuthorizeAccountAccess : Attribute
{
private readonly string[] _permissionsSplit;
public ClaimsAuthorizeAccountAccess(string permissions)
{
_permissionsSplit = SplitString(value);
}
internal string[] PermissionsSplit
{
get { return this._permissionsSplit; }
}
internal static string[] SplitString(string original)
{
if (string.IsNullOrEmpty(original))
{
return new string[0];
}
return (from piece in original.Split(new char[] { ',' })
let trimmed = piece.Trim()
where !string.IsNullOrEmpty(trimmed)
select trimmed).ToArray<string>();
}
}
Run Code Online (Sandbox Code Playgroud)
接下来,我们有我们的授权过滤器,它将充当全局过滤器.
我们WhiteListMode默认添加一个是真的,因为这是配置安全性的推荐方法(控制器和操作需要登录,除非给出一个AllowAnonymousAttribute).幸运的是,它的框架已经内置,AuthorizeAttribute因此我们只是将其用作标志,无论是否全局检查.
我们还添加了一个扩展点,可以注入我们的自定义授权服务.最有可能改变的两个方面是:
所以这些是我们添加到服务中的东西.如果需要,您可以将其重构为2个单独的服务.
public class ClaimsIdentityAuthorizationFilter : AuthorizeAttribute
{
private readonly IAuthorizationService _authorizationService;
private string _permissions;
private string[] _permissionsSplit = new string[0];
private bool _whiteListMode = true;
public ClaimsIdentityAuthorizationFilter(IAuthorizationService authorizationService)
{
if (authorizationService == null)
throw new ArgumentNullException("authorizationService");
this._authorizationService = authorizationService;
}
// Hide users and roles, since we aren't using them.
[Obsolete("Not applicable in this class.")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
new public string Roles { get; set; }
[Obsolete("Not applicable in this class.")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
new public string Users { get; set; }
public string Permissions
{
get
{
return (this._permissions ?? string.Empty);
}
set
{
this._permissions = value;
this._permissionsSplit = SplitString(value);
}
}
public bool WhiteListMode
{
get { return this._whiteListMode; }
set { this._whiteListMode = value; }
}
internal static string[] SplitString(string original)
{
if (string.IsNullOrEmpty(original))
{
return new string[0];
}
return (from piece in original.Split(new char[] { ',' })
let trimmed = piece.Trim()
where !string.IsNullOrEmpty(trimmed)
select trimmed).ToArray<string>();
}
private ClaimsAuthorizeAccountAccess GetClaimsAuthorizeAccountAccess(ActionDescriptor actionDescriptor)
{
ClaimsAuthorizeAccountAccess result = null;
// Check if the attribute exists on the action method
result = (ClaimsAuthorizeAccountAccess)actionDescriptor
.GetCustomAttributes(attributeType: typeof(ClaimsAuthorizeAccountAccess), inherit: true)
.SingleOrDefault();
if (result != null)
{
return result;
}
// Check if the attribute exists on the controller
result = (ClaimsAuthorizeAccountAccess)actionDescriptor
.ControllerDescriptor
.GetCustomAttributes(attributeType: typeof(ClaimsAuthorizeAccountAccess), inherit: true)
.SingleOrDefault();
return result;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var actionDescriptor = httpContext.Items["ActionDescriptor"] as ActionDescriptor;
if (actionDescriptor != null)
{
var authorizeAttribute = this.GetClaimsAuthorizeAccountAccess(actionDescriptor);
// If the authorization attribute exists
if (authorizeAttribute != null)
{
// Run the authorization based on the attribute
return this._authorizationService.HasPermission(
httpContext,
authorizeAttribute.PermissionsSplit);
}
else if (this.WhiteListMode)
{
// Run the global authorization
return this._authorizationService.HasPermission(
httpContext,
this._permissionsSplit);
}
}
return true;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
// Pass the current action descriptor to the AuthorizeCore
// method on the same thread by using HttpContext.Items
filterContext.HttpContext.Items["ActionDescriptor"] = filterContext.ActionDescriptor;
base.OnAuthorization(filterContext);
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = this._authorizationService.GetUnauthorizedHandler(filterContext);
}
}
Run Code Online (Sandbox Code Playgroud)
public interface IAuthorizationService
{
bool HasPermission(HttpContextBase httpContext, string[] permissions);
ActionResult GetUnauthorizedHandler(AuthorizationContext filterContext);
}
Run Code Online (Sandbox Code Playgroud)
所以现在我们进行高级定制以支持声明.我们将其分开,因此如果业务逻辑在将来发生变化,我们可以使用它来注入另一个实例.
public class ClaimsIdentityAuthorizationService : IAuthorizationService
{
private IAccountRepository _accountRepository { get; set; }
public ClaimsIdentityAuthorizationService(IAccountRepository accountRepository)
{
if (accountRepository == null)
throw new ArgumentNullException("accountRepository");
_accountRepository = accountRepository;
}
public bool HasPermission(HttpContextBase httpContext, string[] permissions)
{
if (httpContext == null)
{
throw new ArgumentNullException("httpContext");
}
IPrincipal user = httpContext.User;
if (!user.Identity.IsAuthenticated)
{
return false;
}
if (!user.IsInRole("Account Owner"))
{
ClaimsIdentity claimsIdentity = (ClaimsIdentity)user.Identity;
List<AccountLinkPermissionDTO> accountLinkPermissions = new List<AccountLinkPermissionDTO>();
int accountOwnerID = 0;
Int32.TryParse(claimsIdentity.Claims.Where(c => c.Type == "AccountOwnerID").Select(c => c.Value).SingleOrDefault(), out accountOwnerID);
int guestID = 0;
Int32.TryParse(claimsIdentity.Claims.Where(c => c.Type == ClaimTypes.Sid).Select(c => c.Value).SingleOrDefault(), out guestID);
//NULL
accountLinkPermissions = _accountRepository.GetAccountLinkPermissions(accountOwnerID, guestID);
if (accountLinkPermissions != null)
{
List<string> accountLinkPermissionsToString = accountLinkPermissions.Select(m => m.Permission.Name).ToList();
int hits = accountLinkPermissionsToString.Where(m => permissions.Contains(m)).Count();
if (hits == 0)
{
return false;
}
}
else
{
return false;
}
}
return true;
}
public ActionResult GetUnauthorizedHandler(AuthorizationContext filterContext)
{
//Guest doesnt have right permissions
return new RedirectToRouteResult(
new RouteValueDictionary {
{ "action", "AccessDenied" },
{ "controller", "Account" }
});
}
}
Run Code Online (Sandbox Code Playgroud)
全局注册过滤器并将其依赖项注入容器.
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters, IUnityContainer container)
{
filters.Add(new HandleErrorAttribute());
filters.Add(container.Resolve<IAuthorizationFilter>());
}
}
Run Code Online (Sandbox Code Playgroud)
注意:如果您需要任何过滤器的依赖项,其生命周期短于单例,则需要
GlobalFilterProvider在此答案中使用as .
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
var container = Bootstrapper.Initialise();
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters, container);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
Run Code Online (Sandbox Code Playgroud)
public static class Bootstrapper
{
public static IUnityContainer Initialise()
{
var container = BuildUnityContainer();
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
return container;
}
private static IUnityContainer BuildUnityContainer()
{
var container = new UnityContainer();
container.RegisterType<IAccountRepository, AccountRepository>();
container.RegisterType<IAdministrationRepository, AdministrationRepository>();
container.RegisterType<IUploadDirectlyRepository, UploadDirectlyRepository>();
container.RegisterType<IUserRepository, UserRepository>();
container.RegisterType<INewsRepository, NewsRepository>();
container.RegisterType<IContactRepository, ContactRepository>();
// Register the types for the authorization filter
container.RegisterType<IAuthorizationFilter, ClaimsIdentityAuthorizationFilter>(
// Not sure whether you want white list or black list
// but here is where it is set.
new InjectionProperty("WhiteListMode", true),
// For white list security, you can also set the default
// permissions that every action gets if it is not overridden.
new InjectionProperty("Permissions", "read"));
container.RegisterType<IAuthorizationService, ClaimsIdentityAuthorizationService>();
// register all your components with the container here
// it is NOT necessary to register your controllers
// e.g. container.RegisterType<ITestService, TestService>();
RegisterTypes(container);
return container;
}
public static void RegisterTypes(IUnityContainer container)
{
}
}
Run Code Online (Sandbox Code Playgroud)
然后在您的控制器中,为了黑名单安全性,您需要装饰每个动作(或控制器)以将其锁定.
public class HomeController : Controller
{
// This is not secured at all
public ActionResult Index()
{
return View();
}
[ClaimsAuthorizeAccountAccess("read")]
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
[ClaimsAuthorizeAccountAccess("read,edit")]
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
}
Run Code Online (Sandbox Code Playgroud)
对于白名单安全性,您只需要装饰每个人都可以访问的操作,AllowAnonymous或者添加ClaimsIdentityAuthorizeAttribute具有比全局或控制器级别更多或更少限制的权限.
public class HomeController : Controller
{
// This is not secured at all
[AllowAnonymous]
public ActionResult Index()
{
return View();
}
// This is secured by ClaimsAuthorizeAccountAccess (read permission)
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
[ClaimsAuthorizeAccountAccess("read,edit")]
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
}
Run Code Online (Sandbox Code Playgroud)
您不能将依赖项作为构造函数参数注入动作过滤器,因为它们是作为C#中的属性实现的.你需要使用它来解决它们DependencyResolver.Current.这是一种服务定位器,它并不酷,但你真的没有选择.ASP.NET MVC不使用DI容器来创建操作过滤器实例.
public ClaimsAuthorizeAccountAccess(params string[] permissions)
{
_permissions = permissions;
_accountRepository = DependencyResolver.Current.GetService<IAccountRepository>();
}
Run Code Online (Sandbox Code Playgroud)
首先安装官方包,Unity.Mvc而不是Unity.MVC4.这个包自动安装和注册UnityFilterAttributeFilterProvider我们需要它来进行属性的依赖注入.您可以通过查看App_Start> UnityMvcActivator的Start方法来检查Unity是否配置良好.您必须看到以下两行:
public static void Start()
{
// other codes
FilterProviders.Providers.Remove(FilterProviders.Providers.OfType<FilterAttributeFilterProvider>().First());
FilterProviders.Providers.Add(new UnityFilterAttributeFilterProvider(container));
}
Run Code Online (Sandbox Code Playgroud)
现在,您可以将[Dependency]属性添加到过滤器的公共属性.
public class ClaimsAuthorizeAccountAccess : AuthorizeAttribute
{
[Dependency]
public IAccountRepository AccountRepository { get; set; }
private String[] _permissions { get; set; }
public ClaimsAuthorizeAccountAccess(params String[] permissions)
{
_permissions = permissions;
}
}
Run Code Online (Sandbox Code Playgroud)