Skip to content
This repository has been archived by the owner on Feb 25, 2020. It is now read-only.

Prototype code for packet handler decorators #40

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions Demos/Demo.Decorators/AuthorizationHandlerDecorator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Networker.Common;
using Networker.Common.Abstractions;
using System.Reflection;
using System.Threading.Tasks;

namespace Demo.Decorators
{
// TODO: Test and make sure works with inheritance and multiple methods
// Aka replace SomePacket with PacketBase and test on SomePacket and SomeOtherPacket
public class AuthorizationHandlerDecorator : PacketHandlerDecorator<SomePacket>
{
public override async Task Process(SomePacket packet, IPacketContext context)
{
Role userRole = Role.AuthenticatedUser; // Get from service
Role minimumRequiredRole = GetMinimumRequiredRole(packet);
if (userRole >= minimumRequiredRole)
{
await ContinueProcessing(packet, context);
}
}

private Role GetMinimumRequiredRole(SomePacket packet)
{
RequireRoleAttribute authorize = GetProcessMethodInfo(packet).GetCustomAttribute<RequireRoleAttribute>();
if (authorize == null)
{
return Role.GuestUser; // No authorization required
}
else
{
return authorize.Role;
}
}
}
}
10 changes: 10 additions & 0 deletions Demos/Demo.Decorators/AuthorizeAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Networker.Common;
using System;

namespace Demo.Decorators
{
public class AuthorizeAttribute : DecoratorAttribute
{
public override Type[] Types => new Type[] { typeof(AuthorizationHandlerDecorator) };
}
}
12 changes: 12 additions & 0 deletions Demos/Demo.Decorators/Demo.Decorators.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\Networker\Networker.csproj" />
</ItemGroup>

</Project>
10 changes: 10 additions & 0 deletions Demos/Demo.Decorators/LogAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Networker.Common;
using System;

namespace Demo.Decorators
{
public class LogAttribute : DecoratorAttribute
{
public override Type[] Types => new Type[] { typeof(LoggingHandlerDecorator) };
}
}
23 changes: 23 additions & 0 deletions Demos/Demo.Decorators/LoggingHandlerDecorator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Microsoft.Extensions.Logging;
using Networker.Common;
using Networker.Common.Abstractions;
using System.Threading.Tasks;

namespace Demo.Decorators
{
public class LoggingHandlerDecorator : PacketHandlerDecorator<SomePacket>
{
private readonly ILogger logger;

public LoggingHandlerDecorator(ILogger logger)
{
this.logger = logger;
}

public override async Task Process(SomePacket packet, IPacketContext context)
{
logger.LogInformation("Logging to fake database: SomePacket received, SomePacket.SomeString = " + packet.SomeString);
await ContinueProcessing(packet, context);
}
}
}
10 changes: 10 additions & 0 deletions Demos/Demo.Decorators/MeasureExecutionAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Networker.Common;
using System;

namespace Demo.Decorators
{
public class MeasureExecutionAttribute : DecoratorAttribute
{
public override Type[] Types => new Type[] { typeof(MeasureExecutionHandlerDecorator) };
}
}
27 changes: 27 additions & 0 deletions Demos/Demo.Decorators/MeasureExecutionHandlerDecorator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Networker.Common;
using Networker.Common.Abstractions;

namespace Demo.Decorators
{
public class MeasureExecutionHandlerDecorator : PacketHandlerDecorator<SomePacket>
{
private readonly ILogger logger;

public MeasureExecutionHandlerDecorator(ILogger logger)
{
this.logger = logger;
}

public override async Task Process(SomePacket packet, IPacketContext context)
{
Stopwatch stopwatch = Stopwatch.StartNew();
await ContinueProcessing(packet, context);
stopwatch.Stop();
long elapsedMs = stopwatch.ElapsedMilliseconds;
logger.LogInformation("SomePacket took " + elapsedMs + " to process.");
}
}
}
28 changes: 28 additions & 0 deletions Demos/Demo.Decorators/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Microsoft.Extensions.Logging;
using Networker.Server;
using System;

namespace Demo.Decorators
{
class Program
{
static void Main(string[] args)
{
var server = new ServerBuilder().
UseTcp(1000).
SetMaximumConnections(6000).
UseUdp(5000).
ConfigureLogging(loggingBuilder =>
{
loggingBuilder.SetMinimumLevel(
LogLevel.Debug);
}).
RegisterPacketHandlerModule<SomePacketHandlerModule>().
Build();

server.Start();

Console.ReadLine();
}
}
}
14 changes: 14 additions & 0 deletions Demos/Demo.Decorators/RequireRoleAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;

namespace Demo.Decorators
{
public class RequireRoleAttribute : Attribute
{
public Role Role { get; private set; }

public RequireRoleAttribute(Role role)
{
Role = role;
}
}
}
12 changes: 12 additions & 0 deletions Demos/Demo.Decorators/Role.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Demo.Decorators
{
public enum Role
{
GuestUser,
AuthenticatedUser,
QATester,
Moderator,
Developer,
Administrator
}
}
7 changes: 7 additions & 0 deletions Demos/Demo.Decorators/SomePacket.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Demo.Decorators
{
public class SomePacket
{
public string SomeString { get; set; }
}
}
41 changes: 41 additions & 0 deletions Demos/Demo.Decorators/SomePacketHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Networker.Common;
using Networker.Common.Abstractions;

