简体   繁体   中英

Log user activity in ASP.NET MVC application

I am planning to create filter based on ActionFilterAttribute to log user activity:

public class LogAttribute : ActionFilterAttribute
{
    public ActionType Type { get; set; }

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
      // logic for log
    }
}

I would like to use this attribute on Actions in my controllers. I'd like to log some values X,Y,Z which user is set in my views and also some action related info. In that case I have to probably send these X,Y,Z to my actions as parameters and then in Attribute code get these values from ResultExecutedContext. But this approach confused a little bit me because I will have redundant parameters on each of my actions.

Is there any good approach to share values from View to C# code?

I am able to achieve this by following the below way

Create a table(name - Portal_Logger and D_MASTER )

Next, Created a filter which will track the activity for user/s like below

LoggerActionFilter.cs

using System;
using System.Web.Mvc;
using System.Net;
using System.Linq;
using System.Net.Sockets;
using MyMvcApplication.Models.Entity;
using MyMvcApplication.Utilities;

namespace MyMvcApplication.Filters
{
    /// <summary>
    /// 
    /// </summary>
    public class LoggerActionFilter : ActionFilterAttribute
    {
        private static TimeZoneInfo INDIAN_ZONE = TimeZoneInfo.FindSystemTimeZoneById("India Standard Time");
        IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            // Stores the Request in an Accessible object
            var request = filterContext.HttpContext.Request;
            var visitCount = Convert.ToInt64(filterContext.HttpContext.Session["LoggerActivityTracking"].ToString());
            // Generate an audit
            Portal_Logger aLogger = new Portal_Logger()
            {
                // Your Logger Identifier     
                LoggerId = Guid.NewGuid(),
                //Logged On User Id
                LogedUserId = Convert.ToInt32(filterContext.HttpContext.Session["LogedUserID"]),
                // The IP Address of the Request
                IPAddress = Convert.ToString(ipHostInfo.AddressList.FirstOrDefault(address => address.AddressFamily == AddressFamily.InterNetwork)),
                //Get the Web Page name(from the URL that was accessed)
                AreaAccessed = UserActivityUtility.GetWebPageName(request.RawUrl == "/"? "/Home/UserLandingPage" : request.RawUrl),
                // Creates our Timestamp
                Timestamp = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, INDIAN_ZONE),
                VisitorSessionId = visitCount
            };
            
            // Stores the Logger in the Database
            using (dbEntities context = new dbEntities())
            {
                if (aLogger.LogedUserId != null)
                {
                    aLogger.LogedUserEmpId = context.D_MASTER
                        .Where(x => x.User_Id == aLogger.LogedUserId)
                        .Select(x => x.Emp_Id).FirstOrDefault();
                    aLogger.LogedUserEmpName = context.D_MASTER
                        .Where(x => x.User_Id == aLogger.LogedUserId)
                        .Select(x => x.Emp_Name).FirstOrDefault();
                    aLogger.AccessedType = aLogger.AreaAccessed.Contains("Report") ? "Report" : "Page";
                }
                context.Portal_Logger.Add(aLogger);
                context.SaveChanges();
            }
            // Finishes executing the Logger as normal 
            base.OnActionExecuting(filterContext);
        }

    }
}

Note : Session["LoggerActivityTracking"] and Session["LogedUserID"] data i am initializing in a Separate Controller( AuthController ) Action( ValidateUser ) which executes when user logging into the application.

Below i am using a Utility class which will get the exact name for the web page from the RawUrl(eg : /Home/MyController or /Home/MyController1?id=1&name=chandan )

Below is my Utility Class

UserActivityUtility.cs

using System;       
namespace MyMvcApplication.Utilities
{
    public static class UserActivityUtility
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="pageName"></param>
        /// <returns></returns>
        public static string GetWebPageName(string pageName)
        {
            string formattedPageName = string.Empty;
            //For All user web pages
            if (pageName.Contains("UserLandingPage"))
            {
                formattedPageName = "Home - App Management";
            }            
            //For Report Web Pages 
            else if (pageName.Contains("AppUtilization"))
            {
                formattedPageName = "Application Utilization Report";
            }           
            else if (pageName.Contains("AlertReport"))
            {
                formattedPageName = "Alert Report";
            }                       
            else
            {
                formattedPageName = pageName;
            }
            return formattedPageName;
        }
    }
}

And finally apply that attribute to the Controller Action/s(which actions you want to track) like below

public class HomeController : Controller
{
    [LoggerActionFilter(Order = 1)]
    public ActionResult UserLandingPage()
    {
        return View();
    }
}


public class AdminController : Controller
{
    [LoggerActionFilter(Order = 1)]
    public ActionResult CreateUser()
    {
        return View();
    }
}


public class ReportController : Controller
{
    [LoggerActionFilter(Order = 1)]
    public ActionResult AppUtilization()
    {
        return View();
    }
}

