Is there a way for me to catch all incoming requests to my ASP.NET MVC 4 app and run some code before continuing the request onward to the specified controller/action?
I need to run some custom auth code with existing services, and to do this properly, I'll need to be able intercept all incoming requests from all clients to double check some things with the other service.
The most correct way would be to create a class that inherits ActionFilterAttribute and override OnActionExecuting method. This can then be registered in the GlobalFilters in Global.asax.cs
Of course, this will only intercept requests that actually have a route.
You can use a HttpModule to accomplish this. Here is a sample I use to calculate the process time for all requests:
using System;
using System.Diagnostics;
using System.Web;
namespace Sample.HttpModules
{
public class PerformanceMonitorModule : IHttpModule
{
public void Init(HttpApplication httpApp)
{
httpApp.BeginRequest += OnBeginRequest;
httpApp.EndRequest += OnEndRequest;
httpApp.PreSendRequestHeaders += OnHeaderSent;
}
public void OnHeaderSent(object sender, EventArgs e)
{
var httpApp = (HttpApplication)sender;
httpApp.Context.Items["HeadersSent"] = true;
}
// Record the time of the begin request event.
public void OnBeginRequest(Object sender, EventArgs e)
{
var httpApp = (HttpApplication)sender;
if (httpApp.Request.Path.StartsWith("/media/")) return;
var timer = new Stopwatch();
httpApp.Context.Items["Timer"] = timer;
httpApp.Context.Items["HeadersSent"] = false;
timer.Start();
}
public void OnEndRequest(Object sender, EventArgs e)
{
var httpApp = (HttpApplication)sender;
if (httpApp.Request.Path.StartsWith("/media/")) return;
var timer = (Stopwatch)httpApp.Context.Items["Timer"];
if (timer != null)
{
timer.Stop();
if (!(bool)httpApp.Context.Items["HeadersSent"])
{
httpApp.Context.Response.AppendHeader("ProcessTime",
((double)timer.ElapsedTicks / Stopwatch.Frequency) * 1000 +
" ms.");
}
}
httpApp.Context.Items.Remove("Timer");
httpApp.Context.Items.Remove("HeadersSent");
}
public void Dispose() { /* Not needed */ }
}
}
And this is how you register the module in Web.Config:
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="PerformanceMonitorModule" type="Sample.HttpModules.PerformanceMonitorModule" />
</modules>
<//system.webServer>
I think that what you search for is this:
Application_BeginRequest()
http://www.dotnetcurry.com/showarticle.aspx?ID=126
You put it in Global.asax.cs.
protected void Application_BeginRequest(object sender, EventArgs e)
{
HttpContext.Current.Request.....;
}
I use this for debugging purposes but I am not sure how good solution it is for your case.
I'm not sure about MVC4 but I think it is fairly similar to MVC5. If you have created a new web project -> look in Global.asax and you should see the following line FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); in the method Application_Start().
RegisterGlobalFilters is a method in the file FilterConfig.cs located in the folder App_Start.
As #YngveB-Nilsen said ActionFilterAttribute is the way to go in my opinion. Add a new class that derives from System.Web.Mvc.ActionFilterAttribute. This is important because System.Web.Http.Filters.ActionFilterAttribute will fail with the following exception for example.
The given filter instance must implement one or more of the following
filter interfaces: System.Web.Mvc.IAuthorizationFilter,
System.Web.Mvc.IActionFilter, System.Web.Mvc.IResultFilter,
System.Web.Mvc.IExceptionFilter,
System.Web.Mvc.Filters.IAuthenticationFilter.
Example that writes the request to the debug window:
public class DebugActionFilter : System.Web.Mvc.ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
Debug.WriteLine(actionContext.RequestContext.HttpContext.Request);
}
}
In FilterConfig -> RegisterGlobalFilters -> add the following line: filters.Add(new DebugActionFilter());.
You can now catch all incoming requests and modify them.
Related
I have below handler,
public class ShutdownHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/plain";
context.Response.Write("Currently we are down for mantainance");
}
public bool IsReusable
{
get { return false; }
}
}
What web config setting is required to call this handler on every request of a Asp.net MVC application??
I tried this with some code, but not able to call on every request,
routes.Add(new Route("home/about", new ShutDownRouteHandler()));
public class ShutDownRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new ShutdownHandler();
}
}
You first need a module to map your handler:
public class ShutDownModule : IHttpModule
{
public void Init(HttpApplication app)
{
app.PostResolveRequestCache += (src, args) => app.Context.RemapHandler(new ShutDownHandler());
}
public void Dispose() { }
}
And then in your web.config:
<system.webServer>
<modules>
<add name="ShutDownModule" type="YourNameSpace.ShutDownModule" />
</modules>
</system.webServer>
MVC is an endpoint handler, just like WebForms. You are saying, "hey don't call MVC handler, call this instead".
For that you need to intercept the mapping that would have occurred and invoked MVC, and instead invoke your own handler. To intercept an event in the pipeline we use HttpModules and register them as above.
So you're effectively turning MVC off as the request never gets there.
Right now I've got my validators hooked up and building in my app, but every time we add a new validator we need to manually go into our Unity configuration and register the type. I'd like to do this automatically, much like this blog post describes doing with StructureMap, only for Unity instead.
Right now I've got something like this:
// in global.asax.cs
protected void Application_Start(Object sender, EventArgs e)
{
// some irrelevant registrations (area registrations, route config, etc)
var container = new UnityContainer();
UnityConfig.RegisterComponents(container);
FluentValidationModelValidatorProvider.Configure(c => c.ValidatorFactory = new UnityValidatorFactory(container));
}
public class UnityValidatorFactory : ValidatorFactoryBase
{
private readonly IUnityContainer container;
public UnityValidatorFactory(IUnityContainer container)
{
this.container = container;
}
public override IValidator CreateInstance(Type validatorType)
{
if (container.IsRegistered(validatorType))
{
return container.Resolve(validatorType) as IValidator;
}
return null;
}
}
public static class UnityConfig
{
public static void RegisterComponents(IUnityContainer container)
{
container.RegisterTypes(
AllClasses.FromAssemblies(
Assembly.GetAssembly(typeof (UnityConfig)),
WithMappings.FromMatchingInterface, WithName.Default);
RegisterValidators(container);
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
}
private static void RegisterValidators(IUnityContainer container)
{
container.RegisterType<IValidator<MyFirstViewModel>, MyFirstViewModelValidator>();
container.RegisterType<IValidator<MySecondViewModel>, MySecondViewModelValidator>();
}
}
What I have is working, but I have to keep adding registrations to RegisterValidators() every time a new validator is created. Is there a way I can set this up to automatically detect and register all validators?
This turned out to be pretty easy once I figured out what I was doing, which maybe explains why Googling for the answer was yielding no results. I rewrote RegisterValidators as follows:
private static void RegisterValidators(IUnityContainer container)
{
var validators = AssemblyScanner.FindValidatorsInAssemblyContaining<OneOfMyValidators>();
validators.ForEach(validator => container.RegisterType(validator.InterfaceType, validator.ValidatorType));
}
You can let FV setup do the job for you, please refer to 1st comment here for more details from the contributers
public IServiceProvider ConfigureServices( IServiceCollection services )
{
services.AddAuthorization(options =>...);
services.AddMvc()
.AddFluentValidation( o =>
{
o.RegisterValidatorsFromAssemblyContaining<Startup>();
} );
How do I implement a global exception handler in MVC4 as it seems to be different from MVC3.
Not sure how to implement the following:
public class ErrorHandlerAttribute: System.Web.Mvc.FilterAttribute,
IExceptionFilter
{
public Task ExecuteExceptionFilterAsync(
HttpActionExecutedContext actionExecutedContext,
CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
Unfortunately the link provided in Eric Leschinski's commet only shows how to implement the System.Web.Mvc.IExceptionFilter interface, and not the System.Web.Http.Filters.IExceptionFilter interface. The first is used in regular MVC controllers, while the second targets ApiCotrollers.
Here is a simple class example I came up with for logging unhandled exceptions thrown in my ApiControllers:
public class ExceptionLoggerFilter: IExceptionFilter
{
public ExceptionLoggerFilter(Logger logger)
{
this.logger = logger;
}
public bool AllowMultiple { get { return true; } }
public Task ExecuteExceptionFilterAsync(
HttpActionExecutedContext actionExecutedContext,
CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() =>
{
logger.Error("web service error", actionExecutedContext.Exception);
}, cancellationToken);
}
private Logger logger;
}
And all you have to do to enable this filter is register it in yours Global.asax Application_Start method:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
// allocate filter and add it to global configuration
var exceptionLogger = new ExceptionLoggerFilter(Container.Get<Logger>());
GlobalConfiguration.Configuration.Filters.Add(exceptionLogger);
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
I hope this helps other googlers out there!
The way I created an exception handler for MVC part of it, I created a class that implemented IExceptionFilter
public class MVCExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
Trace.TraceError(filterContext.Exception.ToString());
}
}
You then register it in the Global.asax.cs
inside protected void Application_Start()
The method already contains the line
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
So, you will need to add this line ABOVE it
GlobalFilters.Filters.Add(new MVCExceptionFilter());
Im using umbraco v 4.7.1 (Assembly version: 1.0.4281.20201) and have a project where I must extend the global.asax file.
Please not the following
I have tried this, http://blog.mattbrailsford.com/2010/07/11/registering-an-application-start-event-handler-in-umbraco/, didn't work in 4.7
it is Global.asax I need to extend since I'm working with dependency injection
I cannot delete the App_global.asax.dll file (as some may suggest) since it will regenerate everytime I restart or rebuild my project
Here is my implementation,
using Project.Umbraco.DependencyInjection;
using Project.Umbraco.IoC;
using Microsoft.Practices.Unity;
using System;
using System.Diagnostics;
using umbraco;
namespace Project.Umbraco.App_Start
{
public class MyGlobal : Global, IContainerAccessor
{
///
/// Returns the IoC container
/// IContainerAccessor
///
public IUnityContainer Container
{
get
{
return MvcUnityContainer.Instance.Container;
}
}
protected override void Application_Start(object sender, EventArgs e)
{
base.Application_Start(sender, e);
Debug.WriteLine("Application start");
}
protected override void Application_BeginRequest(object sender, EventArgs e)
{
base.Application_BeginRequest(sender, e);
Debug.WriteLine("Application start");
}
//protected void Session_Start(object sender, EventArgs e) {}
//protected void Application_AuthenticateRequest(object sender, EventArgs e) {}
//protected void Application_Error(object sender, EventArgs e) {}
//protected void Session_End(object sender, EventArgs e) {}
//protected void Application_End(object sender, EventArgs e) {}
}
}
The implementation seems as if should work, maybe I've just placed this in the wrong namespace or something?
Thanks for any help
T
From Umbraco 4.8.0 and onwards, the App_global.asax.dll is no longer needed, so you might want to consider upgrading to a newer version.
In 4.7 though, you could simply use the PreApplicationStart method by creating a class that looks a little something like this:
using System.Linq;
using System.Web.Routing;
using System.Web.Http;
using CustomerName.Extensions;
[assembly: System.Web.PreApplicationStartMethod(typeof(AppStart), "PreStart")]
namespace CustomerName.Extensions
{
public static class AppStart
{
public static void PreStart()
{
RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
Of course, instead of defining WebAPI routes, you could insert your DI code.
I know we could simply use an app_offline.htm file to do this.
But I want to be able access the website if my IP is 1.2.3.4 (for example), so that I can do a final testing.
if( IpAddress != "1.2.3.4" )
{
return Redirect( offlinePageUrl );
}
How can we implement this in ASP.NET MVC 3?
You can use a catch-all route with a RouteConstraint with the IP check:
Make sure you put the offline route first.
routes.MapRoute("Offline", "{controller}/{action}/{id}",
new
{
action = "Offline",
controller = "Home",
id = UrlParameter.Optional
},
new { constraint = new OfflineRouteConstraint() });
and the constraint code:
public class OfflineRouteConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
// return IpAddress != "1.2.3.4";
}
}
Per Max's suggestion here is an actual implementation.
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CheckForDownPage());
}
//the rest of your global asax
//....
}
public sealed class CheckForDownPage : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var path = System.Web.Hosting.HostingEnvironment.MapPath("~/Down.htm");
if (System.IO.File.Exists(path) && IpAddress != "1.2.3.4")
{
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.Redirect("~/Down.htm");
return;
}
base.OnActionExecuting(filterContext);
}
}
You can define a global filter that stop all the requests if they don't come from your IP. you can enable the filter by configuration.
I got an infinite loop on colemn615's solution, so I added a check for the offline page.
Also, for later versions of ASP.NET this is split into a FilterConfig.cs file in the App_Start folder.
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CheckForDownPage());
}
public sealed class CheckForDownPage : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (HttpContext.Current.Request.RawUrl.Contains("Down.htm"))
{
return;
}
var path = System.Web.Hosting.HostingEnvironment.MapPath("~/Down.htm");
if (System.IO.File.Exists(path) && IpAddress != "1.2.3.4")
{
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.Redirect("~/Down.htm");
return;
}
base.OnActionExecuting(filterContext);
}
}
I add an AppSetting in the Web.Config file:
<add key="MaintenanceMsg" value="We are currently performing some routine maintenance. We expect to be back up at around 01:00." />
I then update the global.asax file's Application_BeginRequest method to check if the appsetting has a value, if it does, I redirect everything to the maintenance page:
private void Application_BeginRequest(object sender, EventArgs e)
{
if(!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["MaintenanceMsg"]) && !Request.Url.ToString().Contains("UndergoingMaintenance"))
Response.Redirect("~/Home/UndergoingMaintenance");
** Do whatever you do when you don't want to show your maintenance page here**
}
Finally, create your view and controller action.
If you use Azure you can simply add and delete the value for the AppSetting in the portal so you can suspend your site in a matter of minutes without a deployment.