I have a base AsyncController
BaseController : AsyncController
{
[Authorize("Admin")]
public virtual async Task<ActionResult> SomeMethod()
{
//code
}
}
How it is correct to inheritance and override SomeMethod?
UserController : BaseController
{
[Authorize("User")]
public override Task<ActionResult> SomeMethod()
{
return base.SomeMethod()
}
}
OR
UserController : BaseController
{
[Authorize("User")]
public override async Task<ActionResult> SomeMethod()
{
return await base.SomeMethod()
}
}
P.S. Sorry for my english
SomeMethod() is defined within your BaseController class. Therefore child classes should inherit from BaseController rather than AsyncController. To override the method, just add virtual keyword into the definition.
BaseController : AsyncController
{
[Authorize("Admin")]
public virtual async Task<ActionResult> SomeMethod()
{
//code
}
}
Inheritance and override
UserController : BaseController
{
[Authorize("User")]
public override Task<ActionResult> SomeMethod()
{
return base.SomeMethod()
}
}
Also I have noticed something odd in your Authorize attributes. If the base method is allow to Admin only and the child method is allow to User only, then you most likely end up being unable to execute the base method from the child class.
Related
I tried this answer: [https://stackoverflow.com/questions/18406506/custom-filter-attributes-inject-dependency][1] to implement ActionFilterAttribute (System.Web.Http.Filters) for Web API project (not MVC). But my custom attribute never called in controller. I would be grateful for any advice.
Custom attribute:
public class MyAttribute : FilterAttribute { }
Filter:
public class MyFilter : ActionFilterAttribute
{
private readonly IMyService _myService;
public MyFilter(IMyService myService)
{
_myService = myService;
}
public override async Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
//do some with actionContext
throw new Exception("You can`t go here");
}
}
Controller method:
[My] // Not called
[HttpPost]
[Route("/do-some")]
public async Task DoSome(string myString)
{
//do some
}
Register filter:
public partial class Startup
{
protected void ConfigureApi(IAppBuilder app, IContainer container)
{
var configuration = new HttpConfiguration();
//...
var serviceInstance = container.GetInstance<IMyService>();
configuration.Filters.Add(new MyFilter(serviceInstance));
}
}
Is something wrong here?
Almost everything is fine with the your code, but you should register your filter and service in another way.
In Asp Net Core WebAPI there several ways you can register your filter:
Globally - for all controllers, actions, and Razor Pages. More information in Microsoft documentation
For only one controller/method. More information in Microsoft documentation
Example of global registration:
services.AddControllers(options =>
{
options.Filters.Add(typeof(LoggerFilterAttribute));
});
Example of method registration in Controller:
I want notice - in this case you should use ServiceFilter - this helps DI resolve any dependecines for your filter.
[HttpGet]
[ServiceFilter(typeof(LoggerFilterAttribute))]
public IEnumerable<WeatherForecast> Get()
{
}
This is my simple example for this task:
My SimpleService
public interface ISimpleService
{
void Notify(string text);
}
public class SimpleService : ISimpleService
{
public void Notify(string text)
{
Console.WriteLine($"Notify from {nameof(SimpleService)}. {text}");
}
}
ActionFilterAttribute
public class LoggerFilterAttribute : ActionFilterAttribute
{
private readonly ISimpleService _simpleService;
public LoggerFilterAttribute(ISimpleService simpleService)
{
_simpleService = simpleService;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
_simpleService.Notify($"Method {nameof(OnActionExecuting)}");
}
public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
_simpleService.Notify($"Method {nameof(OnActionExecutionAsync)}");
return base.OnActionExecutionAsync(context, next);
}
}
The main step - you should choose way of registration, because there is main difference between global registration and per controller/method in code.
If you want use this way of registration - you need only register global filter and this is enough. All magic will be do by WebAPI with DI registration.
services.AddControllers(options =>
{
options.Filters.Add(typeof(LoggerFilterAttribute));
});
If you want use registration per controller/method. You need to register your filter in DI. Because without it you will have Exception.
services.AddScoped<LoggerFilterAttribute>();
[HttpGet]
[ServiceFilter(typeof(LoggerFilterAttribute))]
public IEnumerable<WeatherForecast> Get()
{
}
The last step register my service
services.AddTransient<ISimpleService, SimpleService>();
Results
Currently to return view, I have to write one method in all the controllers. That is "Index" action method. It has nothing to do except returning their respective views.
So is it possible to make them common?
I have inherited one common basecontroller which is inherited from apicontroller. So is there a way that I write base index method. and i can override it as well if needed?
You can create a BaseController class like this:
public class BaseController : Controller
{
public virtual IActionResult Index()
{
return View();
}
}
Then inherit your controller clasess from base controller:
public class CustomersController : BaseController
{
}
You can override the Index method like this:
public class HomeController : BaseController
{
public override IActionResult Index()
{
return View("About");
}
}
Here is an example controller to explain the case
[Authorize]
public class AccountController : ControllerBase
{
[AllowAnonymous]
[Authorize(Policy = "SpecificPolicy")]
public string MethodA() {}
public string MethodB() {}
}
MethodA should only be authorized via "SpecificPolicy".
MethodB should be authorized via the Authorized attribute
The issue I'm having is that if I remove the AllowAnonymous attribute then Authorize on the controller takes precedence which I don't want for MethodA.
When I keep AllowAnonymous for MethodA then Authorize(Policy = "SpecificPolicy") is ignored.
When I keep AllowAnonymous for MethodA then Authorize(Policy = "SpecificPolicy") is ignored.
[AllowAnonymous] bypasses all other authorization attributes. When you have it with other authorize attributes at the same time, all other attributes are ignored, even other attributes are the-more-specific method level.
For example:
[AllowAnonymous]
public class DashboardController : Controller
{
[Authorize]
public IActionResult Index()
{
return View();
}
}
/dashboard will be open/public.
The issue I'm having is that if I remove the AllowAnonymous attribute then Authorize on the controller takes precedence which I don't want for MethodA.
When you have multiple authorize attributes, all of them need to be satisfied before you can make the call to the method. In your case, both [Authorize] and [Authorize(Policy = "SpecificPolicy")] must pass before access is granted.
If you don't want [Authorize] to take the precedence, you can only apply it to method B:
public class AccountController : ControllerBase
{
[Authorize(Policy = "SpecificPolicy")]
public string MethodA() {}
[Authorize]
public string MethodB() {}
}
I want to avoid putting specific [Authorize] attributes on actions since that Controller has lots of actions but a single action that has it's own authorize rule.
Then this might be good time for you to separate MethodA into Areas.
For example:
You still have [Authorize] on your AccountController, but just take out the MethodA:
[Authorize]
public class AccountController : ControllerBase
{
public string MethodB() {}
}
Then you create an Area for MethodA:
[Area("specific")]
[Authorize(Policy = "SpecificPolicy")]
public abstract class SpecificControllerBase : ControllerBase
{ }
public class AccountController : SpecificationControllerBase
{
public string MethodA() {}
}
Lastly you need to register the area route in your Startup.cs:
app.UseMvc(routes =>
{
...
routes.MapRoute(
name: "areaRoute",
template: "{area:exists}/{controller=dashboard}/{action=index}/{id?}");
routes.MapRoute(
name: "default",
template: "{controller=home}/{action=index}/{id?}");
});
You could try to implement your own Authorize Attribute with checking the Policy.
Follow Steps below:
AllowAnonymousWithPolicyFilter
public class AllowAnonymousWithPolicyFilter : IAsyncAuthorizationFilter
{
private readonly IAuthorizationService _authorization;
public string Policy { get; private set; }
public AllowAnonymousWithPolicyFilter(string policy, IAuthorizationService authorization)
{
Policy = policy;
_authorization = authorization;
}
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
var authorized = await _authorization.AuthorizeAsync(context.HttpContext.User, Policy);
if (!authorized.Succeeded)
{
context.Result = new ForbidResult();
return;
}
}
}
AllowAnonymousWithPolicyAttribute
public class AllowAnonymousWithPolicyAttribute : TypeFilterAttribute, IAllowAnonymous
{
public AllowAnonymousWithPolicyAttribute(string Policy) : base(typeof(AllowAnonymousWithPolicyFilter))
{
Arguments = new object[] { Policy };
}
}
Use
[Authorize]
public class HomeController : Controller
{
[AllowAnonymousWithPolicy("MyPolicy")]
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";
return View();
}
I have multiple classes (more than 100) which inherit from my base class BaseController. All my classes are their logics and models but format's response (200, 404, 500, ...) are always the same.
But when I inherit from my base class, in my swagger documentation I see my endpoints but the details of the response are not there. How can I do this?
public class BaseController : Controller
{
public BaseController() {}
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK)]
[SwaggerResponse(StatusCodes.Status404NotFound)]
protected async Task<IActionResult> Get(int id)
{
...
}
}
public class MyController : BaseController
{
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
return await base.Get(id).ConfigureAwait(false);
}
}
As #Helder Sepulveda Said, this goes beyond swashbuckle.
I Think you can use IActionModelConvention to simulate inherit the action attributes.
Use Action.Filters like this
public class ActionMethodConvention : IActionModelConvention
{
public void Apply(ActionModel action)
{
var actonBaseResponses = new List<SwaggerResponseAttribute>();//some code to get baseAction reflections
foreach (var attr in actonBaseResponses)
{
action.Filters.Add(new Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute(actonBaseResponses.StatusCode));
}
}
Suppose I have following Controller and action with authorization Attribute:
public class IndexController : Controller
{
//
// GET: /Index/
[Authorize(Roles="Registered")]
public ActionResult Index()
{
return View();
}
}
I've searched over the entire Internet and not found an answer for this simple question: how to get the roles annotated to an especific Action/Controller? In this case: Index Action has: string[] = {"Registered"}
Finally I found the solution! Was more easy than I thought! ahahha I need extend a class from AuthorizeAttribute and use it in actions. The information I need is the attribute "Roles" of the inherited class:
public class CustomAuthorizationAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
var roles = this.Roles;
base.OnAuthorization(filterContext);
}
}
And on Index Controller:
public class IndexController : Controller
{
//
// GET: /Index/
[CustomAuthorizationAttribute(Roles = "Registered")]
public ActionResult Index()
{
return View();
}
}