How do I get RouteData in Application_EndRequest - c#

I am building a simple performance logger that hooks in to Application_EndRequest / Application_BeginRequest
I would like to send my logger the name of the action and controller as some sort of key.
How can I access this information? (Don't mind if I have to intercept it earlier on and keep it around in the context)

I know this is an old question, but you can access the requested information using:
HttpContext.Current.Request.RequestContext.RouteData.Values("controller")
HttpContext.Current.Request.RequestContext.RouteData.Values("action")

Not sure that you can.
I poked around the HttpContext.Current and found that on the second (and subsequent requests), the HttpContext.Current.Items collection contains an instance of a System.Web.Routing.UrlRoutingModule.RequestData class. Unfortunately, this class is private so you can't access its data. In the debugger, however, it seems that this contains the information you're looking for (not sure why it doesn't exist on the first request though).
Alternatively, could you just use an action filter and add that to a BaseController class that all of your controllers derive from? Something like:
public class LoggingActionAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
var controllerName = filterContext.Controller.ControllerContext.RouteData.Values["controller"];
var actionName = filterContext.Controller.ControllerContext.RouteData.Values["action"];
}
}
Then create a base controller class with this attribute:
[LoggingAction]
public abstract class BaseController : Controller
{
}

This is working:
protected void Application_BeginRequest(object sender, EventArgs e)
{
var context = new HttpContextWrapper(HttpContext.Current);
var rd = RouteTable.Routes.GetRouteData(context);
// use rd
}

object GetControllerFromContext(HttpContext context) {
object controller = null;
HttpContextBase currentContext = new HttpContextWrapper(context);
UrlHelper urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext);
RouteData routeData = urlHelper.RouteCollection.GetRouteData(currentContext);
if(routeData != null) {
controller = routeData.Values["controller"];
}
return controller;
}

Related

Sending a http response through a custom attribute in dotnet

I'm creating a custom attribute in dotnet that is supposed to check the authorization header. If it is the same as some hard coded string it is supposed to pass but else the user should not be able to use the specified route.
I think I'm getting the response header correctly but I'm not sure how to send a HTTP response if it fails.
public class CustomAuthorization : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext context)
{
var httpContext = context.HttpContext;
string authHeader = httpContext.Request.Headers["Authorization"];
if(authHeader == "Kawaii")
{
return;
//do nothing cause its fine
}
else
{
httpContext.Response.WriteAsync("The authorization header was incorrect, is should be Kawaii");
}
}
}
Any help would be greatly appreciated!
From what you've described, it sounds like you should be using OnActionExecuting instead of OnActionExecuted. Within the body, instead of writing to context.HttpContext.Response, you set context.Result to an ActionResult representing the response
public class CustomAuthorization : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
string authHeader = context.HttpContext.Request.Headers["Authorization"];
if(authHeader == "Kawaii")
return;
context.Result = new UnauthorizedResult();
}
}
However, this approach sounds like a better fit for an AuthorizationFilter instead of an ActionFilter. Have a look at the filter pipeline documentation for a list of the different types of filters and what they do.

How to get params from AuthorizationHandler .NET Core

