In my Application_Start method I'm doing some setup and logging to my database. I had an issue where my connectionstring was wrong, which is not a big deal but I'd like to validate the database is available during Application_Start() and report back to the user if it's down.
Since the httpResponse isn't yet available I can't write something to the browser.
What other practical options do I have?
Here's a quick implementation of my suggestion.
Modify global.asax to have a public variable
public class MvcApplication : System.Web.HttpApplication
{
public static bool IsConfigured { get; set; }
Set IsConfigured = true as you leave Application_Start if everything is configured
Then add a ActionFilter like this
public class ConfiguredAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (MvcApplication.IsConfigured) return;
filterContext.Result = new ViewResult
{
ViewName = "Offline",
TempData = filterContext.Controller.TempData
};
}
}
Create your Offline view in the Shared views folder
Register your new filter
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new ConfiguredAttribute());
}
}
Related
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();
}
}
I have my controller SomeController and its inherited from ApiController, also, I have an ActionFilter:
FilterConfig
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
//filters.Add(new HandleErrorAttribute());
GlobalConfiguration.Configuration.Filters.Add(new LogExceptionFilterAttribute());
}
}
ErrorLogService
public static class ErrorLogService
{
public static void LogError(Exception ex, string metodo, string clase)
{
Utilidades.EnviarErrorHTTP(ex, null, metodo, clase);
}
}
LogExceptionFilterAttribute
public class LogExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
//TODO
}
}
Well, the session is handled by the ApiController and in my SomeController I can use it like:
var session = TokenUser;
But, there nothing in my ErrorLogService to invoke the function to know the token.
Is there a way to share this variable if it is different in each session?
(TokenUser is an object).
I found a way to do it.
In the Global.asax you must add the next code:
protected void Application_PostAuthorizeRequest()
{
System.Web.HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
}
And now, you are available to use Session:
var session = System.Web.HttpContext.Current.Session;
session["token"] = sesion;
And the variable session would persist in the application.
I have a custom RequireHttpsAttribute and a custom AuthorizeAttribute that I apply in FilterConfig to ensure that all controllers uses HTTPS and authorizes in the same way.
I also have a controller action that need some other authorization. In that case I must first use [OverrideAuthorization] to override the global authorization filter, and then I can set the special authorization for this action.
But [OverrideAuthorization] will also override the CustomRequireHttpsAttribute since that also inherts from IAuthorizationFilter. What can I do so that I don't have to readd the CustomRequireHttpsAttribute attribute every time I override the authorization?
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CustomRequireHttpsAttribute());
filters.Add(new CustomAuthorizeAttribute(Role = "User"));
}
}
public class MyController : BaseController
{
public ActionResult DoSomeUserStuff()
{
}
[OverrideAuthorization]
[CustomRequireHttpsAttribute]
[CustomAuthorizeAttribute(Role = "Admin")]
public ActionResult DoSomeAdminStuff()
{
}
}
I ended up creating my own custom IFilterProvider based on a modified version of this post.
I added an extra attribute that I can use for those Controllers or Actions where I want to override the attribute that is set globally. It really does nothing else than extend CustomAuthorizeAttribute so that it carries the same functionality:
public class OverrideCustomAuthorizeAttribute : CustomAuthorizeAttribute {}
Then I create an IFilterProvider that checks the presence of any OverrideCustomAuthorizeAttributes in the list of filters. If so, remove all CustomAuthorizeAttributes from the list:
public class CustomFilterProvider : IFilterProvider
{
private readonly FilterProviderCollection _filterProviders;
public CustomFilterProvider(IList<IFilterProvider> filters)
{
_filterProviders = new FilterProviderCollection(filters);
}
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
var filters = _filterProviders.GetFilters(controllerContext, actionDescriptor).ToArray();
var shouldOverrideCustomAuthorizeAttribute = filters.Any(filter => filter.Instance is OverrideCustomAuthorizeAttribute);
if (shouldOverrideCustomAuthorizeAttribute)
{
// There is an OverrideCustomAuthorizeFilterAttribute present, remove all CustomAuthorizeAttributes from the list of filters
return filters.Where(filter => filter.Instance.GetType() != typeof(CustomAuthorizeAttribute));
}
return filters;
}
}
I register this IFilterProvider in Global.asax.cs:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
// Some other stuff first....
var providers = FilterProviders.Providers.ToArray();
FilterProviders.Providers.Clear();
FilterProviders.Providers.Add(new CustomFilterProvider(providers));
}
}
And I register the global CustomAuthorizeAttribute in FilterConfig.cs just like before:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CustomRequireHttpsAttribute());
filters.Add(new CustomAuthorizeAttribute(Role = "User"));
}
}
The difference is that I use OverrideCustomAuthorizeAttribute in the controller instead:
public class MyController : BaseController
{
public ActionResult DoSomeUserStuff()
{
}
[OverrideCustomAuthorizeAttribute(Role = "Admin")]
public ActionResult DoSomeAdminStuff()
{
}
}
This way, CustomRequireHttpsAttribute is always set globally and never overridden.
According to this page, you can specify which filters to override in the OverrideAuthorizationAttribute.FiltersToOverride Property.
public class MyController : BaseController
{
public ActionResult DoSomeUserStuff()
{
}
[OverrideAuthorization(FiltersToOverride = typeof(CustomAuthorizeAttribute))]
[CustomAuthorizeAttribute(Role = "Admin")]
public ActionResult DoSomeAdminStuff()
{
}
}
I am trying to intercept all exceptions, but the code is never run. I have tried putting this to GlobalFilters, and also putting it directly on my method.
My Attributes:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class HandleExceptionAttribute : HandleErrorAttribute
{
private ILog log = LogManager.GetLogger(typeof(HandleExceptionAttribute));
public override void OnException(ExceptionContext filterContext)
{
log.Info("inside on exception"); // this never appears
}
}
My class:
public class Tester
{
[HandleException]
public void Except()
{
var asd = 0;
var qwe = 1 / asd;
}
}
Dividing by zero give me an exception, my debugger catches it, I continue, but nothing is written into log file.
The logger works. Other logs appear in file. Even if I disable debugging, it doesn't read the log file, so it's not debuggers fault.
Running this on IIS Express. Windows 7.
EDIT:
Moved the thing to controller. Still not working
public class UserController : ApiController
{
private ILog log = LogManager.GetLogger(typeof(UserController));
[HandleException]
[CheckModelForNull]
[ValidateModelState]
public object Post([FromBody]User user)
{
var asd = 0;
var qwe = 1 / asd;
return new HttpStatusCodeResult(HttpStatusCode.OK);
}
}
ApiControllers do not use HandleErrorAttribute
Should better use ExceptionFilterAttribute
public class NotImplExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
log.error("ERROR",context.Exception);
}
}
http://www.asp.net/web-api/overview/web-api-routing-and-actions/exception-handling
As noted in a comment, Filter attributes only apply to actions in controllers. If you want to also capture errors from other classes or something that happens before the code enters an action, you need to overwrite Application_Error method in Global.asax:
protected void Application_Error(object sender, EventArgs e)
{
log.Info("inside on exception");
}
In WebForm we could write a method in MasterPage.cs and it ran in each request .
e.g:
MasterPage.cs
--------------
protected void Page_Load(object sender, EventArgs e)
{
CheckCookie();
}
How can we do something like this in MVC ?
In ASP.NET MVC you could write a custom global action filter.
UPDATE:
As requested in the comments section here's an example of how such filter might look like:
public class MyActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var fooCookie = filterContext.HttpContext.Request.Cookies["foo"];
// TODO: do something with the foo cookie
}
}
If you want to perform authorization based on the value of the cookie, it would be more correct to implement the IAuthorizationFilter interface:
public class MyActionFilterAttribute : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
var fooCookie = filterContext.HttpContext.Request.Cookies["foo"];
if (fooCookie == null || fooCookie.Value != "foo bar")
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
If you want this action filter to run on each request for each controller action you could register it as a global action filter in your global.asax in the RegisterGlobalFilters method:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new MyActionFilterAttribute());
}
And if you need this to execute only for particular actions or controllers simply decorate them with this attribute:
[MyActionFilter]
public ActionResult SomeAction()
{
...
}
You could use Global.asax Application_AcquireRequestState method which will get called on every request:
protected void Application_AcquireRequestState(object sender, EventArgs e)
{
//...
}