Check annotation schema from base class - c#

Is there any way to check schema annotation from a base class?
Is simple to explain throw an example:
I have this 2 controllers
[Authorize]
public class HomeController : _baseController
{
//Some actions here
}
[AllowAnonymous]
public class OtherController : _baseController
{
//Some actions here
}
Then I have this base class which overrides OnActionExecuting. The objective is to perform some action if the controller has an annotation.
public class _baseController
{
public override void OnActionExecuting(ActionExecutingContext context)
{
base.OnActionExecuting(context);
if(context.Controller.hasAnnotation("Authorize")){
//do something
}
else if(context.Controller.hasAnnotation("AllowAnonymous")){
//do something
}
}
}
Obviously context.Controller.hasAnnotation is not a valid method. But you get the idea.

Further to my comment above, I have tested the following solution in ASP.Net Core 3.
public override void OnActionExecuting(ActionExecutingContext context)
{
var allowAnonAttr = Attribute.GetCustomAttribute(context.Controller.GetType(), typeof(AllowAnonymousAttribute));
if(allowAnonAttr != null)
{
// do something
}
}
In older versions of ASP.NET, you also have to reference System.Reflection to make use of the GetCustomAttribute extension.
Note that this solution works for attributes placed on the controller class itself (as asked in the question), but will not work for attributes placed on action methods. To get it to work for action methods, the below works:
public override void OnActionExecuting(ActionExecutingContext context)
{
var descriptor = context.ActionDescriptor as ControllerActionDescriptor;
var actionName = descriptor.ActionName;
var actionType = context.Controller.GetType().GetMethod(actionName);
var allowAnonAttr = Attribute.GetCustomAttribute(actionType, typeof(AllowAnonymousAttribute));
if(allowAnonAttr != null)
{
// do something
}
}

As suggested in the comments, the below should work for you:
public class _baseController
{
public override void OnActionExecuting(ActionExecutingContext context)
{
base.OnActionExecuting(context);
System.Attribute[] attrs = System.Attribute.GetCustomAttributes(context.Controller.GetType());
}
}

Related

Add OutputFormatters for selected Action only

There is an option to add new OutputFormatters globally for all controllers, but how to add OutputFormatters for selected Action?
builder.Services.AddControllers(options =>
{
options.OutputFormatters.Insert(0, new CsvOutputFormatter(new CsvFormatterOptions { CsvDelimiter = "," }));
})
I have found IResultFilter and it has the method OnResultExecuting where formatted can be added. After that, I can decorate the Action with a new ResultFilter attribute. Is this the correct way to assign custom formats for certain actions?
public class CsvResultFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
if (context.Result is ObjectResult objectResult)
{
var csvOutputFormatter = new CsvOutputFormatter(new CsvFormatterOptions { CsvDelimiter = "," });
objectResult.Formatters.Add(csvOutputFormatter);
}
}
public void OnResultExecuted(ResultExecutedContext context)
{
}
}
Another problem I am facing is if I decorate Action with attribute I get an exception that the service is not registered.
[ServiceFilter(typeof(CsvResultFilter))]
public async Task<ActionResult> Statistic([FromQuery] ExportStatisticParams model, CancellationToken cancellationToken)
{
}
If I register it in builder.Services.AddControllers(options => options.Filters.Add(typeof(CsvResultFilter))) the CsvResultFilter filter gets executed on all actions.
There's a couple of things you need to do to make this work. If you really need to use ServiceFilterAttribute then you need to ensure:
The output formatter type registered in the DI container (e.g. builder.Services.AddSingleton<CsvOutputFormatter>();
The formatter must also implement IFilterMetadata.
However, there's another way which I think is a little cleaner. You can create your own attribute that inherits from ActionFilterAttribute. Something like this:
public class CsvOutputAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
if (context.Result is ObjectResult objectResult)
{
// You could also use DI here with
// context.HttpContext.RequestServices.GetService<CsvOutputFormatter>();
var formatter = new CsvOutputFormatter(...);
//objectResult.Formatters.Clear(); //<- You may want to add this?
objectResult.Formatters.Add(formatter);
}
}
}
And decorate your action or controller with it:
[CsvOutput]
public IActionResult MyAction()
{
return new ObjectResult("hello");
}

Pass TempData to ActionFilter RedirectToAction

