How to modify ViewComponent result in ASP.NET Core 3.1 - asp.net-core

I want to modify the result of ViewComponent by using a filter as we do with MVC ActionFiltersAttribute. I've tried ActionFilterAttribute but it's not working with ViewComponent even it's not calling.
public class BeforeCheckoutCallFilter : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext context)
{
if (context.RouteData.Values["action"].ToString().Equals("ProductDetails_AttributeChange", StringComparison.InvariantCultureIgnoreCase))
{
//Business logic
}
return;
}
}
I'm registering this filter inside Startup.cs
public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
services.Configure<MvcOptions>(options =>
{
options.Filters.Add<BeforeCheckoutCallFilter>();
}
}
Is there a way to get the ViewComponent result and modify it as we were used to doing with MVC filters?
Updated: I want to intercept the call after returning IViewComponentResult.
Note: I've got know that the ViewComponent does not take part in the controller lifecycle, which means we can’t use filters in a view component.

There is no support for direct interception of ViewComponents, as it does not take part of the request pipeline. From the official doc:
A view component class:
Doesn't take part in the controller lifecycle, which means you can't use filters in a view component
But you can do it indirectly by invoking the ViewComponent from an Action instead. Then decorate the Action with your Filter:
[BeforeCheckoutCall]
public IActionResult Checkout()
{
return ViewComponent("PriorityList", new { maxPriority = 3, isDone = false });
}

Related

IHttpClientFactory using in ActionFilterAttribute [duplicate]

