ASP.net MVC Logging Page Views - asp.net-mvc

Is there an easy way to log every page hit by any user.
I am thinking this will sit within the global.asax.cs file, so that I can write to a db table the URL of the page being hit.

I've found a way to complete this problem which seems to fit my purpose.
I use the PostAuthenticateRequestHandler method, as it is called for every page hit.
I ignore any empty path and just "/" as these are not actual pages hit.
//in global.asax.cs file
private void PostAuthenticateRequestHandler(object sender, EventArgs e)
{
///...///
string extension = this.Context.Request.CurrentExecutionFilePathExtension;
string path = this.Context.Request.CurrentExecutionFilePath;
if (extension == string.Empty && path != "/")
{
PageVisitedLogModel pageVisitedLogModel = new PageVisitedLogModel
{
DateVisited = DateTime.Now,
IPAddress = this.Context.Request.UserHostAddress,
PageURL = this.Context.Request.RawUrl,
Username = this.Context.User.Identity.Name
};
//then writes to log
DataHelper.UpdatePageVisitedLog(pageVisitedLogModel);
}
}

One way to do this would be to use a global action filter, as in this example. This allows you to run some code for any action in your solution.

Related

Dynamically extract information from FluentSecurity configuration

I'm working on an ASP.NET MVC Website that uses FluentSecurity to configure authorizations. Now we need a custom ActionLink Helper which will be displayed only if the current user has access to the targeted action.
I want to know if there is a way to know dynamically, from FluentSecurity Configuration (using SecurityConfiguration class for example), if the current logged user has access to an action given her name (string) and her controller name (string). I spend a lot of time looking in the source code of FluentSecurity https://github.com/kristofferahl/FluentSecurity but with no success.
For example:
public bool HasAccess(string controllerName, string actionName) {
//code I'm looking for goes here
}
Finally, i will answer myself may this will help another one.
I just emulate the OnAuthorization method of the HandleSecurityAttribute class. this code works well:
public static bool HasAccess(string fullControllerName, string actionName)
{
ISecurityContext contx = SecurityContext.Current;
contx.Data.RouteValues = new RouteValueDictionary();
var handler = new SecurityHandler();
try
{
var result = handler.HandleSecurityFor(fullControllerName, actionName, contx);
return (result == null);
} catch (PolicyViolationException)
{
return false;
}
}

ELMAH - Using custom error pages to collecting user feedback

I'm looking at using ELMAH for the first time but have a requirement that needs to be met that I'm not sure how to go about achieving...
Basically, I am going to configure ELMAH to work under asp.net MVC and get it to log errors to the database when they occur. On top of this I be using customErrors to direct the user to a friendly message page when an error occurs. Fairly standard stuff...
The requirement is that on this custom error page I have a form which enables to user to provide extra information if they wish. Now the problem arises due to the fact that at this point the error is already logged and I need to associate the loged error with the users feedback.
Normally, if I was using my own custom implementation, after I log the error I would pass through the ID of the error to the custom error page so that an association can be made. But because of the way that ELMAH works, I don't think the same is quite possible.
Hence I was wondering how people thought that one might go about doing this....
Cheers
UPDATE:
My solution to the problem is as follows:
public class UserCurrentConextUsingWebContext : IUserCurrentConext
{
private const string _StoredExceptionName = "System.StoredException.";
private const string _StoredExceptionIdName = "System.StoredExceptionId.";
public virtual string UniqueAddress
{
get { return HttpContext.Current.Request.UserHostAddress; }
}
public Exception StoredException
{
get { return HttpContext.Current.Application[_StoredExceptionName + this.UniqueAddress] as Exception; }
set { HttpContext.Current.Application[_StoredExceptionName + this.UniqueAddress] = value; }
}
public string StoredExceptionId
{
get { return HttpContext.Current.Application[_StoredExceptionIdName + this.UniqueAddress] as string; }
set { HttpContext.Current.Application[_StoredExceptionIdName + this.UniqueAddress] = value; }
}
}
Then when the error occurs, I have something like this in my Global.asax:
public void ErrorLog_Logged(object sender, ErrorLoggedEventArgs args)
{
var item = new UserCurrentConextUsingWebContext();
item.StoredException = args.Entry.Error.Exception;
item.StoredExceptionId = args.Entry.Id;
}
Then where ever you are later you can pull out the details by
var item = new UserCurrentConextUsingWebContext();
var error = item.StoredException;
var errorId = item.StoredExceptionId;
item.StoredException = null;
item.StoredExceptionId = null;
Note this isn't 100% perfect as its possible for the same IP to have multiple requests to have errors at the same time. But the likely hood of that happening is remote. And this solution is independent of the session, which in our case is important, also some errors can cause sessions to be terminated, etc. Hence why this approach has worked nicely for us.
The ErrorLogModule in ELMAH (version 1.1 as of this writing) provides a Logged event that you can handle in Global.asax and which you can use to communicate details, say via HttpContext.Items collection, to your custom error page. If you registered the ErrorLogModule under the name ErrorLog in web.config then your event handler in Global.asax will look like this:
void ErrorLog_Logged(object sender, ErrorLoggedEventArgs args)
{
var id = args.Entry.Id
// ...
}