I am using an authorization handler to put custom authorization in my controller in .net core. How can I get the parameters from the controller and use it to the authorization handler?
In the old .NET I can get the parameters from HttpContext request param like this:
var eventId = filterContext.RequestContext.HttpContext.Request.Params["id"];
I am not sure how can I achieved it in .net core
public class HasAdminRoleFromAnySiteRequirement : AuthorizationHandler<HasAdminRoleFromAnySiteRequirement>, IAuthorizationRequirement
{
public HasAdminRoleFromAnySiteRequirement()
{
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
HasAdminRoleFromAnySiteRequirement requirement)
{
//need to call get param from controller to used in the validation
// something like this
//var eventId = filterContext.RequestContext.HttpContext.Request.Params["id"];
// I tried the suggestion below but I can't get the parameter from routedata
// var mvcContext = context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext;
return Task.FromResult(0);
}
}
In ASP.NET Core 3.0 with endpoint routing enabled, you can get a route parameter value like this:
public class MyRequirementHandler : AuthorizationHandler<MyRequirement>
{
private readonly IHttpContextAccessor _httpContextAccessor;
public MyRequirementHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, MyRequirement requirement)
{
var routeData = _httpContextAccessor.HttpContext.GetRouteData();
var areaName = routeData?.Values["area"]?.ToString();
var area = string.IsNullOrWhiteSpace(areaName) ? string.Empty : areaName;
var controllerName = routeData?.Values["controller"]?.ToString();
var controller = string.IsNullOrWhiteSpace(controllerName) ? string.Empty : controllerName;
var actionName = routeData?.Values["action"]?.ToString();
var action = string.IsNullOrWhiteSpace(actionName) ? string.Empty : actionName;
//...
}
}
In your handler you can do the following
var mvcContext = context.Resource as
Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext;
if (mvcContext != null)
{
// Examine MVC specific things like routing data.
}
If you want parameter values then the authorize attribute pieces run before binding has taking place. Instead you would move to an imperative call, inside your controller. This is basically resource based authorization, your parameter is a resource.
You would inject the authorization service into your controller;
public class DocumentController : Controller
{
IAuthorizationService _authorizationService;
public DocumentController(IAuthorizationService authorizationService)
{
_authorizationService = authorizationService;
}
}
Then write your handler slightly differently;
public class DocumentAuthorizationHandler : AuthorizationHandler<MyRequirement, Document>
{
public override Task HandleRequirementAsync(AuthorizationHandlerContext context,
MyRequirement requirement,
Document resource)
{
// Validate the requirement against the resource and identity.
return Task.CompletedTask;
}
}
You can see this handler takes a document, this can be whatever you like, be it an integer for an ID, or some type of view model.
Then you have access to it inside your HandleRequirementAsync() method.
Finally, you'd call it from within your controller, once binding has taken place;
if (await authorizationService.AuthorizeAsync(
User,
document,
yourRequirement))
{
}
In ASP.NET Core 2.2, you can get a route parameter value like this:
public class MyRequirementHandler : AuthorizationHandler<MyRequirement>
{
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, MyRequirement requirement)
{
var authContext = (AuthorizationFilterContext)context.Resource;
var routeValueOfX = authContext.HttpContext.GetRouteValue("X");
.
.
.
}
}
For future reference, starting .NET Core 5.0, the HttpContext is now passed instead, so you can do:
if (context.Resource is HttpContext httpContext)
{
var value = httpContext.GetRouteValue("key");
}
For .Net 5.0 (If you are using .NET 3.0, 3.1 then it will be better) use following:
public class MyAuthorizationPolicyHandler : AuthorizationHandler<OperationAuthorizationRequirement>
{
public MyAuthorizationPolicyHandler()
{
}
protected async override Task HandleRequirementAsync(AuthorizationHandlerContext context, OperationAuthorizationRequirement requirement)
{
var result = false;
if (context.Resource is Microsoft.AspNetCore.Http.DefaultHttpContext httpContext)
{
var endPoint = httpContext.GetEndpoint();
if (endPoint != null)
{
var attributeClaims = endPoint.Metadata.OfType<MyAuthorizeAttribute>()
//TODO: Add your logic here
}
if (result)
{
context.Succeed(requirement);
}
}
}
Please refer to following related discussion: "context.Resource as AuthorizationFilterContext" returning null in ASP.NET Core 3.0
You can access parameters directly from your handler pretty easily. Now, I'm sure if think works for earlier versions of core (you should update core anyway if you can), but in core 2.0 and beyond, you can cast the context.Resource to an AuthorizationFilterContext in the HandleRequirementAsync method like so
if(context.Resource is AuthorizationFilterContext mvcContext)
{
//do stuff to the mvcContext
}
Then, you can access the parameters like this
var value = mvcContext.HttpContext.Request.Query[key].FirstOrDefault();
where key is the parameter name you are looking for.
Or you could parse the query string like this
var queryString = mvcContext.HttpContext.Request.QueryString.ToString()
var foo = HttpUtility.ParseQueryString(queryString);
var value = foo[key]
again, where key is the parameter name you are looking for.

Implementing IActionFilter

I'm building the below filter:
public class TestflowFilter : FilterAttribute, IActionFilter
{
public void OnActionExecuted(ActionExecutedContext filterContext)
{
var profileId = int.Parse(ClaimsPrincipal.Current.GetClaimValue("UserId"));
var appId = int.Parse(filterContext.RouteData.Values["id"].ToString());
if (profileId != 0 && appId != 0)
{
if (CheckIfValid(profileId, appId))
{
// redirect
filterContext.Result = // url to go to
}
}
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
}
}
I actually only need OnActionExecuted, but since IActionFilter is an interface I have to implement them both. Is it ok to leave OnActionExecuting blank if I don't need anything to happen, or do I need to call a base version that MVC always runs?
Also in the OnActionExecuted method if the CheckIfValid is true I redirect the user, but if not I don't do anything. Is that ok or do I need to set some property on the filterContext instead.
I actually only need OnActionExecuted, but since IActionFilter is an interface I have to implement them both. Is it ok to leave OnActionExecuting blank if I don't need anything to happen, or do I need to call a base version that MVC always runs?
Leaving the method body empty is perfectly acceptable in this case. Looks good!
Also In the OnActionExecuted method if the CheckIfValid is true I redirect the user, but if not I don't do anything, is that ok or do I need to set some property on the filterContext instead.
Your filter is fine. MVC does offer a different abstract base class called ActionFilterAttribute, which implements these interfaces for you to override as needed. There's a nice overview that you can read about here. If you derive from that class, your filter attribute code could be simplified a little bit:
public class TestflowFilter : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var profileId = int.Parse(ClaimsPrincipal.Current.GetClaimValue("UserId"));
var appId = int.Parse(filterContext.RouteData.Values["id"].ToString());
if (profileId != 0 && appId != 0)
{
if (CheckIfValid(profileId, appId))
{
// redirect
filterContext.Result = // url to go to
}
}
}
}

