Log visitor data using sessions, or an alternative? - asp.net

I don't use session state for anything. I've even disabled it in my Web.config, and removed the session module. But, I now need to log visitor data, and don't know how?
I though of doing this:
re-enable session
catch new sessions in void Session_Start(object, EventArgs) method in Global.asax
log stuff of interest
But I'd prefer not to use session state, unless I need to. I recall that sessions are locked by default, which will slow the pipeline.
So how do I log visitor data in ASP.NET MVC without session state?

You use an ActionFilterAttribute
There a how to on the asp.net/mvc site: http://www.asp.net/mvc/overview/older-versions-1/controllers-and-routing/understanding-action-filters-cs
In summary, add a class like:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class LogVisitorsAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var descriptor = filterContext.ActionDescriptor;
var controller = descriptor.ControllerDescriptor.ControllerName;
var action = descriptor.ActionName;
var user = filterContext.HttpContext.User.Identity.Name;
// add your logging here
log(description, controller, action, user);
}
}
You can pull all sorts of interesting "visitor data" during the OnActionExecuting, such as controller/action names (as shown) and the values passed to the action (action parameters).
Then you can either add this to individual actions
[LogVisitors]
public ActionResult Index()
or the controller (or a base controller)
[LogVisitors]
public class HomeController
or to all actions and controllers by adding it to your FilterConfig (which should already exist)
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new LogVisitorsAttribute());
}
}

So how do I log visitor data in ASP.NET MVC without session state?
The simple way is, don't log it. Let a 3rd party log it for you. You could use a free service such as Google Analytics or a paid service such as Hitslink to monitor everything about your users, and have many charting options to analyze the data later.
Then you don't need to have session state, worry about storage for the data, or have to build your own reporting solution to analyze it.

I thought of another way, but it's messy:
intercept in Application_BeginRequest() method in Global.asax
compare a cookie to current time
if >20 minutes then it's a new session, else update cookie with current timestamp
if new session then perform logging
Cons:
fires for every request
reinventing the wheel - this is basically session state!
Pros:
Lighter than session state
doesn't lock anything
Hope there's a better way, as this seems messy.

Related

Handling cookies based on route in a single request lifecycle in ASP.NET MVC?

I'm writing a route that will allow the user to set a cookie with the version of some JSON object that the application will use to set client-side configurations. It is a fairly large JSON object that we don't want to store in a cookie alone. We want to store ONLY the version to be looked up and set from some map up in the cloud on every request since multiple versions of the client are running around and we want those to be separated on a per request basis.
Currently, I know the problem is due to my lack of understanding of the single request lifecycle of ASP.NET MVC as I'm sure the following code proves. I do know that the Application_BeginRequest Action is probably happening BEFORE the route is handled (correct me if I'm wrong here), but I am not sure where it SHOULD be happening so that the cookie is populated BEFORE it is retrieved. I also don't believe that Application_EndRequest would be better due to the same, but opposite issue.
Any and all suggestions that lead to my understanding of the lifecycle and an appropriate Action to handle that kind of cookie value getting will be welcomed!
// Working controller (cookie does get set, this is confirmed)
using System;
using System.Web;
using System.Web.Mvc;
using SMM.Web.Infrastructure.Filters;
namespace SMM.Web.Controllers
{
[NoCache]
public class SetCookieController : ApplicationController
{
private HttpCookie CreateVersionCookie(int versionId)
{
HttpCookie versionCookie = new HttpCookie("version_id");
versionCookie.Value = versionId.ToString();
return versionCookie;
}
public ActionResult SetCookie(int versionId)
{
Response.Cookies.Add(CreateVersionCookie(versionId));
return Redirect("/");
}
}
}
// In Global.asax.cs (this does not work to get the cookie)
private void LoadSomeJsonFromACookie()
{
HttpCookie someJsonThingCookie = HttpContext.Current.Request.Cookies["version_id"];
string jsonVersion = (string)staticVersionCookie.Value;
string json = FunctionToGetSomeJsonThingByVersion(jsonVersion); // This returns a stringified JSON object based on the jsonVersion supplied
dynamic someJsonThing = JsonConvert.DeserializeObject<dynamic>(json);
HttpContext.Current.Items["someJsonThing"] = someJsonThing;
}
protected void Application_BeginRequest(object sender, EventArgs e)
{
RedirectToHttps();
// some other redirects happen here
LoadSomeJsonFromACookie();
}
Application_BeginRequest is the right place. Since in the code, you can see I'm firing a redirect back to root /, it will set the cookie before it ever needs the cookie.

Action requires multiple controllers to execute