I want to set a ViewBag for second an action from the first action by using ActionFilter.
In the first Action i do the following :
TempData["Test"] = "Test";
return RedirectToAction("Action2", new { values = values });
Then in IActionFilter :
public class HelpertestActionFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
}
public void OnActionExecuting(ActionExecutingContext context)
{
var controller = context.Controller as Controller;
if (controller != null)
{
if (controller.TempData["Test"] != null)
{
controller.ViewBag.Notification = controller.TempData["Test"];
}
}
}
}
But in ActionFilter OnActionExecuting, TempData["Test"] is always null.
I have followed this article
After some try, there is no errors in my code, except in the startup configuration.
In Startup.Configure() the app.UseCookiePolicy() has to be after app.UseMVC() to work as expected.

Why is it so complicated to get the ActionName in ApiController? Easier way?

An MVC controller getting the action name and controller name:
public class AuthorizeController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
string actionName = filterContext.ActionDescriptor.ActionName;
string controllerNamespace = filterContext.ActionDescriptor.ControllerDescriptor.ControllerType.FullName;
//..more code
base.OnActionExecuting(filterContext);
}
}
Pretty straight forward.
But when I have an ApiController (System.Web.Http.ApiController), things are more complicated:
Eventually with the help of some rsharper tips I was able to reduce it to a 'few' lines.
private string GetActionName(HttpControllerContext context)
{
var httpRouteDataCollection = context.RouteData.Values.Values;
var httpRouteDataCollection2 = httpRouteDataCollection.FirstOrDefault();
if (!(httpRouteDataCollection2 is IHttpRouteData[] httpRouteData))
{
return null;
}
IHttpRouteData routeData = httpRouteData.FirstOrDefault();
var httpActionDescriptorCollection = routeData?.Route.DataTokens["actions"];
if (!(httpActionDescriptorCollection is HttpActionDescriptor[] httpActionDescriptor))
{
return null;
}
HttpActionDescriptor reflectedHttpActionDescriptor = httpActionDescriptor.FirstOrDefault();
return reflectedHttpActionDescriptor?.ActionName;
}
Can't it be done easier?
Reason for asking this is because currently I am implementing a generic way of determining who can open what action. Some actions are within an WebApi and every time I would need to perform above 'querying'. So this whole conversion things eat up some performance time.
The WHY?
Without going in to much detail, let just assume you have 40 MVC controllers and 20 API controllers with each about 5-10 actions. All of them are stored in the database (loop through them on startup) and can be linked to an Identity role. An admin is able to choose the actions a certain role can perform. After receiving the first answers I might not be clear enough why I would like to create an controller override where I want to do the programming only once.
One of the potential solutions might be an ActionFilterAttribute:
public class ValidateAccessAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var actionName = actionContext.ActionDescriptor.ActionName;
......
base.OnActionExecuting(actionContext);
}
}
And then on your controllers:
[ValidateAccess]
public async Task<IHttpActionResult> Stuff()
You can even pass arguments to those attributes and have them "smart", like for each action will belong to a certain group and validation of access will be based on a group rather action name. Which can be hard to maintain.
Eg
public class ValidateAccessAttribute2 : ActionFilterAttribute
{
private readonly FunctionalArea _area;
public ValidateAccessAttribute2(FunctionalArea area)
{
_area = area;
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
base.OnActionExecuting(actionContext);
if (!actionContext.Request.Headers.Contains(AuthorizationHeaders.UserNameHeader))
{
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden);
return;
}
var userName = actionContext.Request.Headers.GetValues("UserNameHeader").First();
if (!UserCanAccessArea(userName, _area))
{
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden);
return;
}
}
}
[ValidateAccess2(FunctionalArea.AccessToGenericStuff)]
public async Task<IHttpActionResult> Stuff()
Why don't You use ActionContext and ControllerContext?
public class ValuesController : ApiController
{
[HttpGet]
[AllowAnonymous]
public IHttpActionResult Get()
{
var actionName = this.ActionContext.ActionDescriptor.ActionName;
var controlerName = this.ControllerContext.ControllerDescriptor.ControllerName;
return this.Ok();
}
}

How to prevent action execution in OnActionExecuting of class that is inhereted from the Controller