I am trying to inject a service into my action filter but I am not getting the required service injected in the constructor. Here is what I have:
public class EnsureUserLoggedIn : ActionFilterAttribute
{
private readonly ISessionService _sessionService;
public EnsureUserLoggedIn()
{
// I was unable able to remove the default ctor
// because of compilation error while using the
// attribute in my controller
}
public EnsureUserLoggedIn(ISessionService sessionService)
{
_sessionService = sessionService;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
// Problem: _sessionService is null here
if (_sessionService.LoggedInUser == null)
{
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
context.Result = new JsonResult("Unauthorized");
}
}
}
And I am decorating my controller like so:
[Route("api/issues"), EnsureUserLoggedIn]
public class IssueController : Controller
{
}
Startup.cs
services.AddScoped<ISessionService, SessionService>();
Using these articles as reference:
ASP.NET Core Action Filters
Action filters, service filters and type filters in ASP.NET 5 and MVC 6
Using the filter as a ServiceFilter
Because the filter will be used as a ServiceType, it needs to be registered with the framework IoC. If the action filters were used directly, this would not be required.
Startup.cs
public void ConfigureServices(IServiceCollection services) {
services.AddMvc();
services.AddScoped<ISessionService, SessionService>();
services.AddScoped<EnsureUserLoggedIn>();
...
}
Custom filters are added to the MVC controller method and the controller class using the ServiceFilter attribute like so:
[ServiceFilter(typeof(EnsureUserLoggedIn))]
[Route("api/issues")]
public class IssueController : Controller {
// GET: api/issues
[HttpGet]
[ServiceFilter(typeof(EnsureUserLoggedIn))]
public IEnumerable<string> Get(){...}
}
There were other examples of
Using the filter as a global filter
Using the filter with base controllers
Using the filter with an order
Take a look, give them a try and see if that resolves your issue.
Hope this helps.
Global filters
You need to implement IFilterFactory:
public class AuthorizationFilterFactory : IFilterFactory
{
public bool IsReusable => false;
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
// manually find and inject necessary dependencies.
var context = (IMyContext)serviceProvider.GetService(typeof(IMyContext));
return new AuthorizationFilter(context);
}
}
In Startup class instead of registering an actual filter you register your filter factory:
services.AddMvc(options =>
{
options.Filters.Add(new AuthorizationFilterFactory());
});
One more way for resolving this problem. You can get your service via Context as in the following code:
public override void OnActionExecuting(ActionExecutingContext context)
{
_sessionService = context.HttpContext.RequestServices.GetService<ISessionService>();
if (_sessionService.LoggedInUser == null)
{
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
context.Result = new JsonResult("Unauthorized");
}
}
Please note that you have to register this service in Startup.cs
services.AddTransient<ISessionService, SessionService>();
Example
private ILoginService _loginService;
public override void OnActionExecuting(ActionExecutingContext context)
{
_loginService = (ILoginService)context.HttpContext.RequestServices.GetService(typeof(ILoginService));
}
Hope it helps.
After reading this article ASP.NET Core - Real-World ASP.NET Core MVC Filters (Aug 2016) I implemented it like this:
In Starup.cs / ConfigureServices:
services.AddScoped<MyService>();
In MyFilterAttribute.cs:
public class MyFilterAttribute : TypeFilterAttribute
{
public MyFilterAttribute() : base(typeof (MyFilterAttributeImpl))
{
}
private class MyFilterAttributeImpl : IActionFilter
{
private readonly MyService _sv;
public MyFilterAttributeImpl(MyService sv)
{
_sv = sv;
}
public void OnActionExecuting(ActionExecutingContext context)
{
_sv.MyServiceMethod1();
}
public void OnActionExecuted(ActionExecutedContext context)
{
_sv.MyServiceMethod2();
}
}
}
In MyFooController.cs :
[MyFilter]
public IActionResult MyAction()
{
}
Edit: Passing arguments like [MyFilter("Something")] can be done using the Arguments property of the TypeFilterAttribute class: How do I add a parameter to an action filter in asp.net? (rboe's code also shows how to inject things (the same way))
While the question implicitly refers to "filters via attributes", it is still worth highlighting that adding filters "globally by type" supports DI out-of-the-box:
[For global filters added by type] any constructor dependencies will be populated by dependency injection (DI). Adding a filter by type is equivalent to filters.Add(new TypeFilterAttribute(typeof(MyFilter))).
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2#dependency-injection
With regards to attribute-based filters:
Filters that are implemented as attributes and added directly to controller classes or action methods cannot have constructor dependencies provided by dependency injection (DI). This is because attributes must have their constructor parameters supplied where they're applied. This is a limitation of how attributes work.
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2#dependency-injection
However, as mentioned in the previous answers to the OP, there are ways of indirection that can be used to achieve DI. For the sake of completeness, here are the links to the official docs:
ServiceFilterAttribute
TypeFilterAttribute
IFilterFactory implemented on your attribute

ASP.NET MVC 4 Custom Authorize filter on Controller class and Method

I did see this stackoverflowQuestion but this revolves around using the, Authorize attribute. I am using a custom authorize attribute, by extending AuthorizeAttribute.
I want to be able to place this custom filter at the top level of the controller class, but for a couple of methods enforce only a specific role, not both the top level and action method role.
so,
[AuthorizeUser("Transact")]
public class HomeController : Controller
{
//
// GET: /Search/Home/
public ActionResult Index()
{
return View();
}
[AuthorizeUser("Search")]
public ActionResult Search()
{
return View();
}
}
Doing this, the framework will check to see if a user has both the Transact, and Search role.. I just want to check for the search role in this scenario.
I am reusing this search functionality and partialview in another Area.
Going back to the link I posted: stackoverflowQuestion I was able to make it work in my situation. What seems to be happening is the call to my action first looks at the attribute from the Controller level, but using filterContext.ActionDescriptor.IsDefined inside the OnAuthroization method will tell me if the called action has my override attribute attached. If it does it skips calling the base.OnAuthorization method, then the override attribute will be invoked.
So, what I had to do was create the override class and extend my custom authorization class. I have a flag declared in the parent and set in the override class's constructor to tell me if the override authorization method is calling the authorization methods of my custom authorization class.
Here is the example to make sense of it all.
public class AuthorizeUserAttribute : AuthorizeAttribute
{
protected bool isOverrideAuthorize = false;
public AuthorizeUserAttribute(params...)
{
}
public AuthorizeUserAttribute(MenuItems...)
{
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
var action = filterContext.ActionDescriptor;
if (action.IsDefined(typeof(OverrideAuthorizeUserAttribute), true) && !isOverrideAuthorize)
{
return;
}
base.OnAuthorization(filterContext);
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
}
}
The override class:
public class OverrideAuthorizeUserAttribute : AuthorizeUserAttribute
{
public OverrideAuthorizeUserAttribute(params...) : base(roles)
{
base.isOverrideAuthorize = true;
}
public OverrideAuthorizeUserAttribute(MenuItems...) : base(item)
{
base.isOverrideAuthorize = true;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return base.AuthorizeCore(httpContext);
}
}
This allowed me to do authorization on the action solely on the authorization attribute declared on that action, rather than the authorization attribute on the controller AND action method, as defaulted by the framework.
Also, MVC5 seems to have this problem covered by including a, "OverrideAuthorization" attribute.. Unfortunately I am still on MVC4.
You can try at the top of controller like bellow this will support multiple role for single controller or you can use same Authorize(Roles = "Admin") filter top of every action....
[Authorize(Roles = "Admin,HRManager,Finance")]
Public class MyController:Controller{
// inside controller action methods
}

Conditionally use custom middleware

I created my custom authentication middleware in asp. net core project, and registered it as shown below:
public class MyAuthenticationMidleware
{
private readonly RequestDelegate _next;
public ConnectAuthenticationMidleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
if (!UserIsAuthenticated())
{
context.Response.StatusCode = 401;
return;
}
...
await _next.Invoke(context);
}
}
public static class MyAuthenticationMidlewareExtensions
{
public static IApplicationBuilder UseMyAuthentication(this IApplicationBuilder builder)
{
return builder.UseMiddleware<MyAuthenticationMidleware>();
}
}
In Startup:
public void Configure(...)
{
app.UseStaticFiles();
app.UseMyAuthentication();
app.UseMvc();
}
This works correctly - authentication middleware is run for each request. If user is not authenticated, 401 is returned. Otherwise specific mvc action is invoked.
What I tried to do was to prevent the authentication middleware from running for some specific actions. I used MapWhen method to create another extension method and used it as follows:
public static class MyAuthenticationMidlewareExtensions
{
public static IApplicationBuilder UseMyAuthentication(this IApplicationBuilder builder)
{
return builder.UseMiddleware<MyAuthenticationMidleware>();
}
public static IApplicationBuilder UseMyAuthenticationWhen(this IApplicationBuilder builder, Func<HttpContext, bool> predicate)
{
return builder.MapWhen(predicate, applicationBuilder => applicationBuilder.UseMyAuthentication());
}
}
public void Configure(...)
{
app.UseStaticFiles();
app.UseMyAuthenticationWhen(context => context.Request.Path != "xyz");
app.UseMvc();
}
Unfortunately, this doesn't work as expected. The middleware is invoked only when path is different than "xyz", but it seems that it short-circuts the whole chain - no mvc specific actions or filters are invoked.
Probably my understanding of MapWhen is incorrect. Is there any way to get the result I want?
MapWhen creates a new pipeline branch when the supplied predicate is true, and that branch does not rejoin with the main branch where you have UseMvc().
You can change your extension method to use UseWhen instead of MapWhen. UseWhen rejoins with the main pipeline so that your UseMvc() will still get called.
Note: While the above link references aspnet-contrib, the UseWhen extension method is now part of Microsoft.AspNetCore.Http.Abstractions.
This allows you to keep UseMvc() explicit in your Configure method instead of hidden away in your authentication extension method, where it really has no business being.
MapWhen is used to seperate middleware pipeline. If you want to use mvc for branced pipeline you need to add separetely. So you should use .UseMvc(); in extension method like below:
public static IApplicationBuilder UseMyAuthenticationWhen(this IApplicationBuilder builder, Func<HttpContext, bool> predicate)
{
return builder.MapWhen(predicate, applicationBuilder =>
{
applicationBuilder.UseMyAuthentication();
applicationBuilder.UseMvc();
});
}
However i wouldn't go with your way. For authentication middleware i would implement my own middleware like Simple token based authentication/authorization in asp.net core for Mongodb datastore and use Authorize attribute for authorization mvc actions.