I have a UserController that has a Destroy function. It is a rather complex function because it demands to destroy all user's data. I have another action, from the Admin panel that deletes all data from a specific set of users.
Since I don't want to replicate the code from the UserController, I would like to call the Destroy function from UserController for each User to destroy its data.
How should I proceed?
Thanks in advance.
Why not move this functionality to a common class method which can be accessed from both the controllers as needed ?
public class UserManager
{
public void Destroy(List<int> userIdsToDestroy)
{
foreach(var userId in userIdsToDestroy)
{
//Execute code to destroy
}
}
}
and from your action methods, you can call it like
var mgr = new UserManager();
var badUsers = new List<int> { 1,2,3};
mgr.Destroy(badUsers);
Update the badUsers variable value as needed based on from where you are calling it.
Shared functionality like this would ideally be in a business layer, and both controllers would call that code. If it's a little app, you could just create a separate folder structure for shared code. Larger projects would have a business layer dll.
Why not make the Destroy() method as a Non-Action method then like
[Non-Action]
public void Destroy(User user)
{
// code goes here
}
You can as well make this Destroy() function as part of your business layer logic instead of handling this in controller. In that case, you call it from anywhere.
If you want it to be #controller, you can as well consider usig [ChildActionOnly] action filter attribute.

The Application_PreRequestHandlerExecute event doesn't fire for PageMethods. What can I use instead?

This article explains that the PreRequestHandlerExecute event does not fire for PageMethod calls for whatever reason. However, I'm trying to use that event to populate the Principal object with the user's permissions so they can be checked within any web request (PageMethod call or not). I'm caching the permissions in the Session, so I need an event that fires whenever a PageMethod is called, and I need to have access to the Session. This way I can populate the Principal object with the security permissions cached in the session, and User.IsInRole() calls will work as expected. What event can I use?
You should implement an authorization module that will be run with every request that goes up to the server. This way you are able to authorize your principal for any request that come up to the server (page request, method, etc.)
public class AuthorizationModule : IHttpModule, IRequiresSessionState
{
//not going to implement it fully, might not compile
public void Init( HttpApplication context )
{
//you'll prolly want to hook up to the acquire request state event, but read up to make sure this is the one you want on the msdn
context.AcquireRequestState += AuthorizeRequest;
}
public void AuthorizeRequest( HttpContextBase httpContext )
{
// do you work in here
// you can redirect them wherever if they don't have permssion, log them out, etc
}
}
}
After you've crated the module, you'll need to hook it up in the web.config. Your type should include the namespace if it has one.
<httpModules>
<add name="AuthorizationModule" type="AuthorizationModule"/>
</httpModules>
I hope this helps.
You can use the Application_OnPostAuthenticateRequest as shown below (assuming you are using Forms Authentication. Else, pls replace the code with your Authentication mechanism):
public void Application_OnPostAuthenticateRequest(object sender, EventArgs e)
{
IPrincipal usr = HttpContext.Current.User;
if (usr.Identity.IsAuthenticated && usr.Identity.AuthenticationType == "Forms")
{
var fIdent = (FormsIdentity)usr.Identity;
var ci = new CustomIdentity(fIdent.Ticket);
var p = new CustomPrincipal(ci);
HttpContext.Current.User = p;
Thread.CurrentPrincipal = p;
}
}
Page Methods are static, and bypass the normal Page lifecycle, its objects and its events. The best you can do is pass authentication information as parameters to the Page Method itself.
From my point of view, you can:
1.- Use a common method you can call from every page method server code that have access to Session variables. Please refer to:
http://mattberseth.com/blog/2007/06/aspnet_ajax_use_pagemethods_pr.html
2.- Try to capture a similar behaviour later using __doPostBack() function to run server code. See if this work for you to capture page method async posbacks:
http://www.dotnetcurry.com/ShowArticle.aspx?ID=256
Hope that helps,

Intercept Unity 2.0 HandlerAttribute without an interface

