I want to keep a log of all the requests to my MVC 3 app, including requested URL, user's ip adress, user agent, etc. Where is the best place to do this,
1) use a base controller?
2) use an action filter?
3) others?
I do this inside my BaseController. Something like this:
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
// If in Debug mode...
if (filterContext.HttpContext.IsDebuggingEnabled)
{
var message = string.Format(CultureInfo.InvariantCulture,
"Leaving {0}.{1} => {2}",
filterContext.Controller.GetType().Name,
filterContext.ActionDescriptor.ActionName.Trim(),
filterContext.Result);
Logger.Debug(message);
}
// Logs error no matter what
if (filterContext.Exception != null)
{
var message = string.Format(CultureInfo.InvariantCulture,
"Exception occured {0}.{1} => {2}",
filterContext.Controller.GetType().Name,
filterContext.ActionDescriptor.ActionName.Trim(),
filterContext.Exception.Message);
Logger.Error(message);
}
base.OnActionExecuted(filterContext);
}
Hope you get the idea.
You can also log before the action is executed using:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
An HttpModule seems like the best fit, if you ask me.
All of the data you're talking about logging is available well before any particular Controller gets invoked. So if you do logging outside of the controller, then you get to capture even those requests which are not to valid controller actions. And there's no need to clutter your controller code with something that's really a cross-cutting concern.
You can have multiple action methods triggered when rendering a single request. Consider using RenderAction on your layout page as follows:
Html.RenderAction("Navigation", "Menu")
It's worth noting that you'd then have two log entries with the same information if you choose to use action filter for logging.
Related
So I've come to understand that MVC doesn't really have a forms PageLoadevent equivalency so where do I put a code that I would like to execute every time a page loads? I'd like to check for a cookie.
Put it in the Constructor of the MVC Controller.
Or like this:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
// check for cookies!
}
I think this might fire for every action on the page if there are multiple actions on the page (for example partial views).
If you only want it to fire once you many need to check for
filterContext.IsChildAction
Like this
protected override void OnActionExecuting(ActionExecutingContext filterContext) {
if (filterContext.IsChildAction) return;
// check for cookies!
}
Mentioned here
In ASP.NET MVC 3, what is filterContext.IsChildAction?
I just wanted to gauge opinions on how I should approach this problem and ultimately looking for a quick win (wrong way to think about things nut time pressures mean I have to think and act quickly!
I've been given a website that has a bit of an issue.
I login using standard forms authentication as User1234 and my url is as follows:
www.mywebsite.co.uk/1234/Contact.
This will take me to User1234's details.
You can put two and two together and correctly assume that 1234 is a user id of some sort.
Once authenticated, I can access the views with [Authorize] attribute present, any anonymous/unathenticated users get redirected.
However, once logged in as User1234, I can then tinker with the url like so:
www.mywebsite.co.uk/1235/Contact.
So I am authenticated as User1234 but can see User1235's data. This is BAD for obvious reasons.
When I log in, I actively set the login ID in session so in theory, I could do a check whenever a user hits an ActionResult, I could cross check the ID present in the URL against the session login ID. However, it is a rather project with lots of action results and as such, I'm reluctant to spend my Saturday afternoon adding something to each and every ActionResult.
Is there an event in the global.asax I could use that is hit on each ActionResult request where I can compare Session login ID with url ID?
Alternatively, can anyone offer some suggestions about how I can achieve this or restrict URL tampering?
You can try and do a base controller
public class BaseController : Controller
{
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
//Do your stuff here
base.OnActionExecuted(filterContext);
}
}
I assume that you don't want to change your URL routes, as you could retrieve the user id also from the session. A quick solution would be to use an ActionFilter which you can place on the affected controllers or action methods:
public class VerifyUserIdAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var sessionUserId = filterContext.HttpContext.Session["UserId"];
var routeUserId = filterContext.RouteData.Values["UserId"];
if (routeUserId != null && sessionUserId == routeUserId)
filterContext.Result = new RedirectResult("<<url to redirect to>>");
}
}
I don't understand why the URL contains a data entry point. This appears to be a design flaw. I would remove all code that uses a URL parameter and instead make sure the controller looks up what the ID is based on the logged in user.
I have a site with a lot of routes.
Some routes, e.g. /sector-overview are to a specific page that I want the user to see.
Other routes, e.g. /sectoroverview are to an an action that ultimately renders a partial which is included on the homepage.
the second route is only meant to be internal to the application, but if the user types that into their address bar (it's an easy mistake to make), the system sees that as a valid request and it'll return the HTML partial.
I could rename the second route to something like /internal-sectoroverview, but this isn't really fixing the problem, just hiding it.
Is there any way for me to prevent the request from being processed if the user types this? What's the best way for me to deal with this issue?
You can block the route by using route constraints. However, in your case I would decorate your internal Action with [ChildActionOnly] like this:
[ChildActionOnly]
public ActionResult Overview()
{
return View();
}
By doing this, the action will be only rendered when using #Html.Action or #Html.RenderAction. If you try to access it through a browser, you'll get an error.
UPDATE
To return a 404 instead of an error you can override the OnException method on the controller and handle it there. Something like this:
protected override void OnException(ExceptionContext filterContext)
{
filterContext.ExceptionHandled = true;
//check if filterContext.Exception was thrown by child action only (maybe by text)
filterContext.Result = new HttpStatusCodeResult(404);
}
If I understand right you should resolve the problem of the partial not being called using the attribute ChildActionOnly.just for reference if you don't want that a method in your action can be called at all use the NonActionAttribute
I have a similar problem issue that people finding this might also need - I want to return 404 if a certain criteria is met from a function that returns a PartialViewResult. The solution for me was
public PartialViewResult MyFunction()
{
if( criteria ) {
Response.StatusCode = 404;
return null;
}
}
I have a controller protected with AuthorizeAttribute. When the authorization fails i get just an empty page. If i override OnAuthorization() i can see that after calling base.OnAuthorization() filterContext.Result is null (why?). If i override OnException() and set a breakpoint it never hits. Can please someone explain how it's supposed to work? How can i make it redirect to specified page? Where can i inject into to log failed authorization attempts (better not to write custom filter)? I use MVC 3 RC1 if it's important.
You want to override the AuthorizeAttribute.HandleUnauthorizedRequest method. Here's the default implementation:
protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext) {
// Returns HTTP 401 - see comment in HttpUnauthorizedResult.cs.
filterContext.Result = new HttpUnauthorizedResult();
}
You'll instead want to set the Result to be a RedirectResult (or some other result depending on your desired logic). This would also be a good place for logging.
Consider an ASP.NET MVC application that requires a session variable be set. It's used throughout the app. It'll be set by either reading a hashed value on the browser cookie, or after having the user login.
In the WebForms + Master Page model, I'd check the Page_Load() of the master page. Perhaps not the ultimate event, but it was an easy one to find.
How would you check and enforce the existence of a session variable in ASP.NET MVC? Consider that this question might not involve user login details, but some other piece of data (first visit time, perhaps).
Solution Attempts
public void Application_BeginRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Session["SomeDateTime"] = DateTime.Now.ToString();
// results in Object reference not set to an instance of an object.
// context.Session is null
}
You have two options.
1.Place logic in base controller's Initialize function
Assuming that all your controllers inherit from a base controller, you can place the logic needed in the override of the Execute() function of the base controller.
public class BaseController : Controller
{
public BaseController()
{
}
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
// check if the user has the value here using the requestContext.HttpContext object
}
{
2. Use the Global.asax void Application_PreRequestHandlerExecute(Object source, EventArgs e) function
public void Application_PreRequestHandlerExecute(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
// use an if statement to make sure the request is not for a static file (js/css/html etc.)
if(context != null && context.Session != null)
{
// use context to work the session
}
}
Note: The second part works with any ASP.NET application, WebForms or MVC.
As for enforcing that they have a certain session variable, its very open really. You can redirect to a certain page for them to fill out a form or select an option or something. Or maybe just have a default value that is set to a certain session key if it is not found.
EDIT
While playing with this, I noticed a big issue with Application_PreRequestHandlerExecute approach. The event handler is being called for any request done to the server, be it .css/.js/.html files. I'm not sure if this is an issue with the way my workstation is setup, or just how ASP.NET/IIS works, so I would make sure that this isn't being called on all requests when implementing the approach above.
It is for the previous reasons I wrapped the work to be done in the session with an if statement.
Not sure I fully understand the question, but I do this by override the OnActionExecuting method of the controller.
In there you do the code to see if the Session Variable exists. If not, create it, if so then use it.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.Session != null)
{
//TODO: Get value from session etc.
}
base.OnActionExecuting(filterContext);
}
As another alternative, the ControllerActionInvoker class invokes every action method; it gets assigned to the controller via the controller factory. So you could subclass this action invoker, everytime an action is invoked (by overridding the InvokeAction method) check here for this existence...