I created a custom ActionFilterAttribute which I call like this :
[ScopeActionFilter(acceptedScopes = new string[] { "Files.Upload" })]
public IActionResult Upload(IFormFile[] files)
{
}
Now, how do I find the value of acceptedScopes in the OnActionExecuting method ? And how do I check that acceptedScopes was passed to the ActionFilter ?
public class ScopeActionFilter : ActionFilterAttribute
{
public string[] acceptedScopes { get; set; }
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
ScopesRequiredByWebApiExtension.VerifyUserHasAnyAcceptedScope(actionContext.HttpContext, actionContext.ActionArguments["acceptedScopes"] as string[]);
}
}
how do I find the value of acceptedScopes in the OnActionExecuting method ?
In your code, we can find that you set the value for acceptedScopes property while you applying the ScopeActionFilter to action method, to get the value of acceptedScopes in the OnActionExecuting method, you can try:
public class ScopeActionFilter : ActionFilterAttribute
{
public string[] acceptedScopes { get; set; }
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
var args = acceptedScopes;
ScopesRequiredByWebApiExtension.VerifyUserHasAnyAcceptedScope(actionContext.HttpContext, args);
}
}
Test Result
string[] ActionArguments = ((ScopeActionFilter)actionContext.Filters.Where(t => t is ScopeActionFilter).First()).acceptedScopes;
will work
Related
I am looking to implement Guid trace mechanism for every method that is being called but I don't want to write this on every method:
Guid TraceId = Guid.NewGuid()
Is there an attribute or some other way that can automatically generate Guid whenever a method is being called and have it stored/available as local variable?
For MVC ApiControllers I used this logic, but I need it to happen for different class types as well.
[TraceActionFilter]
public class TracedApiController : System.Web.Http.ApiController
{
public TracedApiController()
{
}
public string TraceId
{
get
{
return ActionContext.ActionArguments[TraceActionFilterAttribute.TRACE_ID].ToString();
}
}
public Guid TraceIdGuid
{
get
{
return Guid.Parse(TraceId);
}
}
}
public class TraceActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ActionArguments.ContainsKey(TRACE_ID))
{
actionContext.ActionArguments.Add(TRACE_ID, Guid.NewGuid());
}
else actionContext.ActionArguments[TRACE_ID] = Guid.NewGuid();
}
public static string TRACE_ID = "TraceId";
}
I created a custom validation attribute that I want to use for my API controller DTOs. This attribute needs values from the configured options, that's why I'm injecting them in the constructor, so that I can use the options service later on in the IsValid and FormatErrorMessage method.
internal class MyValidationAttribute : ValidationAttribute
{
private readonly IOptionsMonitor<MyOptions> myOptionsMonitor;
public MyValidationAttribute(IOptionsMonitor<MyOptions> myOptionsMonitor)
{
this.myOptionsMonitor = myOptionsMonitor;
}
public override bool IsValid(object value)
{
// ... use myOptionsMonitor here ...
return false;
}
public override string FormatErrorMessage(string name)
{
// ... use myOptionsMonitor here ...
return string.Empty;
}
}
Unfortunately when I want to use this as an attribute in my DTO
internal class MyDTO
{
[MyValidationAttribute]
public string Foo { get; set; }
}
I get the error message
There is no argument given that corresponds to the required formal
parameter 'myOptionsMonitor' of
'MyValidationAttribute.MyValidationAttribute(IOptionsMonitor)'
Is there a way I can use dependency injection for validation attributes? I know that I can use the ValidationContext like so
internal class MyValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
IOptionsMonitor<MyOptions> myOptionsMonitor = validationContext.GetService<IOptionsMonitor<MyOptions>>();
// ...
return ValidationResult.Success;
}
return new ValidationResult("Something failed");
}
}
But I want to use the FormatErrorMessage method from the base class and this has no access to the options service.
My current solution
For now, this is the code I'm using
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
internal class CustomValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
IOptionsMonitor<MyOptions> myOptionsMonitor = validationContext.GetService<IOptionsMonitor<MyOptions>>();
Dictionary<string, string> myMap = myOptionsMonitor.CurrentValue.MyMap;
string key = value.ToString() ?? string.Empty;
if (myMap.ContainsKey(key))
return ValidationResult.Success;
string[] formattedKeys = myMap.Keys.Select(key => $"'{key}'").ToArray();
string keysText = string.Join(" / ", formattedKeys);
string errorMessage = $"Invalid value. Valid ones are {keysText}";
return new ValidationResult(errorMessage);
}
}
Attributes are not designed for this purpose. But you can use action filters instead.
Let`s make your attribute as simple as it can be, we don't need any validation logic there.
[AttributeUsage(AttributeTargets.Property)]
public class CustomValidationAttribute : Attribute
{ }
For my example I created service that we are going to inject
public class SomeService
{
public bool IsValid(string str)
{
return str == "Valid";
}
}
and a class that we are going to validate
public class ClassToValidate
{
[CustomValidation]
public string ValidStr { get; set; } = "Valid";
[CustomValidation]
public string InvalidStr { get; set; } = "Invalid";
}
Now we can finally create action filter to validate our properties. In the snippet below, we hook into ASP.NET Core pipeline to execute code just before our controller action executes. Here I get action arguments and try to find CustomValidationAttribute on any property. If it is there, grab the value from the property, cast to type (I simply invoke .ToString()) and pass to your service. Based on value that is returned from service, we continue execution or add error to ModelState dictionary.
public class CustomValidationActionFilter : ActionFilterAttribute
{
private readonly SomeService someService;
public CustomValidationActionFilter(SomeService someService)
{
this.someService = someService;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
var actionArguments = context.ActionArguments;
foreach (var actionArgument in actionArguments)
{
var propertiesWithAttributes = actionArgument.Value
.GetType()
.GetProperties()
.Where(x => x.GetCustomAttributes(true).Any(y => y.GetType() == typeof(CustomValidationAttribute)))
.ToList();
foreach (var property in propertiesWithAttributes)
{
var value = property.GetValue(actionArgument.Value).ToString();
if (someService.IsValid(value))
continue;
else
context.ModelState.AddModelError(property.Name, "ModelState is invalid!!!");
}
}
base.OnActionExecuting(context);
}
}
Don't forget to add your filter to the pipeline in Startup.cs!
services.AddMvc(x =>
{
x.Filters.Add(typeof(CustomValidationActionFilter));
});
Update:
If you strictly want to use dependency injection inside attribute, you could use service locator anti-pattern. For that we need to emulate DependencyResolver.Current from ASP.NET MVC
public class CustomValidationAttribute : ValidationAttribute
{
private IServiceProvider serviceProvider;
public CustomValidationAttribute()
{
serviceProvider = AppDependencyResolver.Current.GetService<IServiceProvider>();
}
public override bool IsValid(object value)
{
// scope is required for scoped services
using (var scope = serviceProvider.CreateScope())
{
var service = scope.ServiceProvider.GetService<SomeService>();
return base.IsValid(value);
}
}
}
public class AppDependencyResolver
{
private static AppDependencyResolver _resolver;
public static AppDependencyResolver Current
{
get
{
if (_resolver == null)
throw new Exception("AppDependencyResolver not initialized. You should initialize it in Startup class");
return _resolver;
}
}
public static void Init(IServiceProvider services)
{
_resolver = new AppDependencyResolver(services);
}
private readonly IServiceProvider _serviceProvider;
public object GetService(Type serviceType)
{
return _serviceProvider.GetService(serviceType);
}
public T GetService<T>()
{
return (T)_serviceProvider.GetService(typeof(T));
}
private AppDependencyResolver(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
}
It should be initialized in Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
AppDependencyResolver.Init(app.ApplicationServices);
// other code
}
This is my code:
// Controller
[HttpGet("{id}")]
[MyFilter]
public async Task<MyCustomType> Load(string id)
{
return new MyCustomType(....);
}
// Custom attribute
public class MyFilterAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
// Can I have my MyCustomType result here?
}
}
I need to implement some special logic in case of specific property values of MyCustomType result.
Public class MyCustomType
{
// assuming that there will be more properties
public int Id { get; set; }
public string Name { get; set; }
}
// Now, Move to Controller method.
public class CustomController : Controller
{
[HttpGet({"id"})]
[MyFilter]
public async Task<MyCustomType> Load(string id)
{
// Do some async operations
// Or do some Db queries
// returning MyCustomType
MyCustomType typeToReturn = new MyCustomType();
typeToReturn.Id = 1;
typeToReturn.Name = "something";
return typeToReturn;
}
}
// Well here goes the attribute
public class MyFilterAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
// you have to do more digging i am using dynamic to get the values and result.
dynamic content = context.Result;
if (content != null)
{
dynamic values = content.Value;
}
}
}
EDIT changed the code and ran it in a dot net core project and i was able to get the values, how ever i have used dynamic you can dig more on it.
I have a TestResult class which is inherited from ActionResult class as you can see in the code below.
public class TestResult : ActionResult
{
public ActionResult InnerResult { get; set; }
public TestResult(ActionResult innerResult)
{
InnerResult = innerResult;
}
public override void ExecuteResult(ActionContext context)
{
var tempDataService = context.HttpContext.RequestServices.GetRequiredService<ITempDataDictionary>();
tempDataService.AddTestObject(new TestClass("TestValue1", "TestValue1"));
tempDataService.AddTestString("TestString1");
InnerResult.ExecuteResult(context);
}
}
i have two objects one is TestClass another one is a random string , i add them to tempDataService, the implementation of the AddTestObject and AddTestString methods is in the code below :
public static class TestExtensions
{
const string TestObject1 = "_Test1";
const string TestObject2 = "_Test2";
public static void AddTestObject(this ITempDataDictionary tempData, TestClass testClass)
{
if (!tempData.ContainsKey(TestObject1))
{
tempData[TestObject1] = new List<TestClass>();
}
((List<TestClass>)tempData[TestObject1]).Add(testClass);
}
public static void AddTestString(this ITempDataDictionary tempData,string testString)
{
tempData[TestObject2] = testString;
}
public static ActionResult WithTestMessages(this ActionResult result)
{
return new TestResult(result);
}
}
now i have a TestController with two actions as you can see below :
public class TestController : Controller
{
private readonly ITempDataDictionary _tempDataDictionary;
public TestController(ITempDataDictionary tempDataDictionary)
{
_tempDataDictionary = tempDataDictionary;
}
public IActionResult TestAction()
{
return RedirectToAction(nameof(TestAction2)).WithTestMessages();
}
public IActionResult TestAction2()
{
return Content("TestAction2");
}
}
the strange thing here is that if i inject the ITempDataDictionary in a controller and go through the QuickWatch window and see the _tempDataDictionary value there is not anything for the TestObject1 and i just see the TestObject2 , the point is that in TestObject1 i expect to see the class object and in the TestObject2 i expect to see the string value.
AFAIK, in the current implementation ITempDataDictionary accepts only primitive values. So you can workaround it by serializing and then deserializing your collection List<TestClass>() to json.
I am working on a Asp.net MVC project and I am wondering if there is a way for the attributes to talk to other attributes.
For example, I have the following attributes
public class SuperLoggerAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
//Do something super
}
}
public class NormalLoggerAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
//Do something normal ONLY if the super attribute isn't applied
}
}
And I have the following controllers
[NormalLogger]
public class NormalBaseController : Controller
{
}
public class PersonController: NormalBaseController
{
}
[SuperLogger]
public class SuperController: NormalBaseControler
{
}
So basically, I want my SuperController to use SuperLogger and ignore NormalLogger (which was applied in the base), and PersonController should use the NormalLogger since it's not "overrided" by the SuperLogger. Is there a way to do that?
Thanks,
Chi
Why not just have SuperLoggerAttribute inherit from NormalLoggerAttribute and override the Log method?
I think this should work:
public enum LoggerTypes
{
Normal,
Super
}
public class LoggerAttribute : ActionFilterAttribute
{
public LoggerAttribute() : base()
{
LoggerType = LoggerTypes.Normal;
}
public LoggerAttribute(LoggerTypes loggerType) : base()
{
LoggerType = loggerType;
}
public LoggerTypes LoggerType
{
get;
set;
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (LoggerType == LoggerTypes.Super)
{
//
}
else
{
//
}
}
Usage:
[Logger(LoggerTypes.Normal)]
or
[Logger(LoggerTypes.Super)]