And below are the "MS SQL StoredProcedure" to get the user login details and page wise access summary details

User Login Details

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

CREATE Procedure [dbo].[sp_Portal_GetUserLoginDetails_Logger]
(
@FromDate DATE,
@ToDate DATE
)
AS
BEGIN
        --EXEC sp_Portal_GetUserLoginDetails_Logger '2017-06-16','2017-06-16'
        SET NOCOUNT ON

        SELECT  u.LogedUserID,u.UserName,u.IPAddress,u.AreaAccessed,u.[Timestamp],u.LastLoggedInDay
        FROM 
        (
                SELECT  DISTINCT l.LogedUserID,e.Emp_Name as 'UserName',l.IPAddress,l.AreaAccessed,l.[Timestamp],l.LastLoggedInDay
                FROM 
                (
                        SELECT * 
                        FROM 
                        (
                            SELECT  Row_Number() Over ( PARTITION BY LogedUserID ORDER BY [Timestamp] DESC) As RowNum, LogedUserID,

                                    IPAddress,AreaAccessed,TIMESTAMP, DATEDIFF(D,TIMESTAMP,CURRENT_TIMESTAMP) AS 'LastLoggedInDay'
                            FROM    Portal_Logger 
                            WHERE   CAST([Timestamp] AS DATE) BETWEEN @FromDate AND @ToDate
                        ) t2
                    WHERE RowNum = 1
                ) l
                JOIN D_MASTER e on l.LogedUserID = e.User_Id                
        ) u     
        ORDER BY u.[Timestamp] DESC     
END

Page Wise Access Summary Details

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[sp_Portal_GetUserAccessedSummary_Logger]
(
@FromDate DATE,
@ToDate DATE
)
AS
BEGIN       
        --EXEC sp_Portal_GetUserAccessedSummary_Logger '2017-06-16','2017-06-16'

        SET NOCOUNT ON      
            SELECT  A.AreaAccessed,
                    COUNT(A.Timestamp) AS NoofTimeAccessed,
                    CONVERT(TIME(0), DATEADD(SECOND,SUM(SessionSeconds), 0)) AS TotalTimeSpent,
                    CONVERT(TIME(0), DATEADD(SECOND,AVG(SessionSeconds), 0)) AS AvgTimeSpent,
                    COUNT(DISTINCT LogedUserID) AS NoofUserAccessed
  
            FROM
            (
                SELECT  
                        A.AreaAccessed,A.Timestamp,
                        ISNULL(DATEDIFF(S,A.Timestamp,ISNULL(LEAD(A.Timestamp,1)OVER(PARTITION BY A.VisitorSessionId ORDER BY A.Timestamp),A.Timestamp)),0) AS SessionSeconds,
                        A.LogedUserID
        
                FROM    Portal_Logger A(NOLOCK)
                JOIN    D_MASTER B(NOLOCK) ON A.LogedUserID=B.User_Id
                WHERE   A.VisitorSessionId IS NOT NULL
                AND     CAST(A.Timestamp AS DATE) BETWEEN @FromDate AND @ToDate

            )A
            GROUP BY A.AreaAccessed
            ORDER BY NoofTimeAccessed DESC      
        SET NOCOUNT OFF
END

The values would have to be set at some point, either by the user or by code. You can however, use the Session array to contain these values. They will be preserved for the duration of the login session.

To store to session

Session["key"] = "data";

and later in code

var val = Session["key"]; // val now has a value of "data";

what I use is a helper method that use reflection and dumps the model into a dictionary, that way you don't have the issue to copy the values elsewhere, everything ships from a single place "your model"

private static Dictionary<string, string> DumpModel(object obt)
        {
            Type type = obt.GetType();
            if (TypeDefinitionIsList(type))
                return null;

            PropertyInfo[] properties = obt.GetType().GetProperties();
            Func<PropertyInfo, string> func = info =>
                                                  {
                                                      if (info.PropertyType.GetInterface(typeof (ICatalogue<>).FullName) != null)
                                                      {
                                                          dynamic propertyValue = info.GetValue(obt, null);
                                                          if (propertyValue != null)
                                                              return string.Format("{{ Id: {0}, Description: {1} }}",
                                                                                   propertyValue.Id,
                                                                                   propertyValue.Description);
                                                          return "null";
                                                      }
                                                      object normalValue = info.GetValue(obt, null);
                                                      if (normalValue == null) return "null";
                                                      return TypeDefinitionIsList(normalValue.GetType()) ? HelpersMessages.NotSupportedList : normalValue.ToString();
                                                  };

            return properties.ToDictionary(x => "model-"+x.Name, func);
        }

you can use it in your filter attribute

var request = filterContext.HttpContext.Request;
object model = filterContext.Controller.ViewData.Model;

and you can use something like

 DumpModel(model);    

use the values to log wharever you need, I hope this helps

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM