Is there an equivalent for MVC.NET's OnActionExecuting in standard asp.NET? ?
I thought it would be Page_Load since OnActionExecuting would be called each time an action is executed (or the page loads). But I'm running into inheritance issues when I try to use Page_Load instead.
Since it is very difficult to make my solution work with a Page_Load I'm thinking I might not have the best ... solution.
Any thoughts on whether they are equivalent or close enough?
Background:
I'm converting a piece of an MVC3 application into a standard .NET to wrap in a SharePoint Web Part.
Here's the MVC code I'm trying to translate, as you can see its the user security bits I'm translating:
protected override void OnActionExecuting(ActionExecutingContext filterContext) {
if (!SiteCacheProvider.ItemCached(enmCacheKey.SiteSetting)) {
if (filterContext.IsImplementedGeneralPrincipal()) {
IUserProfile userProfile = ((IGeneralPrincipal)filterContext.HttpContext.User).UserProfile;
SiteCacheProvider.ChangeSiteSetting(userProfile.SiteID);
}
}
base.OnActionExecuting(filterContext);
}
First, take on account that no Actions are in ASP.NET because the model is different (Event-Based) - There're no methods(actions) which you can decorate with Action Filters, it's all about the Page-Cycle events.
Second, In ASP.NET, you may use HTTP modules (HttpApplication.BeginRequest particularly) in order to intercept incoming requests to your application pages by adding your required logic.
From MSDN:
HTTP Modules use to intercept HTTP requests for modifying or utilize
HTTP based requests according to needs like authentication,
authorization, session/state management, logging, modifying Response,
URL rewriting, Error handling, Caching....
For example:
using System;
using System.Web;
using System.Collections;
public class HelloWorldModule : IHttpModule
{
public string ModuleName
{
get { return "HelloWorldModule"; }
}
public void Init(HttpApplication application)
{
application.BeginRequest += (new EventHandler(this.Application_BeginRequest));
application.EndRequest += (new EventHandler(this.Application_EndRequest));
}
private void Application_BeginRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("<h1>HelloWorldModule: Beginning of Request</h1><hr>");
}
private void Application_EndRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("<hr><h1>HelloWorldModule: End of Request</h1>");
}
public void Dispose()
{
}
}
Related
I'm using code first to develop my database for my MVC app. Here's the connection string:
<add name="OrtundWebConnectionString" connectionString="Data Source=.\SQLEXPRESS; Initial Catalog=OrtundWeb; User Id=sa; Password=sa;" providerName="System.Data.SqlClient"/>
It uses SQL Authentication because for reasons I've yet to determine, Windows Auth doesn't allow me to do anything on the server...
Here's Application_Start():
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
OrtundDB.InitializeDB();
}
This class calls db.Database.Initialize(true); and runs a method I wrote to add some default information into the database
public static class OrtundDB
{
private static OrtundDBContext db = new OrtundDBContext();
public static void InitializeDB()
{
db.Database.Initialize(true);
db.InitializeDB();
}
}
Here's the DBContext class I wrote that's being used. This exists in another project which is referenced in the MVC project:
internal class OrtundDBContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// modelBuilder.Configuration.Adds()
}
public OrtundDBContext()
: base("OrtundWebConnectionString")
{
Database.SetInitializer<OrtundDBContext>(new DropCreateDatabaseAlways<OrtundDBContext>());
}
public void InitializeDB()
{
// inserts the default data - an admin user account and information for user roles
}
// DbSet calls here
}
I've previously built an MVC app with this same methodology, and that one works. This, however, doesn't even execute Application_Start() every time.
On the odd occasion where it has executed Application_Start, it went all the way through to execute db.Database.Initialize(true); at which point, it just never seemed to end. I got no errors, but the action didn't seem to complete.
I'm a bit of a novice when it comes to MVC so I have no idea what the problem could be or how to fix it.
Any assistance to fix the problem (and possibly explanation as to what might cause it) will be greatly appreciated.
Thanks in advance!
Perhaps the issues lies with the expectation you have on Application_Start.
Application_Start is called once for the application being loaded into the app pool on IIS.
Only when the app pool is recycled or IIS is restarted will it be called again.
Take 10-15mins to read IIS pipeline
It will help with what is going on.
I had a nasty bug due to my false expectations about threads and requests.
alternatives to Consider and test in debug to see if they suit you:
The INIT in Global.asax
public override void Init() {
base.Init();
// handlers managed by ASP.Net during Forms authentication
BeginRequest += new EventHandler(BeginRequestHandler);
// PostAuthorizeRequest += new EventHandler(PostAuthHandler);
EndRequest += new EventHandler(EndRequestHandler);
}
Better: A single baseController for all your controllers so as to control bootstrap each call.
public class SomeController : MyBaseMvcController{
// whatever as usual....
}
[System.Web.Mvc.Authorize]
[MyMVCFilter] // see the filter below. Get the MVC pipeline to call your code on Executing
public abstract class MyBaseMvcController : Controller
{
protected MyBaseMvcController () {
// a place to get a NEW uow or new Context ....
}
}
public class MyMVCFilter : System.Web.Mvc.ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext) {
// a useful bootstrap option when you need the httpContext for bootstrap.
BootStrapHttp(filterContext.HttpContext);
base.OnActionExecuting(filterContext);
}
}
So HttpContext.Request throws if called within a global start
public HttpRequest get_Request()
{
if (this.HideRequestResponse)
{
throw new HttpException(SR.GetString("Request_not_available"));
}
return this._request;
}
This is actually documented
ASP.NET will throw an exception if you try to use this property when the HttpRequest object is not available. For example, this would be true in the Application_Start method of the Global.asax file, or in a method that is called from the Application_Start method. At that time no HTTP request has been created yet.
Is there a way of checking if a HttpContext.Request is in a state that it can be retrieved without throwing the exception? Effectively I want to write a TryGetRequest helper method.
Reflection is not an option. It needs to be a public API.
I dont have access to the application context. this is generic logging code. So setting some flag when startup has finished is not an option
As deostroll observed, it is reasonable and may in fact be necessary to rely on the ASP.NET application lifecycle to determine when the current HttpRequest has become available. The generic logging code is presumably dependent at the very least on HttpContext.Current or some other reference to the current HttpContext instance. If that assumption holds true, then an HttpModule can be implemented that stores a flag in the HttpContext.Items collection when the BeginRequest event fires. The static TryGetRequest helper method can test for the presence of that flag to determine whether it is safe to use HttpContext.Request.
Perhaps like this:
public class HttpRequestHelper : IHttpModule
{
private const string HttpRequestIsAvailable = "HttpRequestIsAvailable";
public static bool TryGetRequest(HttpContext context, out HttpRequest request)
{
request = null;
if (context != null)
{
if (context.Items.Contains(HttpRequestIsAvailable))
request = context.Request;
}
return (request != null);
}
#region IHttpModule
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.BeginRequest += context_BeginRequest;
}
private void context_BeginRequest(object sender, EventArgs e)
{
((HttpApplication)sender).Context.Items.Add(HttpRequestIsAvailable, true);
}
#endregion
}
The module must be registered in web.config (assuming IIS 7.0 integrated pipeline):
<system.webServer>
<modules>
<add name="HttpRequestHelper" type="Utility.HttpRequestHelper" />
</modules>
</system.webServer>
The logging code would use the helper method like this:
HttpRequest request;
if (HttpRequestHelper.TryGetRequest(HttpContext.Current, out request))
LogWithRequest(request, message);
else
LogWithoutRequest(message);
The implementation does not rely on private API's or reflection. It relies on a flag, but the state information remains with the HttpContext instance and is well encapsulated.
Why not wrap the call to HttpContext.Request with a try, catch block then you can catch the exception and modify your behaviour accordingly.
It is not possible without using reflection
i need to hit DB and load the settings file before every page loads. Am currently using MVC and am creating that call in constructor in All controllers.
Am not sure of what is the better way to handle this scenario ?? I read like we can use singleton class in this scenario.
Is it possible to have the data once and reuse across pages ? What is the best way ?
Some sample code snippets will help !
Option one: you can used Application_BeginRequest in Global.asax.cs:
protected void Application_BeginRequest(object sender, System.EventArgs e)
{
//something
}
Option two: create a global filter:
public class ActionLogFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext filterContext)
{
// do your stuff. This is run before control is passed to controller
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
// do stuff here - control here is passed after controller is done with the action execution
}
}
and then add controller to execution stack in Global.asax.cs:
FilterConfig.RegisterGlobalFilters(GlobalFilterCollection filters)
and FilterConfig is usually looks like this:
public static class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new MyFilter());
}
}
Option three: Create your global controller that overrides OnActionExecuting (see the filter example). Make your controllers to inherit from that global base controller.
I prefer option with filters. Favour composition over inheritance
I have an website under which i have 5 files
test1.aspx
test2.aspx
test3.aspx
test4.aspx
test5.aspx
i have an http module which gets called in all the pages
but i have an condition where on test5.aspx page i do not want the http module to be called what settings need to be done in order to resolve the issue?
HttpModules get run before your page lifecycle, so you'd have to match it on the request path.
Assuming your HttpModule's Init function sets up a BeforeRequest handler, something like:
public class MyModule : IHttpModule
{
public void Init(HttpApplication application)
{
application.BeginRequest += this.BeginRequest;
}
public void BeginRequest(object sender, EventArgs e)
{
var app = sender as HttpApplication;
if (app.Request.Path.Contains("test5.aspx")) {
return;
}
// Process logic for other pages here
}
}
In my project (c#), we have an HttpModule that creates a custom Principal that we attach to the CurrentPrincipal.
This is working in all of our MVC3 apps and in our classic ASP.Net apps.
We have an AuthorizeAttribute override we use to secure our Controller methods - seems pretty standard.
The problem is that in the custom authorizeAttribute, the user (httpContext.User) is a RolePrincipal and not the custom principal.
To troubleshoot, I put some handlers in my global.asax to trap beginrequest() and endrequest(). Well, in BeginRequest my User is what we expect - the custom principal. In EndRequest, the user is a RolePrincipal again. The web.config declarations of the HttpModule are good - I can step thru the HttpModule's code.
Does anyone know what's going on? We have a custom HttpModule, but I can't modify that code for this project (it's being used everywhere and it works fine).
This is my first MVC4 project - I'm wondering if MVC4 does something differently.
Code below. Is there any info I've left out?
edit: added authorizationattribute code.
Code
(In HttpModule)
private void BeginRequest(object Sender, EventArgs e)
{
HttpContext context = HttpContext.Current;
var Id = new TASBIdentity();
context.User = new TASBPrincipal(Id);
Thread.CurrentPrincipal = context.User;
(etc...)
(In global.asax)
void Application_BeginRequest(object sender, EventArgs e)
{
HttpContext context = HttpContext.Current;
var user = context.User; // user is correct type
}
void Application_EndRequest(object sender, EventArgs e)
{
HttpContext context = HttpContext.Current;
var user = context.User; // user is default type (not correct)
}
(In the authorizeattribute)
public class SecuredByFunctionAttribute : AuthorizeAttribute
{
private readonly string _functionKey;
public SecuredByFunctionAttribute(string functionKey)
{
_functionKey = functionKey;
}
protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
{
return httpContext.User.IsInRole(_functionKey);
}