I need to check some conditions from request before action and decide whether to perform the action or not. I need this for all the actions of controller. For this, I make base controller inherited from standart MVC Controller class.
public abstract class BaseController : Controller
{
public override void OnActionExecuting(ActionExecutingContext context)
{
//some code
...
if(condition)
{
//not executeAction
}
base.OnActionExecuting(context);
}
I have quickly found the answer. You need set Context.Result property.
For instance.
public override void OnActionExecuting(ActionExecutingContext context)
{
if(context.HttpContext.Request.Headers.ContainsKey("key"))
{
context.HttpContext.Response.StatusCode = 401;
NotExecuteAction = true;
}
if (NotExecuteAction)
{
context.Result = NoContent();
}
base.OnActionExecuting(context);
}

Why is my ASP.NET Web API ActionFilterAttribute OnActionExecuting not firing?

I'm trying to implement what's seen here: http://www.piotrwalat.net/nhibernate-session-management-in-asp-net-web-api/ but I'm having an issue with my NhSessionManagementAttribute.
I've set breakpoints on my OnActionExecuting(HttpActionContext actionContext) to see whether the function was ever being called -- it wasn't.
I double-checked my global.asax.cs file & found I am in fact registering the ActionFilter with:
GlobalConfiguration.Configuration.Filters.Add(new NhSessionManagementAttribute());
I have also decorated both my controller class itself, as well as its actions with the attribute to no avail:
public class ClientsController : ApiController {
static readonly ClientRepository repository = new ClientRepository();
[NhSessionManagement]
public IEnumerable<Client> GetAllClients() {
return repository.GetAll();
}
[NhSessionManagement]
public Client GetClient(int id) {
Client client = repository.Get(id);
if (client == null) {
throw new HttpResponseException(
new HttpResponseMessage(HttpStatusCode.NotFound)
);
}
return client;
}
}
Why would this action filter not be firing any of the events within?
If you're working in a project contains both MVC and WebAPI assembilies, could you check what's the namespace your ActionFilterAttribute's namespace. It's fairly confusing cause there are two ActionFilterAttributes under both:
WebAPI: System.Web.Http.Filters
MVC: System.Web.Http.Mvc
The answer above definitely helped me - to save others some time... here is explicitly the difference.
Standard MVC Controllers use:
// System.Web.Mvc
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
}
OData HTTP Controllers use:
// System.Web.Http.Filters;
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
base.OnActionExecuted(actionExecutedContext);
}
For anyone else who comes across this, ActionFilterAttribute will not fire when calling YourController.YourAction from your UnitTest.
[TestMethod]
public void RevokeSiteAdmin_SessionOver()
{
FakeDbContext db = new FakeDbContext();
YourController controller = new YourController(db);
var result = controller.YourAction();
//Some Assertions
}
In the TestMethod above, any ActionFilterAttributes on YourController.YourAction will not be called. However; if you call YourController.YourAction from a browser, your ActionFilterAttribute will be called.
This is true for at least WebApi, but I don't know if it applies to MVC.
Here is the complete Implementation:
public class AllowCrossSiteJsonAttribute : System.Web.Mvc.ActionFilterAttribute
{
public override void OnActionExecuted(System.Web.Mvc.ActionExecutedContext filterContext)
{
if (filterContext.HttpContext != null && filterContext.HttpContext.Response != null && filterContext.HttpContext.Request != null && filterContext.HttpContext.Request.UrlReferrer != null)
{
var allowedCrossDomains = TypeSafeConfigurationManager.GetValueString("allowedCrossDomains", "none");
var allowedHosts = allowedCrossDomains.Split(',');
var requestHost = filterContext.HttpContext.Request.UrlReferrer.GetLeftPart(UriPartial.Authority);
if (allowedHosts.Contains(requestHost.ToLower()))
{
filterContext.HttpContext.Response.Headers.Add("Access-Control-Allow-Origin", requestHost);
}
}
base.OnActionExecuted(filterContext);
}
}
public class AllowCrossSiteJsonForWebApiAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
if (actionExecutedContext.Response != null && actionExecutedContext.Request != null &&
actionExecutedContext.Request.Headers.Referrer != null)
{
var allowedCrossDomains = TypeSafeConfigurationManager.GetValueString("allowedCrossDomains", "none");
var allowedHosts = allowedCrossDomains.Split(',').ToList();
var requestHost = actionExecutedContext.Request.Headers.Referrer.GetLeftPart(UriPartial.Authority);
if (allowedHosts.Contains(requestHost.ToLower()))
{
actionExecutedContext.Response.Headers.Add("Access-Control-Allow-Origin", requestHost);
}
base.OnActionExecuted(actionExecutedContext);
}
}
}
For WebApi, you should install Microsoft.AspNet.WebApi.Core from nuget.
For MVC you can use System.Web.MVC.
My problem was much more simple:
Check your Controller is decorated with <actionPreProcessActivitiesAttribute()> _

Categories

Resources