namespace Demo.Decorators
{
// Build in attribute
[Decorate(
typeof(AuthorizationHandlerDecorator),
typeof(LoggingHandlerDecorator),
typeof(MeasureExecutionHandlerDecorator))]

// -- or --

// Custom attributes
[Authorize, Log, MeasureExecution]
public class SomePacketHandler : PacketHandlerBase<SomePacket>
{
private readonly ILogger logger;

public SomePacketHandler(ILogger logger)
{
this.logger = logger;
}

[RequireRole(Role.Administrator)]
public override Task Process(SomePacket packet, IPacketContext context)
{
Thread.Sleep(1000); // To test measure execution time decorator
logger.LogInformation("Processing SomePacket.");
return Task.CompletedTask;
}

public string GetLog(SomePacket packet)
{
return "Packet received with data: " + packet.SomeString;
}
}
}
13 changes: 13 additions & 0 deletions Demos/Demo.Decorators/SomePacketHandlerModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using Networker.Common;

namespace Demo.Decorators
{
public class SomePacketHandlerModule : PacketHandlerModuleBase
{
public SomePacketHandlerModule()
{
this.AddPacketHandler<SomePacket, SomePacketHandler>();
}
}
}
7 changes: 7 additions & 0 deletions Networker.sln
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tutorial.Client", "Demos\Tu
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tutorial.Common", "Demos\Tutorial.Common\Tutorial.Common.csproj", "{D6542750-89CB-4D09-9E7B-EAAA392D4E5D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo.Decorators", "Demos\Demo.Decorators\Demo.Decorators.csproj", "{AE162773-D7F0-44D7-A732-8743D08C9CC1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -93,6 +95,10 @@ Global
{D6542750-89CB-4D09-9E7B-EAAA392D4E5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D6542750-89CB-4D09-9E7B-EAAA392D4E5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D6542750-89CB-4D09-9E7B-EAAA392D4E5D}.Release|Any CPU.Build.0 = Release|Any CPU
{AE162773-D7F0-44D7-A732-8743D08C9CC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AE162773-D7F0-44D7-A732-8743D08C9CC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AE162773-D7F0-44D7-A732-8743D08C9CC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AE162773-D7F0-44D7-A732-8743D08C9CC1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -110,6 +116,7 @@ Global
{6ADC9B53-4FB7-487A-8D61-CC4981C30269} = {84C88291-7FC8-47AC-84FA-9CF5DF39028C}
{F7226539-2B8F-492F-B9BD-DA81E298AE67} = {84C88291-7FC8-47AC-84FA-9CF5DF39028C}
{D6542750-89CB-4D09-9E7B-EAAA392D4E5D} = {84C88291-7FC8-47AC-84FA-9CF5DF39028C}
{AE162773-D7F0-44D7-A732-8743D08C9CC1} = {84C88291-7FC8-47AC-84FA-9CF5DF39028C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A022B3C8-E866-480E-82A5-17935BA812CC}
Expand Down
16 changes: 16 additions & 0 deletions Networker/Common/DecorateAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;

namespace Networker.Common
{
public class DecorateAttribute : DecoratorAttribute
{
public override Type[] Types => types;

private readonly Type[] types;

public DecorateAttribute(params Type[] types)
{
this.types = types;
}
}
}
9 changes: 9 additions & 0 deletions Networker/Common/DecoratorAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;

namespace Networker.Common
{
public abstract class DecoratorAttribute : Attribute
{
public abstract Type[] Types { get; }
}
}
55 changes: 55 additions & 0 deletions Networker/Common/PacketHandlerDecorator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using Networker.Common.Abstractions;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;

namespace Networker.Common
{
public abstract class PacketHandlerDecorator<T> : PacketHandlerBase<T> where T: class
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This particular way of doing it would work well as an extension project, but I would like to avoid the dependency on PacketHandlerBase as you are able to use just an IPacketHandler now.

Would this also mean you'd need a new PacketHandlerDecorator for each packet type?

Copy link
Contributor Author

@snarlynarwhal snarlynarwhal Mar 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah implementing IPacketHandler makes more sense. The decorator has helper methods to get the target handler method info based on the packet type. This assumes the handler methods accept a base packet type though. I definitely need to think more about this.

Where should I wire up the decorators? Once I get it to a testable state, I can play around with ideas to make sure a single decorator can handle multiple packet types.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've got a new way of registering packets + handlers coming in soon which might be the best place to wire up the decorator, I'll let you know when it's ready.

Copy link
Contributor Author

@snarlynarwhal snarlynarwhal Mar 27, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright sounds good! Also, it might be preferable to assign decorators using the builder instead of attributes. That approach would require a fluent interface instead of method chaining, which I can add later on if you think it's a good idea.

{
private PacketHandlerBase<T> decoratedHandler;
private Dictionary<Type, MethodInfo> processMethodInfoCache = new Dictionary<Type, MethodInfo>();

public void Decorate(PacketHandlerBase<T> handler)
{
decoratedHandler = handler;
}

protected Task ContinueProcessing(T packet, IPacketContext context)
{
return decoratedHandler.Process(packet, context);
}

protected MethodInfo GetProcessMethodInfo(T packet)
{
return GetProcessMethodInfo(packet.GetType());
}

protected MethodInfo GetProcessMethodInfo<S>() where S : T
{
return GetProcessMethodInfo(typeof(S));
}

protected MethodInfo GetProcessMethodInfo(Type packetType)
{
if (processMethodInfoCache.ContainsKey(packetType))
{
return processMethodInfoCache[packetType];
}

MethodInfo processMethodInfo = null;
if (decoratedHandler is PacketHandlerDecorator<T> decoratorHandler)
{
processMethodInfo = decoratorHandler.GetProcessMethodInfo(packetType);
}
else
{
Type[] processParameterTypes = new Type[] { packetType, typeof(IPacketContext) };
processMethodInfo = GetType().GetMethod("Process", processParameterTypes);
}
processMethodInfoCache.Add(packetType, processMethodInfo);
return processMethodInfo;
}
}
}