I'm a first-time user of the AOP features of Unity 2.0 and would like some advice. My goal is to be able to log method calls in an ASPX page, like so:
public partial class Page2 : Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
[Log]
private void Testing()
{
}
}
Here is the code for the LogAttribute:
public class LogAttribute : HandlerAttribute
{
public override ICallHandler CreateHandler(IUnityContainer container)
{
return new LogHandler(Order);
}
}
Now the LogHandler:
public class LogHandler : ICallHandler
{
public LogHandler(int order)
{
Order = order;
}
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
string className = input.MethodBase.DeclaringType.Name;
string methodName = input.MethodBase.Name;
string preMethodMessage = string.Format("{0}.{1}", className, methodName);
System.Diagnostics.Debug.WriteLine(preMethodMessage);
return getNext()(input, getNext);
}
public int Order { get; set; }
}
The problem I have is how to use the [Log] attribute. I've seen plenty of example of how to configure the interception settings, for example:
container.AddNewExtension<Interception>();
container.Configure<Interception>().SetDefaultInterceptorFor<ILogger>(new InterfaceInterceptor());
But this implies that I have an interface to intercept, which I don't. I have the ASPX page which uses the [Log] attribute.
so how can I configure Unity to make use of the [Log] attribute? I've done this before using PostSharp and would like to be able to use Unity to do the same.
Cheers.
Jas.
You're unfortunately not going to get this to work in an ASP.NET page with Unity interception.
Unity interception uses a runtime interception model. Depending on the interceptor you choose, you'll either get a subclass with virtual method overrides to call the call handlers (VirtualMethodInterceptor) or a separate proxy object (Interface or TransparentProxyInterceptor) which execute the call handlers and then forward to the real object.
Here's the issue - ASP.NET controls creation and calls to your page, and there's no easy way to hook into them. Without controlling the creation of the page object, you can't use the VirtualMethodInterceptor, because that requires that you instantiate a subclass. And you can't use the proxy version either, because you need ASP.NET to make calls through the proxy.
PostSharp gets around this because it's actually rewriting your IL at compile time.
Assuming you could hook into the creation of the page object, you'd have to use the VirtualMethodInterceptor here. It's a private method, so you want logging on "self" calls (calls from one method of the object into another method on the same object). The proxy-based interceptors can't see those, since the proxy is a separate instance.
I expect there is a hook somewhere to customize how ASP.NET creates object - BuildManager maybe? But I don't know enough about the details, and I expect it'll require some pretty serious hacking to get work.
So, how do you get around this? My recommendation (actually, I'd recommend this anyway) is to use the Model-View-Presenter pattern for your ASP.NET pages. Make the page object itself dumb. All it does is forward calls to a separate object, the Presenter. The Presenter is where your real logic is, and is independent of the details of ASP.NET. You get a huge gain in testability, and you can intercept calls on the presenter without all the difficulty that ASP.NET gives you.

Why does my ASP.Net static function's "context" crossover between user sessions?

I think I need some help understanding how static objects persist in an ASP.Net application. I have this scenario:
someFile.cs in a class library:
public delegate void CustomFunction();
public static class A {
public static CustomFunction Func = null;
}
someOtherFile.cs in a class library:
public class Q {
public Q() {
if (A.Func != null) {
A.Func();
}
}
}
Some ASP.Net page:
Page_Init {
A.Func = MyFunc;
}
public void MyFunc() {
System.IO.File.AppendAllText(
"mydebug.txt", DateTime.Now.ToString("hh/mm/ss.fff", Session.SessionID));
}
Page_Load {
Q myQ = new Q();
System.Threading.Thread.Sleep(20000);
mQ = new Q();
}
The idea is that I have a business object which does some operation based on a callback function at the UI level. I set the callback function to a static variable on Page_Init (in the real code version, in the Master page, if that makes a difference). I thought that every execution of the page, no matter what user session it came from, would go through that function's logic but operate on its own set of data. What seems to be happening instead is a concurrency issue.
If I run one user session, then while it is sleeping between calls to that callback function, start another user session, when the first session comes back from sleeping it picks up the session ID from the second user session. How can this be possible?
Output of mydebug.txt:
01/01/01.000 abababababab (session #1, first call)
01/01/05.000 cdcdcdcdcdcd (session #2, first call - started 5 seconds after session #1)
01/01/21.000 cdcdcdcdcdcd (session #1 returns after the wait but has assumed the function context from session #2!!!!!)
01/01/25.000 cdcdcdcdcdcd (session #2 returns with its own context)
Why is the function's context (meaning, its local data, etc.) being overwritten from one user session to another?
Each request to an asp.net site comes in and is processed on it's own thread. But each of those threads belong to the same application. That means anything you mark as static is shared across all requests, and therefore also all sessions and users.
In this case, the MyFunc function that's part of your page class is copied over top of the static Func member in A with every page_init, and so every time any user does a page_init, he's replacing the A.Func used by all requests.
Static data is shared among the entire application domain of your webapp.
In short, it's shared among all the threads serving requests in your webapp, it's not bound to a session/thread/user in any way but to the webapp as a whole.(unlike e.g. php where each request lives in its own isolated environment bar a few knobs provided - such as the session variable.)
I won't try to improve on the other answers' explanations of static members, but do want to point out another way to code around your immediate problem.
As a solution, you could make an instance-oriented version of your class A, store it in a page-level variable, and pass it to Q's constructor on page load:
public class MyPage: Page {
private A2 _a2;
// I've modified A2's constructor here to accept the function
protected Page_Init() { this._a2 = new A2(MyFunc); }
protected Page_Load() {
Q myQ = new Q(this._a2);
// etc..
}
}
In fact, if there's no pressing need to declare A2 earlier, you could just instantiate it when you create your instance of Q in Page_Load.
Edit: to answer the question you raised in other comments, the reason the variables are being shared is that the requests are sharing the same delegate, which has only a single copy of its variables. See Jon Skeet's The Beauty of Closures for more details.
One solution you might consider is using [ThreadStatic].
http://msdn.microsoft.com/en-us/library/system.threadstaticattribute(VS.71).aspx
It will make your statics per thread. There are cavaets however so you should test.
If you want the data to persist only for the current request, use HttpContext.Items:
http://msdn.microsoft.com/en-us/library/system.web.httpcontext.items.aspx
If you want the data to persist for the current user's session (assuming you have session state enabled), use HttpContext.Session:
http://msdn.microsoft.com/en-us/library/system.web.httpcontext.session.aspx

Resources