Skip to content

Commit

Permalink
Merge pull request #52 from engineering87/develop
Browse files Browse the repository at this point in the history
refactor(core): extract common logic to base classes
  • Loading branch information
engineering87 authored Nov 24, 2024
2 parents 097b273 + eb3fda6 commit 947b713
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 395 deletions.
96 changes: 96 additions & 0 deletions src/WART-Core/Controllers/WartBaseController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// (c) 2024 Francesco Del Re <[email protected]>
// This code is licensed under MIT license (see LICENSE.txt for details)
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using WART_Core.Entity;
using WART_Core.Filters;

namespace WART_Core.Controllers
{
public abstract class WartBaseController<THub> : Controller where THub : Hub
{
private readonly ILogger _logger;
private readonly IHubContext<THub> _hubContext;
private const string RouteDataKey = "REQUEST";

protected WartBaseController(IHubContext<THub> hubContext, ILogger logger)
{
_hubContext = hubContext;
_logger = logger;
}

/// <summary>
/// Adds the request objects to RouteData.
/// </summary>
/// <param name="context">The action executing context.</param>
public override void OnActionExecuting(ActionExecutingContext context)
{
context?.RouteData.Values.Add(RouteDataKey, context.ActionArguments);
base.OnActionExecuting(context);
}

/// <summary>
/// Processes the executed action and sends the event to the SignalR hub if applicable.
/// </summary>
/// <param name="context">The action executed context.</param>
public override async void OnActionExecuted(ActionExecutedContext context)
{
if (context?.Result is ObjectResult objectResult)
{
var exclusion = context.Filters.Any(f => f.GetType().Name == nameof(ExcludeWartAttribute));
if (!exclusion && context.RouteData.Values.TryGetValue(RouteDataKey, out var request))
{
var httpMethod = context.HttpContext?.Request.Method;
var httpPath = context.HttpContext?.Request.Path;
var remoteAddress = context.HttpContext?.Connection.RemoteIpAddress?.ToString();
var response = objectResult.Value;

var wartEvent = new WartEvent(request, response, httpMethod, httpPath, remoteAddress);
await SendToHub(wartEvent, [.. context.Filters]);
}
}

base.OnActionExecuted(context);
}

/// <summary>
/// Sends the current event to the SignalR hub.
/// </summary>
/// <param name="wartEvent">The current WartEvent.</param>
/// <param name="filters">The list of filters applied to the request.</param>
private async Task SendToHub(WartEvent wartEvent, List<IFilterMetadata> filters)
{
try
{
if (filters.Any(f => f.GetType().Name == nameof(GroupWartAttribute)))
{
var wartGroup = filters.OfType<GroupWartAttribute>().FirstOrDefault();
var groups = wartGroup?.GroupNames;
if (groups != null)
{
foreach (var group in groups)
{
await _hubContext.Clients.Group(group).SendAsync("Send", wartEvent.ToString());
_logger?.LogInformation($"Group: {group}, WartEvent: {wartEvent}");
}
}
}
else
{
await _hubContext.Clients.All.SendAsync("Send", wartEvent.ToString());
_logger?.LogInformation("Event: {EventName}, Details: {EventDetails}", nameof(WartEvent), wartEvent.ToString());
}
}
catch (Exception ex)
{
_logger?.LogError(ex, "Error sending WartEvent to clients");
}
}
}
}
106 changes: 2 additions & 104 deletions src/WART-Core/Controllers/WartController.cs
Original file line number Diff line number Diff line change
@@ -1,121 +1,19 @@
// (c) 2019 Francesco Del Re <[email protected]>
// This code is licensed under MIT license (see LICENSE.txt for details)
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using WART_Core.Entity;
using WART_Core.Filters;
using WART_Core.Hubs;

