Лесен начин за одитиране на заявки с ASP.NET Core компилиран от няколко различни сайта и модифициран да работи с Core
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
public class AuditAttribute : ActionFilterAttribute { //Our value to handle our AuditingLevel public int AuditingLevel { get; set; } protected DateTime start_time; protected int auditID { get; set; } protected readonly IAuditRepository _repository; public AuditAttribute(IAuditRepository repository) { this._repository = repository; this.AuditingLevel = 3; } public override void OnActionExecuting(ActionExecutingContext filterContext) { start_time = DateTime.Now; //Stores the Request in an Accessible object var request = filterContext.HttpContext.Request; TimeSpan duration = (DateTime.Now - start_time); string controller = (string)filterContext.RouteData.Values["controller"]; string action = (string)filterContext.RouteData.Values["action"]; //Generate the appropriate key based on the user's Authentication Cookie //This is overkill as you should be able to use the Authorization Key from //Forms Authentication to handle this. string sessionIdentifier = null; if (request.Cookies[".AspNetCore.Identity.Application"] != null) { sessionIdentifier = string.Join("", MD5.Create().ComputeHash(Encoding.ASCII.GetBytes(request.Cookies[".AspNetCore.Identity.Application"])).Select(s => s.ToString("x2"))); } string userId = null; if (filterContext.HttpContext.User.Identity.IsAuthenticated) { userId = filterContext.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value; } //Generate an audit AuditModel audit = new AuditModel() { SessionID = sessionIdentifier, IPAddress = request.HttpContext.Connection.RemoteIpAddress.ToString(), URLAccessed = Microsoft.AspNetCore.Http.Extensions.UriHelper.GetDisplayUrl(request), TimeAccessed = DateTime.UtcNow, UserID = userId, UserName = filterContext.HttpContext.User.Identity.Name ?? "Anonymous", Data = SerializeRequest(request), Duration = duration, Controller = controller, Action = action, }; //Stores the Audit in the Database var id = _repository.Add(audit); // Достъп до полето се осъществява чрез RouteData.Values["AuditID"]; auditID = id; if (!filterContext.RouteData.Values.ContainsKey("AuditID")) { filterContext.RouteData.Values.Add("AuditID", id); } else { filterContext.RouteData.Values["AuditID"] = id; } base.OnActionExecuting(filterContext); } public override void OnResultExecuted(ResultExecutedContext filterContext) { TimeSpan duration = (DateTime.Now - start_time); var audit = _repository.GetById(auditID); audit.Duration = duration; _repository.Edit(audit); base.OnResultExecuted(filterContext); } //This will serialize the Request object based on the level that you determine private string SerializeRequest(HttpRequest request) { switch (AuditingLevel) { //No Request Data will be serialized case 0: default: return ""; //Basic Request Serialization - just stores Data case 1: return JsonConvert.SerializeObject(new { request.Cookies, request.Headers }); //Middle Level - Customize to your Preferences case 2: return JsonConvert.SerializeObject(new { request.Cookies, request.Headers, request.QueryString }); //Highest Level - Serialize the entire Request object case 3: //We can't simply just Encode the entire request string due to circular references as well //as objects that cannot "simply" be serialized such as Streams, References etc. //return Json.Encode(request); return JsonConvert.SerializeObject(new { request.Cookies, request.Headers, request.QueryString }); } } } |
Няколко неща, които си заслужава да бъдат обяснени:
Използваме стандартното ASP.NET Core cookie, за да установим нещо като сесия за потребителя. Така получаваме всички действия извършени в хубав хронологичен ред.
Целта да прихванем и OnResultExecuted е да запишем колко време е отнело да се изпълни метода (полезно за разрешаване на проблеми от сорта на “Много е бавно”)
С предаване на параметри може да решим колко детайлно да запазим информация за съответния метод – параметри, query strings и т.н.
Използва се по следния начин в базовия controller (прави се по този начин, за да може да извикаме атрибута хем с DI, хем без параметри)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
[TypeFilter(typeof(AuditAttribute))] public class BaseController : Controller ........... /// <summary> /// Идентификатор на одит записа или 0, ако не може да се извлече /// </summary> public int AuditId { get { int auditId = 0; if (RouteData.Values.ContainsKey("AuditID")) { auditId = (int)RouteData.Values["AuditID"]; } return auditId; } } |
Допълнително разполагаме с property AuditId, което показва Id на съответния одит запис, в случай че искаме да добавим допълнителна информация в тялото на метода.
Например по следния начин:
1 |
_auditRepository.EditActivity(AuditId, $"Редакция на потребител с име {user.UserName} с основание: {model.Comment}"); |