HttpContext is null in abstract class

I am trying to set a session inside a abstract class which is already written. What I am trying to do is;
First I try to check the method type either is get or post. If it is GET method then set the session.
here is the code:
public abstract class BaseAbstractController : Controller
{
public BaseAbstractController()
{
if (this.HttpContext.Request.HttpMethod.ToString() == "GET")
{
this.HttpContext.Session["testsession"] = this.HttpContext.Request.Url.AbsolutePath;
}
}
}
The problem I am facing is, I get the Null exception error and it is because HTTPContext value is null.
Right now there is only one MVC controller that extends from abstract controller.
It is better not doing that in constructor, as constructor should only construct the instance if possible. You can override OnActionExecuting instead.
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
var req = filterContext.RequestContext.HttpContext.Request;
if (req.HttpMethod == "GET")
{
filterContext.RequestContext.HttpContext.Session["testsession"] = req.Url;
}
}
Your problem is that the HttpContext property within Controller that you're referencing isn't set until AFTER the controller has been instansiated - meaning it will not be available within your abstract classes' constructor.
If you need to access the HttpContext object then your best bet is to reference the static instance directly like so: System.Web.HttpContext
public abstract class BaseAbstractController : Controller
{
public BaseAbstractController()
{
if (System.Web.HttpContext.Current.Request.HttpMethod.ToString() == "GET")
{
System.Web.HttpContext.Current.Session["testsession"] = System.Web.HttpContext.Current.Request.Url.AbsolutePath;
}
}
}
Update:
To respond to your comment, if you're trying to access the HttpContext within OnActionExecuted then you should access it via the request context within the ActionExecutedContext argument like this:
protected virtual void OnActionExecuted(ActionExecutedContext filterContext)
{
var context = filterContext.HttpContext;
}
The difference is that at this point the controller has been instantiated and the base controller's HttpContext property has been set. Ultimately all the .NET MVC framework is doing is referencing the System.Web.HttpContext static instance from Controller.HttpContext.

How to call controller method in global.asax?

I want to call controller method in Global.asax. Code is given below.
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
if (User.Identity.IsAuthenticated)
{
//here we can subscribe user to a role via Roles.AddUserToRole()
}
}
This event is in global.asax. I want to call controller method which return user permissions from database. How this is possible to call controller method here after that I will save user permissions in session and my controller constructor? Code is given below.
public class AccountController : Controller
{
private readonly ISecurityService securityService;
public AccountController(ISecurityService securityService)
{
this.securityService = securityService;
}
}
Please guide me.
You could handle this using a custom AuthorizeAttribute. This allows you to place an attribute on the top of any controllers / methods which you require authentication to be successful in order to call. This lets you override AuthorizeCore which you can then use to do any custom authorization you want to perform. You can also save any other information to session from this method.
For example:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
// do your own athorization stuff here
}
}
Then you can decorate you controllers that require the use of this authorization either with your attribute:
[CustomAuthorize]
public class AccountController : Controller
{
}
Or using a base controller:
[CustomAuthorize]
public class BaseAuthController : Controller
{
}
public class AccountController : BaseAuthController
{
}
i Resolve this issue by my self i call service method in global.asax by resolving dependency issue below is the solution of the above problem.
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
if (User.Identity.IsAuthenticated)
{
IUnityContainer container = GetUnityContainer();
ISecurityService securityService = container.Resolve<SecurityService>();
var list = securityService.GetUserRolesandPermissions("1");
}
}
Thank you every one.

Categories