namespace WART_Core.Controllers
{
/// <summary>
/// The WART Controller
/// </summary>
public class WartController : Controller
public class WartController : WartBaseController<WartHub>
{
private readonly ILogger<WartController> _logger;
private readonly IHubContext<WartHub> _hubContext;
private const string RouteDataKey = "REQUEST";

public WartController(IHubContext<WartHub> hubContext, ILogger<WartController> logger)
: base(hubContext, logger)
{
_hubContext = hubContext;
_logger = logger;
}

public WartController(IHubContext<WartHub> hubContext)
{
_hubContext = hubContext;
}

/// <summary>
/// WART OnActionExecuting override
/// </summary>
/// <param name="context">ActionExecutedContext context</param>
public override void OnActionExecuting(ActionExecutingContext context)
{
// add the request objects to RouteData
context?.RouteData.Values.Add(RouteDataKey, context.ActionArguments);

base.OnActionExecuting(context);
}

/// <summary>
/// WART OnActionExecuted override
/// </summary>
/// <param name="context">ActionExecutedContext context</param>
public override async void OnActionExecuted(ActionExecutedContext context)
{
if (context?.Result is ObjectResult objectResult)
{
// check for wart exclusion
var exclusion = context.Filters.Any(f => f.GetType().Name == nameof(ExcludeWartAttribute));
if (!exclusion)
{
// check for RouteData key existence
if (context.RouteData.Values.TryGetValue(RouteDataKey, out var request))
{
// get the request objects from RouteData
var httpMethod = context.HttpContext?.Request.Method;
var httpPath = context.HttpContext?.Request.Path;
var remoteAddress = context.HttpContext?.Connection.RemoteIpAddress?.ToString();
// get the object response
var response = objectResult.Value;
// create the new WartEvent and broadcast to all clients
var wartEvent = new WartEvent(request, response, httpMethod, httpPath, remoteAddress);
await SendToHub(wartEvent, [.. context.Filters]);
}
}
}

base.OnActionExecuted(context);
}

/// <summary>
/// Send the current event to the SignalR hub.
/// </summary>
/// <param name="wartEvent">The current WartEvent</param>
/// <param name="filters">The current Filters</param>
/// <returns></returns>
private async Task SendToHub(WartEvent wartEvent, List<IFilterMetadata> filters)
{
try
{
// check for specific groups
if (filters.Any(f => f.GetType().Name == nameof(GroupWartAttribute)))
{
// get the list of filters of type WartGroupAttribute
var wartGroup = filters.FirstOrDefault(f => f.GetType() == typeof(GroupWartAttribute)) as GroupWartAttribute;
var groups = wartGroup?.GroupNames;
foreach (var group in groups)
{
// send to the specific group
await _hubContext?.Clients
.Group(group)
.SendAsync("Send", wartEvent.ToString());

_logger?.LogInformation($"Group: {group}, WartEvent: {wartEvent}");
}
}
else
{
// send to all clients
await _hubContext?.Clients.All
.SendAsync("Send", wartEvent.ToString());

_logger?.LogInformation("Event: {EventName}, Details: {EventDetails}", nameof(WartEvent), wartEvent.ToString());
}
}
catch (Exception ex)
{
_logger?.LogError(ex, "Error sending WartEvent to clients");
}
}
}
}
107 changes: 3 additions & 104 deletions src/WART-Core/Controllers/WartControllerJwt.cs
Original file line number Diff line number Diff line change
@@ -1,120 +1,19 @@
// (c) 2021 Francesco Del Re <[email protected]>
// This code is licensed under MIT license (see LICENSE.txt for details)
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
using System;
using WART_Core.Entity;
using WART_Core.Hubs;
using System.Linq;
using WART_Core.Filters;
using System.Collections.Generic;