How to inject HttpHeader value in controller?

I have Web API developed using ASP.NET Core API. Every incoming request has a custom header value inserted. eg x-correlationid. The controller use this value for logging and tracing the request.
Currently I'm reading the value in each controller as below
[Route("api/[controller]")]
public class DocumentController : Controller
{
private ILogger<TransformController> _logger;
private string _correlationid = null;
public DocumentController(ILogger<DocumentController > logger)
{
_logger = logger;
_correlationid = HttpContext.Request.Headers["x-correlationid"];
}
[HttpPost]
public async Task<intTransform([FromBody]RequestWrapper request)
{
_logger.LogInformation("Start task. CorrelationId:{0}", _correlationid);
// do something here
_logger.LogInformation("End task. CorrelationId:{0}", _correlationid);
return result;
}
}
I think this is against DI rules.
Instead of reading the value inside the controller's constructor, I want to inject the value in the controller's constructor.
Or
Can middleware read the x-correlationid and *somehow* make it available to all the controllers so we don't have to inject it in any controller?
What would be a better option here?
Instead of reading the value inside the controller's constructor, I want to inject the value in the controller's constructor.
You can't inject the value itself into the constructor of the api controller, because at the time of construction the HttpContext is going to be null.
One "injection-style" option would be to use the FromHeaderAttribute in your actions:
[HttpPost]
public async Task<int> Transform(
[FromBody]RequestWrapper request,
[FromHeader(Name="x-correlationid")] string correlationId)
{
return result;
}
Can middleware read the x-correlationid and somehow make it available to all the controllers so we don't have to inject it in any controller?
I think a middleware solution would probably be overkill for what you need. Instead, you can create a custom base class that derives from Controller and have all your Api controllers derive from that.
public class MyControllerBase : Controller
{
protected string CorrelationId =>
HttpContext?.Request.Headers["x-correlationid"] ?? string.Empty;
}
[Route("api/[controller]")]
public class DocumentController : MyControllerBase
{
private ILogger<TransformController> _logger;
public DocumentController(ILogger<DocumentController> logger)
{
_logger = logger;
}
[HttpPost]
public async Task<intTransform([FromBody]RequestWrapper request)
{
_logger.LogInformation($"Start task. CorrelationId:{CorrelationId}");
// do something here
_logger.LogInformation($"End task. CorrelationId:{CorrelationId}");
return result;
}
}
This is what I came up with. I think i can also unit test it.
public interface IRequestContext
{
string CorrelationId { get; }
}
public sealed class RequestContextAdapter : IRequestContext
{
private readonly IHttpContextAccessor _accessor;
public RequestContextAdapter(IHttpContextAccessor accessor)
{
this._accessor = accessor;
}
public string CorrelationId
{
get
{
return this._accessor.HttpContext.Request.Headers[Constants.CORRELATIONID_KEY];
}
}
}
then in startup's configureservice method register the adapter
services.AddSingleton<IRequestContext, RequestContextAdapter>();
and inject it in controller
[Route("api/[controller]")]
public class DocumentController : Controller
{
private ILogger<TransformController> _logger;
private IRequestContext _requestContext = null;
public DocumentController(ILogger<DocumentController > logger,IRequestContext requestContext)
{
_logger = logger;
_requestContext = requestContext;
}
[HttpPost]
public async Task<intTransform([FromBody]RequestWrapper request)
{
_logger.LogInformation("Start task. CorrelationId:{0}", _requestContext.CorrelationId);
// do something here
_logger.LogInformation("End task. CorrelationId:{0}", _requestContext.CorrelationId);
return result;
}
}
Depending on your needs one of following is suitable:
If you need your header values at action level, then using FromHeaderAttribute sounds better (lighter and easier).
If you need to use this header value in lower layers like Repository or DAL, which will be instantiated before Controller has been initialized, then consider to use middleware to get header values initialized and available for other components.

Inject validator in ASP.NET core filter

I have the following ASP.NET Core controller action:
public async Task<IActionResult> Post([FromBody]Model model) {
IValidator<Model> validator = new Validator<Model>();
if (!validator.IsValid)
return await validation.ToErrorResponse();
// Remaining code
}
I configured ASP.NET core to inject IValidator on Startup.
I would like to make the validation automatic using a filter:
public class ValidateAttribute : ActionFilterAttribute {
public overrideTask OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next) {
}
}
Inside the filter I need to request the correct Validator according to the Model being submitted and returning the errors.
How can I get the validator and the model being submitted inside the filter?