Databinding to a DomainDataSource Query Parameter

I need to bind a username to a DomainDataSource QueryParameter. My understanding is that the following does not work:
<RiaControls:DomainDataSource x:Name="MyData" LoadSize="20" QueryName="GetStockByCompany" AutoLoad="True">
<RiaControls:DomainDataSource.DomainContext>
<ds:InventoryDomainContext />
</RiaControls:DomainDataSource.DomainContext>
<RiaControls:DomainDataSource.QueryParameters>
<riadata:Parameter
ParameterName="userName"
Value="{Binding Path=User.Name}" />
</RiaControls:DomainDataSource.QueryParameters>
</RiaControls:DomainDataSource>
I am not opposed to using the C# code-behind part of the page, but I'm not sure what event to put this in.
So far I've tried this:
public Inventory()
{
InitializeComponent();
Loaded += Inventory_Loaded;
}
private void Inventory_Loaded(object sender, RoutedEventArgs e)
{
this.MyData.QueryParameters.Add(new Parameter { ParameterName = "userID", Value = RiaContext.Current.User.Name});
}
But since InitializeComponent() fires first, and loades the data, which causes the DomainDataSource to bomb due to the Query not having any parameters to run... it didn't work.
Next I tried this...
[xaml file]
<RiaControls:DomainDataSource x:Name="MyData" LoadSize="20" QueryName="GetStockByCompany" AutoLoad="True" LoadingData="MyData_LoadingData">
[cs file]
private void MyData_LoadingData(object sender, LoadingDataEventArgs e)
{
this.MyData.QueryParameters.Add(new Parameter { ParameterName = "userID", Value = RiaContext.Current.User.Name});
}
Unfortunately, the event never fired. I'm not sure why.
I even tried this:
[xaml file]
<RiaControls:DomainDataSource x:Name="MyData" LoadSize="20" QueryName="GetStockByCompany" AutoLoad="True" LoadedData="MyData_LoadedData">
[cs file]
private void MyData_LoadedData(object sender, LoadedDataEventArgs e)
{
this.MyData.QueryParameters.Add(new Parameter { ParameterName = "userID", Value = RiaContext.Current.User.Name});
}
But that was just dumb.
I'm at a loss. How do I load this query, with the parameter, as the page loads?
Thanks!
Hmmm I not a specific answer to your problem but I may know a way to avoid the situation entirely.
I noticed you have a method named "GetStockByCompany" that accept the currently logged in user as a parameter...
You can completely remove the need for the parameter and instead on your server side query for "GetStockByCompany" use this in your "Where" part:
this.ServiceContext.User.Identity.Name
Ex - Getting all the albums for the currently logged in user:
album = this.Context.AlbumSet
.Where(n => n.AlbumId == AlbumId)
.Where(n => n.aspnet_Users.UserName == this.ServiceContext.User.Identity.Name)
.First();
Binding the query parameter works, the typical usage is that you bind it directly to controls.
To set the parameter in code behind, give the parameter a name and set the value property. There's no need to add the whole parameter in code behind.

MVC data annotation layer: where to put the set CurrentUICulture statement?