namespace WART_Core.Controllers
{
/// <summary>
/// The WART Controller
/// The WART Controller with JWT authentication
/// </summary>
public class WartControllerJwt : Controller
public class WartControllerJwt : WartBaseController<WartHubJwt>
{
private readonly ILogger<WartControllerJwt> _logger;
private readonly IHubContext<WartHubJwt> _hubContext;
private const string RouteDataKey = "REQUEST";

public WartControllerJwt(IHubContext<WartHubJwt> hubContext, ILogger<WartControllerJwt> logger)
: base(hubContext, logger)
{
_hubContext = hubContext;
_logger = logger;
}

public WartControllerJwt(IHubContext<WartHubJwt> hubContext)
{
_hubContext = hubContext;
}

/// <summary>
/// WART OnActionExecuting override
/// </summary>
/// <param name="context">ActionExecutedContext context</param>
public override void OnActionExecuting(ActionExecutingContext context)
{
// add the request objects to RouteData
context?.RouteData.Values.Add(RouteDataKey, context.ActionArguments);

base.OnActionExecuting(context);
}

/// <summary>
/// WART OnActionExecuted override
/// </summary>
/// <param name="context">ActionExecutedContext context</param>
public override async void OnActionExecuted(ActionExecutedContext context)
{
if (context?.Result is ObjectResult objectResult)
{
// check for wart exclusion
var exclusion = context.Filters.Any(f => f.GetType().Name == nameof(ExcludeWartAttribute));
if (!exclusion)
{
// check for RouteData key existence
if (context.RouteData.Values.TryGetValue(RouteDataKey, out var request))
{
// get the request objects from RouteData
var httpMethod = context.HttpContext?.Request.Method;
var httpPath = context.HttpContext?.Request.Path;
var remoteAddress = context.HttpContext?.Connection.RemoteIpAddress?.ToString();
// get the object response
var response = objectResult.Value;
// create the new WartEvent and broadcast to all clients
var wartEvent = new WartEvent(request, response, httpMethod, httpPath, remoteAddress);
await SendToHub(wartEvent, [.. context.Filters]);
}
}
}

base.OnActionExecuted(context);
}

/// <summary>
/// Send the current event to the SignalR hub.
/// </summary>
/// <param name="wartEvent">The current WartEvent</param>
/// <param name="filters">The current Filters</param>
/// <returns></returns>
private async Task SendToHub(WartEvent wartEvent, List<IFilterMetadata> filters)
{
try
{
// check for specific groups
if (filters.Any(f => f.GetType().Name == nameof(GroupWartAttribute)))
{
// get the list of filters of type WartGroupAttribute
var wartGroup = filters.FirstOrDefault(f => f.GetType() == typeof(GroupWartAttribute)) as GroupWartAttribute;
var groups = wartGroup?.GroupNames;
foreach (var group in groups)
{
await _hubContext?.Clients
.Group(group)
.SendAsync("Send", wartEvent.ToString());

_logger?.LogInformation($"Group: {group}, WartEvent: {wartEvent}");
}
}
else
{
// send to all clients
await _hubContext?.Clients.All
.SendAsync("Send", wartEvent.ToString());

_logger?.LogInformation("Event: {EventName}, Details: {EventDetails}", nameof(WartEvent), wartEvent.ToString());
}
}
catch (Exception ex)
{
_logger?.LogError(ex, "Error sending WartEvent to clients");
}
}
}
}
23 changes: 22 additions & 1 deletion src/WART-Core/Entity/WartEvent.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// (c) 2019 Francesco Del Re <[email protected]>
// This code is licensed under MIT license (see LICENSE.txt for details)
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using WART_Core.Helpers;
using WART_Core.Serialization;
Expand Down Expand Up @@ -101,5 +102,25 @@ public T GetResponseObject<T>() where T : class
{
return SerializationHelper.Deserialize<T>(JsonResponsePayload);
}
}

/// <summary>
/// Converts the WartEvent instance into a dictionary for flexible logging or data analysis.
/// </summary>
/// <returns>A dictionary representation of the event.</returns>
public Dictionary<string, object> ToDictionary()
{
return new Dictionary<string, object>
{
{ "EventId", EventId },
{ "TimeStamp", TimeStamp },
{ "UtcTimeStamp", UtcTimeStamp },
{ "HttpMethod", HttpMethod },
{ "HttpPath", HttpPath },
{ "RemoteAddress", RemoteAddress },
{ "JsonRequestPayload", JsonRequestPayload },
{ "JsonResponsePayload", JsonResponsePayload },
{ "ExtraInfo", ExtraInfo }
};
}
}
}
Loading

0 comments on commit 947b713

Please sign in to comment.