I am getting crazy with the localization of an MVC application.
After a recent question of mine I have followed this approach:
The language is stored in Session["lang"]
Each controller inherits from my own BaseController, which overrides OnActionExecuting, and in this method reads the Session and sets CurrentCulture and CurrentUICulture
This works great, until the Data Annotation Layer comes in. It seems like it gets called BEFORE the action itself is executed, and therefore it always gets the error messages in the default language!
The fields declarations go like this:
[Required(ErrorMessageResourceName = "validazioneRichiesto", ErrorMessageResourceType = typeof(Resources.Resources))]
public string nome { get; set; }
So, is there any reasonable place where I can put the call?
I initialize the Data Annotation Model Binder in my Controller constructor.
public CardController() : base() {
ModelBinders.Binders.DefaultBinder =
new Microsoft.Web.Mvc.DataAnnotations.DataAnnotationsModelBinder();
}
So, since Session is always null in the controller's constructor, and the action override is called AFTER the data annotation has validated the fields, where can I possibly set the CurrentCulture and CurrentUICulture to get localized errors?
I tried putting the CurrentCulture and CurrentUiCulture in Application_* (e.g. Application_AcquireRequestState or Application_PreRequestHandlerExecute) seems to have no effect...
As the culture is a global user setting, I am using it in the global.asax.cs file in the Application_BeginRequest method.
It does the work for me (using cookies) but the Session is also available there.
EDIT:
/by Brock Allen:
http://www.velocityreviews.com/forums/t282576-aspnet-20-session-availability-in-globalasax.html/
Session is available in PreRequesthandlerExecute.
The problem is that your code is being executed for every request into the server, and some requests (like ones for WebResourxe.axd) don't utlilize
Session (because the handler doesn't implement IRequireSessionState). So change your code to only access Session if that request has access to it.
Change your code to do this:
protected void Application_PreRequestHandlerExecute(Object sender, EventArgs e)
{
if (Context.Handler is IRequiresSessionState || Context.Handler is IReadOnlySessionState)
SetCulture();
}
Anyway, not sure if it works with mvc
After reading your question more carefully, I think that your problem is more in the way the resources are compiled.
Check in the Resource.resx properties, find Build Action and set it to Embedded Resource
Also change Custom Tool to PublicResXFileCodeGenerator
alt text http://img208.imageshack.us/img208/2126/captuream.png
I have tested a mini solution and works perfectly.
If you have more problem, I can send the example to you.
Another approach you can use is to put the lang in the URL, with this benefits:
The site is spidered by search engines in different languages
The user can send a URL to a friend in the selected language
To do this, use the Application_BeginRequest method in Global.asax
Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
Dim lang As String
If HttpContext.Current.Request.Path.Contains("/en/") Then
lang = "en"
Else
lang = "es"
End If
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(lang)
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(lang)
End Sub
See this question for more info on how to implement it
The OP posted the final solution as the following, thanks to the accepted answer by twk:
void Application_PreRequestHandlerExecute(object sender, EventArgs e) {
if (Context.Handler is IRequiresSessionState ||
Context.Handler is IReadOnlySessionState) {
if (Session["lang"] == null) {
Session["lang"] = "it";
}
if (Request.QueryString["lang"] == "it" || Request.QueryString["lang"] == "en") {
Session["lang"] = Request.QueryString["lang"];
}
string lang1 = Session["lang"].ToString();
string lang2 = Session["lang"].ToString().ToUpper();
if (lang2 == "EN")
lang2 = "GB";
System.Threading.Thread.CurrentThread.CurrentCulture =
System.Globalization.CultureInfo.CreateSpecificCulture(lang1 + "-" + lang2);
System.Threading.Thread.CurrentThread.CurrentUICulture =
System.Globalization.CultureInfo.CreateSpecificCulture(lang1 + "-" + lang2);
}
}

How can I find what search terms (if any) brought a user to my site?

I want to create dynamic content based on this. I know it's somewhere, as web analytics engines can get this data to determine how people got to your site (referrer, search terms used, etc.), but I don't know how to get at it myself.
You can use the "referer" part of the request that the user sent to figure out what he searched for. Example from Google:
http://www.google.no/search?q=stack%20overflow
So you must search the string (in ASP(.NET) this can be found be looking in Request.Referer) for "q=" and then URLDecode the contents.
Also, you should take a look at this article that talks more about referrers and also other methods to track your visitors:
http://www.15seconds.com/issue/021119.htm
This is some code to backup the idea of using a querystring method and if that's not available using the UrlReferrer property of the Request object. This can then be stashed in a session object (or somewhere else if that works better for you) so that you can track the source between pages. (Page_Load doesn't seem to be formatted correctly inside the code sample here)
public void Page_Load(Object Sender, EventArgs E) {
if (null == Session["source"] || Session["source"].ToString().Equals(string.Empty)) {
if (Request.QueryString["src"] != null) {
Session["source"] = Server.UrlDecode(Request.QueryString["src"].ToString());
} else {
if (Request.UrlReferrer != null) {
Session["source"] = Request.UrlReferrer.ToString();
} else {
Session["source"] = string.Empty;
}
}
}}

Resources