diff --git a/BehavioralPatterns.sln b/BehavioralPatterns.sln new file mode 100644 index 0000000..bf2c943 --- /dev/null +++ b/BehavioralPatterns.sln @@ -0,0 +1,46 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25123.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{3820200F-354C-41E6-8F34-B301F5D621C2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4780CECA-2B6F-4F79-97C5-D1B483CFC881}" + ProjectSection(SolutionItems) = preProject + global.json = global.json + EndProjectSection +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "BehavioralPatterns", "src\BehavioralPatterns\BehavioralPatterns.xproj", "{E3092EE0-1282-4AB4-9FA2-0338348D8FD1}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ChainOfResponssibility", "src\ChainOfResponssibility\ChainOfResponssibility.xproj", "{89536824-683F-4351-8789-406D7BDD922D}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CommandPattern", "src\CommandPattern\CommandPattern.xproj", "{CCC23C3A-7A67-40BD-80FB-C4D2C0342E50}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E3092EE0-1282-4AB4-9FA2-0338348D8FD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E3092EE0-1282-4AB4-9FA2-0338348D8FD1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E3092EE0-1282-4AB4-9FA2-0338348D8FD1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E3092EE0-1282-4AB4-9FA2-0338348D8FD1}.Release|Any CPU.Build.0 = Release|Any CPU + {89536824-683F-4351-8789-406D7BDD922D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {89536824-683F-4351-8789-406D7BDD922D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {89536824-683F-4351-8789-406D7BDD922D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {89536824-683F-4351-8789-406D7BDD922D}.Release|Any CPU.Build.0 = Release|Any CPU + {CCC23C3A-7A67-40BD-80FB-C4D2C0342E50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CCC23C3A-7A67-40BD-80FB-C4D2C0342E50}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CCC23C3A-7A67-40BD-80FB-C4D2C0342E50}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CCC23C3A-7A67-40BD-80FB-C4D2C0342E50}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {E3092EE0-1282-4AB4-9FA2-0338348D8FD1} = {3820200F-354C-41E6-8F34-B301F5D621C2} + {89536824-683F-4351-8789-406D7BDD922D} = {3820200F-354C-41E6-8F34-B301F5D621C2} + {CCC23C3A-7A67-40BD-80FB-C4D2C0342E50} = {3820200F-354C-41E6-8F34-B301F5D621C2} + EndGlobalSection +EndGlobal diff --git a/global.json b/global.json new file mode 100644 index 0000000..b51e28b --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "projects": [ "src", "test" ], + "sdk": { + "version": "1.0.0-preview1-002702" + } +} diff --git a/src/BehavioralPatterns/BehavioralPatterns.xproj b/src/BehavioralPatterns/BehavioralPatterns.xproj new file mode 100644 index 0000000..7cf4f9e --- /dev/null +++ b/src/BehavioralPatterns/BehavioralPatterns.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + e3092ee0-1282-4ab4-9fa2-0338348d8fd1 + BehavioralPatterns + .\obj + .\bin\ + v4.6.1 + + + + 2.0 + + + diff --git a/src/BehavioralPatterns/Program.cs b/src/BehavioralPatterns/Program.cs new file mode 100644 index 0000000..a8c7f30 --- /dev/null +++ b/src/BehavioralPatterns/Program.cs @@ -0,0 +1,21 @@ +using ChainOfResponssibility; +using ChainOfResponssibility.PurchaseExample; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; + +namespace BehavioralPatterns +{ + public class Program + { + public static void Main(string[] args) + { + //Chain of responsibillity + //This is usefull when you have a request and you don't know who should process it + ChainOfResponsibillityExamples.Run(); + Console.ReadKey(); + } + } +} diff --git a/src/BehavioralPatterns/Properties/AssemblyInfo.cs b/src/BehavioralPatterns/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..42755e8 --- /dev/null +++ b/src/BehavioralPatterns/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Hewlett-Packard Company")] +[assembly: AssemblyProduct("BehavioralPatterns")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("e3092ee0-1282-4ab4-9fa2-0338348d8fd1")] diff --git a/src/BehavioralPatterns/project.json b/src/BehavioralPatterns/project.json new file mode 100644 index 0000000..f111bfd --- /dev/null +++ b/src/BehavioralPatterns/project.json @@ -0,0 +1,20 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "emitEntryPoint": true + }, + + "dependencies": { + "ChainOfResponssibility": "1.0.0-*", + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0-rc2-3002702" + } + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": "dnxcore50" + } + } +} diff --git a/src/ChainOfResponssibility/ChainOfResponssibility.xproj b/src/ChainOfResponssibility/ChainOfResponssibility.xproj new file mode 100644 index 0000000..a16e95e --- /dev/null +++ b/src/ChainOfResponssibility/ChainOfResponssibility.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 89536824-683f-4351-8789-406d7bdd922d + ChainOfResponssibility + .\obj + .\bin\ + v4.6.1 + + + + 2.0 + + + diff --git a/src/ChainOfResponssibility/Properties/AssemblyInfo.cs b/src/ChainOfResponssibility/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..0d80bcf --- /dev/null +++ b/src/ChainOfResponssibility/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Hewlett-Packard Company")] +[assembly: AssemblyProduct("ChainOfResponssibility")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("89536824-683f-4351-8789-406d7bdd922d")] diff --git a/src/ChainOfResponssibility/PurchaseExample/CheckAuthority.cs b/src/ChainOfResponssibility/PurchaseExample/CheckAuthority.cs new file mode 100644 index 0000000..51a9c3b --- /dev/null +++ b/src/ChainOfResponssibility/PurchaseExample/CheckAuthority.cs @@ -0,0 +1,92 @@ +using System; + +namespace ChainOfResponssibility.PurchaseExample +{ + public class CheckAuthority + { + ManagerPPower manager; + DirectorPPower director; + VicePresidentPPower vp; + PresidentPPower president; + + public CheckAuthority() + { + manager = new ManagerPPower(); + director = new DirectorPPower(); + vp = new VicePresidentPPower(); + president = new PresidentPPower(); + + manager.Successor = director; + director.Successor = vp; + vp.Successor = president; + } + + public void PrintHowMuchEachCanSpend() + { + manager.PrintHowMuchICanSpend(); + director.PrintHowMuchICanSpend(); + vp.PrintHowMuchICanSpend(); + president.PrintHowMuchICanSpend(); + } + + public void SpendMoney() + { + string input = ""; + do + { + Console.WriteLine("Enter the amount to check who should approve your expenditure."); + Console.Write(">"); + input = Console.ReadLine(); + + if (IsDoulbe(input)) + { + double d = double.Parse(input); + manager.ProcessRequest(new PurchaseRequest(d, "I am beautifull")); + } + + } while (!IsExitCode(input)); + } + + + + + + + + + + + + + + + + + + + + + + + private static bool IsExitCode(string input) + { + return "exit".Equals(input, StringComparison.OrdinalIgnoreCase); + } + + private bool IsDoulbe(string input) + { + double x = 0; + return double.TryParse(input, out x); + } + + + public string GetDescriptionOfClass() + { + return @"CheckAuthority allows an employee to spend money + if(manager can approve it) manager will process the request + if (director can approve it) director will process the request + if (vice president can approve it) vice president will process the request + if (president can approve it) president will process the request"; + } + } +} diff --git a/src/ChainOfResponssibility/PurchaseExample/PurchasePower.cs b/src/ChainOfResponssibility/PurchaseExample/PurchasePower.cs new file mode 100644 index 0000000..4efa410 --- /dev/null +++ b/src/ChainOfResponssibility/PurchaseExample/PurchasePower.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ChainOfResponssibility.PurchaseExample +{ + public abstract class PurchasePower + { + protected const double BaseUnit = 500; + + public PurchasePower Successor { get; set; } + + protected abstract double MaximumToSpend { get; } + + protected abstract string Role { get; } + + public void ProcessRequest(PurchaseRequest request) + { + if (request.Ammount <= MaximumToSpend) + { + Console.WriteLine("{0} will approve ${1}", Role, request.Ammount); + } + else + { + if (Successor != null) + Successor.ProcessRequest(request); + else + Console.WriteLine("No one has that much money"); + } + } + + public void PrintHowMuchICanSpend() + { + Console.WriteLine("As a {0} I can spend at most {1}", Role, MaximumToSpend); + } + } +} diff --git a/src/ChainOfResponssibility/PurchaseExample/PurchasePowerClasses.cs b/src/ChainOfResponssibility/PurchaseExample/PurchasePowerClasses.cs new file mode 100644 index 0000000..e56060e --- /dev/null +++ b/src/ChainOfResponssibility/PurchaseExample/PurchasePowerClasses.cs @@ -0,0 +1,30 @@ +namespace ChainOfResponssibility.PurchaseExample +{ + public class ManagerPPower : PurchasePower + { + protected override double MaximumToSpend { get { return BaseUnit * 10; } } + + protected override string Role { get { return "Manager"; } } + } + + public class DirectorPPower : PurchasePower + { + protected override double MaximumToSpend { get { return BaseUnit * 20; } } + + protected override string Role { get { return "Director"; } } + } + + public class VicePresidentPPower : PurchasePower + { + protected override double MaximumToSpend { get { return BaseUnit * 40; } } + + protected override string Role { get { return "VicePresident"; } } + } + + public class PresidentPPower : PurchasePower + { + protected override double MaximumToSpend { get { return BaseUnit * 60; } } + + protected override string Role { get { return "President"; } } + } +} diff --git a/src/ChainOfResponssibility/PurchaseExample/PurchaseRequest.cs b/src/ChainOfResponssibility/PurchaseExample/PurchaseRequest.cs new file mode 100644 index 0000000..532f2c9 --- /dev/null +++ b/src/ChainOfResponssibility/PurchaseExample/PurchaseRequest.cs @@ -0,0 +1,16 @@ +namespace ChainOfResponssibility.PurchaseExample +{ + public class PurchaseRequest + { + public PurchaseRequest(double ammount, string reason) + { + Ammount = ammount; + + Reason = reason; + } + + public double Ammount { get; private set; } + + public string Reason { get; private set; } + } +} \ No newline at end of file diff --git a/src/ChainOfResponssibility/RunChainOfResponsibillityExamples.cs b/src/ChainOfResponssibility/RunChainOfResponsibillityExamples.cs new file mode 100644 index 0000000..6c28c10 --- /dev/null +++ b/src/ChainOfResponssibility/RunChainOfResponsibillityExamples.cs @@ -0,0 +1,91 @@ +using ChainOfResponssibility.PurchaseExample; +using ChainOfResponssibility.TransferFileExample; +using ChainOfResponssibility.Validators.UserEntities; +using System; + +namespace ChainOfResponssibility +{ + public class ChainOfResponsibillityExamples + { + public static void Run() + { + Console.WriteLine(GetPatternDescription()); + GoToNextStep(); + + Console.WriteLine(ExeucteFirstWhenConditionMatchesFlavorDescription()); + GoToNextStep(); + + CheckAuthority moneySpender = new CheckAuthority(); + + Console.WriteLine(moneySpender.GetDescriptionOfClass()); + GoToNextStep(); + + moneySpender.PrintHowMuchEachCanSpend(); + moneySpender.SpendMoney(); + GoToNextStep(); + + TransferFilesManager transferFilesManager = new TransferFilesManager(); + + Console.WriteLine(transferFilesManager.GetDescriptionOfClass()); + GoToNextStep(); + transferFilesManager.TransferFiles(); + + GoToNextStep(); + Console.WriteLine(ExecuteAllUntilConditionIsFalseFlavorDescription()); + Console.WriteLine(ExecuteAllFlavorDescritpion()); + GoToNextStep(); + + UserProcessor userProcessor = new UserProcessor(); + userProcessor.DoStuff(); + + GoToNextStep(); + Console.WriteLine(GetPitfalls()); + } + + private static void GoToNextStep() + { + Console.ReadKey(); + Console.Clear(); + } + + public static string GetPatternDescription() + { + return @" +Decouples sender and receiver (as a sender you don't know who will handle the request/ as a receiver you don't know who the sender is necessary) +Hierarchical in nature +When using the Chain of Responsibility is more effective: +More than one object can handle a command +The handler is not known in advance +The handler should be determined automatically +It’s wished that the request is addressed to a group of objects without explicitly specifying its receiver +The group of objects that may handle the command must be specified in a dynamic way. +Examples in real life: + -java.util.logging.Logger.#log() + -javax.servlet.Filter#doFilter() + -Spring Security Filter Chain"; + } + + public static string GetPitfalls() + { + return @" +Handling/Handler guarantee - you won't be sure that someone can process the request +Runtime configuration risk - the order matters/and it might be that the chain is not configured correctly +Chain length/performance issues - in theory you could see a chain that is too big, and it would be a bottleneck in performance"; + } + + public static string ExeucteFirstWhenConditionMatchesFlavorDescription() + { + return @"Flavor 1: Execute first that matches the condition and exit"; + } + + public static string ExecuteAllUntilConditionIsFalseFlavorDescription() + { + return @"Flavor 2 of chain of responssibility:Execute all elements of chain until the condition does not match"; + } + + public static string ExecuteAllFlavorDescritpion() + { + return @"Flavor 3 of chain of responssibility: Execute all elements of chain"; + } + } +} diff --git a/src/ChainOfResponssibility/TransferFileExample/FileCopyClient.cs b/src/ChainOfResponssibility/TransferFileExample/FileCopyClient.cs new file mode 100644 index 0000000..6427ba7 --- /dev/null +++ b/src/ChainOfResponssibility/TransferFileExample/FileCopyClient.cs @@ -0,0 +1,18 @@ +using System; +using System.IO; + +namespace ChainOfResponssibility.TransferFileExample +{ + public class FileCopyClient : TransferClient + { + protected override bool CanTransferTo(string destination) + { + return destination.StartsWith("file://") || Directory.Exists(Path.GetDirectoryName(destination)); + } + + protected override void Transfer(string source, string destination) + { + Console.WriteLine("File copy from: {0} to {1}", source, destination); + } + } +} \ No newline at end of file diff --git a/src/ChainOfResponssibility/TransferFileExample/FtpTransferClient.cs b/src/ChainOfResponssibility/TransferFileExample/FtpTransferClient.cs new file mode 100644 index 0000000..1d84202 --- /dev/null +++ b/src/ChainOfResponssibility/TransferFileExample/FtpTransferClient.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ChainOfResponssibility.TransferFileExample +{ + public class FtpTransferClient : TransferClient + { + protected override bool CanTransferTo(string destination) + { + return destination.StartsWith("ftp:"); + } + + protected override void Transfer(string source, string destination) + { + Console.WriteLine("FTP transfer file from: {0} to {1}", source, destination); + } + } +} diff --git a/src/ChainOfResponssibility/TransferFileExample/FtpsTransferClient.cs b/src/ChainOfResponssibility/TransferFileExample/FtpsTransferClient.cs new file mode 100644 index 0000000..0cf7d91 --- /dev/null +++ b/src/ChainOfResponssibility/TransferFileExample/FtpsTransferClient.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ChainOfResponssibility.TransferFileExample +{ + public class SftpransferClient : TransferClient + { + protected override bool CanTransferTo(string destination) + { + return destination.StartsWith("sftp:"); + } + + protected override void Transfer(string source, string destination) + { + Console.WriteLine("SFTP transfer file from: {0} to {1}", source, destination); + } + } +} diff --git a/src/ChainOfResponssibility/TransferFileExample/HttpTransferClient.cs b/src/ChainOfResponssibility/TransferFileExample/HttpTransferClient.cs new file mode 100644 index 0000000..d188d23 --- /dev/null +++ b/src/ChainOfResponssibility/TransferFileExample/HttpTransferClient.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ChainOfResponssibility.TransferFileExample +{ + public class HttpTransferClient : TransferClient + { + protected override bool CanTransferTo(string destination) + { + return destination.StartsWith("http:") || destination.StartsWith("https:"); + } + + protected override void Transfer(string source, string destination) + { + Console.WriteLine("Http transfer file from: {0} to {1}", source, destination); + } + } +} diff --git a/src/ChainOfResponssibility/TransferFileExample/TransferClient.cs b/src/ChainOfResponssibility/TransferFileExample/TransferClient.cs new file mode 100644 index 0000000..196a72b --- /dev/null +++ b/src/ChainOfResponssibility/TransferFileExample/TransferClient.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ChainOfResponssibility.TransferFileExample +{ + public abstract class TransferClient + { + protected abstract bool CanTransferTo(string destination); + + protected abstract void Transfer(string source, string destination); + + protected TransferClient Successor { get; private set; } + + public void TransferFile(string source, string destination) + { + if(CanTransferTo(destination)) + { + Transfer(source, destination); + } + else + { + if (Successor != null) + Successor.TransferFile(source, destination); + else + Console.WriteLine("Could not transfer file to: {0}", destination); + } + } + + public TransferClient SetSuccessor(TransferClient successor) + { + return Successor = successor; + } + } +} diff --git a/src/ChainOfResponssibility/TransferFileExample/TransferFilesManager.cs b/src/ChainOfResponssibility/TransferFileExample/TransferFilesManager.cs new file mode 100644 index 0000000..eb0e606 --- /dev/null +++ b/src/ChainOfResponssibility/TransferFileExample/TransferFilesManager.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ChainOfResponssibility.TransferFileExample +{ + public class TransferFilesManager + { + TransferClient tranferClient; + + public TransferFilesManager() + { + tranferClient = new FtpTransferClient(); + tranferClient + .SetSuccessor(new HttpTransferClient()) + .SetSuccessor(new SftpransferClient()) + .SetSuccessor(new FileCopyClient()); + } + + public void TransferFiles() + { + string src = "", dst = ""; + + do + { + Console.WriteLine("Source:"); + Console.Write(">"); + src = Console.ReadLine(); + Console.WriteLine("Destination:"); + Console.Write(">"); + dst = Console.ReadLine(); + + if (!IsExitCode(src) && !IsExitCode(dst)) + tranferClient.TransferFile(src, dst); + + } while (!IsExitCode(src) && !IsExitCode(dst)); + } + + private static bool IsExitCode(string input) + { + return "exit".Equals(input, StringComparison.OrdinalIgnoreCase); + } + + public string GetDescriptionOfClass() + { + return @"TransferFilesManager will try to transfer the file to the destination by trying FTP, SFTP, Http, and simple file copy"; + } + } +} diff --git a/src/ChainOfResponssibility/Validators/ChainValidation.cs b/src/ChainOfResponssibility/Validators/ChainValidation.cs new file mode 100644 index 0000000..fcc50be --- /dev/null +++ b/src/ChainOfResponssibility/Validators/ChainValidation.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ChainOfResponssibility.Validators +{ + public abstract class ChainValidation + { + public ChainValidation Successor { get; private set; } + + public ChainValidation SetSuccessor(ChainValidation successor) + { + return Successor = successor; + } + + protected abstract ValidationResult IsValid(T obj); + + public ValidationResult Validate(T obj) + { + ValidationResult result = IsValid(obj); + + if (!result.IsValid) + return result; + + if (Successor != null) + return Successor.Validate(obj); + else + return result; + + } + } +} diff --git a/src/ChainOfResponssibility/Validators/UserEntities/Exceptions/CommandCouldNotBeParsedException.cs b/src/ChainOfResponssibility/Validators/UserEntities/Exceptions/CommandCouldNotBeParsedException.cs new file mode 100644 index 0000000..d74b7b1 --- /dev/null +++ b/src/ChainOfResponssibility/Validators/UserEntities/Exceptions/CommandCouldNotBeParsedException.cs @@ -0,0 +1,19 @@ +using System; + +namespace ChainOfResponssibility.Validators.UserEntities +{ + internal class CommandCouldNotBeParsedException : Exception + { + public CommandCouldNotBeParsedException() + { + } + + public CommandCouldNotBeParsedException(string message) : base(message) + { + } + + public CommandCouldNotBeParsedException(string message, Exception innerException) : base(message, innerException) + { + } + } +} \ No newline at end of file diff --git a/src/ChainOfResponssibility/Validators/UserEntities/Exceptions/DuplicateRecordException.cs b/src/ChainOfResponssibility/Validators/UserEntities/Exceptions/DuplicateRecordException.cs new file mode 100644 index 0000000..9a80b9f --- /dev/null +++ b/src/ChainOfResponssibility/Validators/UserEntities/Exceptions/DuplicateRecordException.cs @@ -0,0 +1,19 @@ +using System; + +namespace ChainOfResponssibility.Validators.UserEntities +{ + internal class DuplicateRecordException : Exception + { + public DuplicateRecordException() + { + } + + public DuplicateRecordException(string message) : base(message) + { + } + + public DuplicateRecordException(string message, Exception innerException) : base(message, innerException) + { + } + } +} \ No newline at end of file diff --git a/src/ChainOfResponssibility/Validators/UserEntities/Exceptions/ForbiddenException.cs b/src/ChainOfResponssibility/Validators/UserEntities/Exceptions/ForbiddenException.cs new file mode 100644 index 0000000..959e137 --- /dev/null +++ b/src/ChainOfResponssibility/Validators/UserEntities/Exceptions/ForbiddenException.cs @@ -0,0 +1,19 @@ +using System; + +namespace ChainOfResponssibility.Validators.UserEntities.Validators +{ + internal class ForbiddenException : Exception + { + public ForbiddenException() + { + } + + public ForbiddenException(string message) : base(message) + { + } + + public ForbiddenException(string message, Exception innerException) : base(message, innerException) + { + } + } +} \ No newline at end of file diff --git a/src/ChainOfResponssibility/Validators/UserEntities/Exceptions/NotFoundException.cs b/src/ChainOfResponssibility/Validators/UserEntities/Exceptions/NotFoundException.cs new file mode 100644 index 0000000..8132071 --- /dev/null +++ b/src/ChainOfResponssibility/Validators/UserEntities/Exceptions/NotFoundException.cs @@ -0,0 +1,26 @@ +using System; + +namespace ChainOfResponssibility.Validators.UserEntities +{ + internal class NotFoundException : Exception + { + private int iD; + + public NotFoundException() + { + } + + public NotFoundException(string message) : base(message) + { + } + + public NotFoundException(int iD) + { + this.iD = iD; + } + + public NotFoundException(string message, Exception innerException) : base(message, innerException) + { + } + } +} \ No newline at end of file diff --git a/src/ChainOfResponssibility/Validators/UserEntities/Exceptions/ValidationResult.cs b/src/ChainOfResponssibility/Validators/UserEntities/Exceptions/ValidationResult.cs new file mode 100644 index 0000000..ab6a0b8 --- /dev/null +++ b/src/ChainOfResponssibility/Validators/UserEntities/Exceptions/ValidationResult.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ChainOfResponssibility.Validators +{ + public class ValidationResult + { + protected ValidationResult(bool isValid, Exception e) + { + IsValid = isValid; + Exception = e; + } + + public static ValidationResult GetValidResult() + { + return new ValidationResult(true, null); + } + + public static ValidationResult GetInvalidResult(Exception e) + { + return new ValidationResult(false, e); + } + + public Exception Exception { get; set; } + + public bool IsValid { get; set; } + } + + public class ValidationResult : ValidationResult + { + public ValidationResult(bool isValid, Exception e, T model) : base(isValid, e) + { + } + + public static ValidationResult GetValidResult(T model) + { + return new ValidationResult(true, null, model); + } + + public static ValidationResult GetInvalidResult(Exception e) + { + return new ValidationResult(false, e, default(T)); + } + + public T Result { get; set; } + } +} diff --git a/src/ChainOfResponssibility/Validators/UserEntities/Infrastructure/PrincipalHelper.cs b/src/ChainOfResponssibility/Validators/UserEntities/Infrastructure/PrincipalHelper.cs new file mode 100644 index 0000000..c50e1b8 --- /dev/null +++ b/src/ChainOfResponssibility/Validators/UserEntities/Infrastructure/PrincipalHelper.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ChainOfResponssibility.Validators.UserEntities.Infrastructure +{ + public class PrincipalHelper + { + User authenticatedUser; + + public User GetAuthenticatedUser() + { + return authenticatedUser; + } + + public void SetAuthenticatedUser(User user) + { + authenticatedUser = user; + } + } +} diff --git a/src/ChainOfResponssibility/Validators/UserEntities/Infrastructure/UserRepository.cs b/src/ChainOfResponssibility/Validators/UserEntities/Infrastructure/UserRepository.cs new file mode 100644 index 0000000..65e9ed5 --- /dev/null +++ b/src/ChainOfResponssibility/Validators/UserEntities/Infrastructure/UserRepository.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace ChainOfResponssibility.Validators.UserEntities +{ + public class UserRepository + { + List users; + public UserRepository() + { + users = new List(); + users.Add(new User { ID = 1, Email = "a@a.a", TenantId = 1, UserName = "a", Rights = Rights.Create | Rights.Update }); + users.Add(new User { ID = 2, Email = "b@a.a", TenantId = 1, UserName = "b", Rights = Rights.Read}); + users.Add(new User { ID = 2, Email = "c@a.a", TenantId = 2, UserName = "c", Rights = Rights.Create | Rights.Update | Rights.Read | Rights.Delete }); + } + + public User Get(string email) + { + return users.First(u => u.Email == email); + } + + public bool Exists(string email) + { + return users.Any(u => u.Email.Equals(email, StringComparison.OrdinalIgnoreCase)); + } + + public bool Exists(int id) + { + return users.Any(u => u.ID == id); + } + + public void Add(User user) + { + lock (users) + { + int maxId = users.Max(u => u.ID); + user.ID = maxId + 1; + users.Add(user); + } + + } + + public void Update(User user) + { + User dbUser = users.First(u => u.ID == user.ID); + + dbUser.Email = user.Email; + dbUser.TenantId = user.TenantId; + dbUser.UserName = user.UserName; + } + + } +} diff --git a/src/ChainOfResponssibility/Validators/UserEntities/Model/User.cs b/src/ChainOfResponssibility/Validators/UserEntities/Model/User.cs new file mode 100644 index 0000000..dab928e --- /dev/null +++ b/src/ChainOfResponssibility/Validators/UserEntities/Model/User.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ChainOfResponssibility.Validators.UserEntities +{ + public class User + { + public int ID { get; set; } + + public string UserName { get; set; } + + public string Email { get; set; } + + public int TenantId { get; set; } + + public Rights Rights { get; set; } + } + [Flags] + public enum Rights + { + Create = 1 << 3, + Read = 1 << 2, + Update = 1 << 1, + Delete = 1 << 0 + } +} diff --git a/src/ChainOfResponssibility/Validators/UserEntities/UserMenu/AuthenticateOperation.cs b/src/ChainOfResponssibility/Validators/UserEntities/UserMenu/AuthenticateOperation.cs new file mode 100644 index 0000000..ef8b6e9 --- /dev/null +++ b/src/ChainOfResponssibility/Validators/UserEntities/UserMenu/AuthenticateOperation.cs @@ -0,0 +1,30 @@ +using System; + +namespace ChainOfResponssibility.Validators.UserEntities.UserMenu +{ + public class AuthenticateOperation : Operation + { + const string prefix = "authenticate as"; + Action authenticateFunction; + public AuthenticateOperation(Action authenticateFunction) + { + this.authenticateFunction = authenticateFunction; + } + + protected override bool CanExecute(string command) + { + return command.ToLower().StartsWith(prefix); + } + + protected override void ExecuteSpecificOperation(string command) + { + string email = command.Substring(prefix.Length + 1); + authenticateFunction(email); + } + + protected override string GetMessageToPrint() + { + return string.Format("To authenticate press: {0} ", prefix); + } + } +} diff --git a/src/ChainOfResponssibility/Validators/UserEntities/UserMenu/CreateNewUserOperation.cs b/src/ChainOfResponssibility/Validators/UserEntities/UserMenu/CreateNewUserOperation.cs new file mode 100644 index 0000000..9a4a631 --- /dev/null +++ b/src/ChainOfResponssibility/Validators/UserEntities/UserMenu/CreateNewUserOperation.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ChainOfResponssibility.Validators.UserEntities.UserMenu +{ + public class CreateNewUserOperation : Operation + { + string prefix = "create user"; + Action createUser; + public CreateNewUserOperation(Action createUser) + { + this.createUser = createUser; + } + + protected override bool CanExecute(string command) + { + var hasCorrectPrefix = command.ToLower().StartsWith(prefix) && command.Contains(","); + return hasCorrectPrefix && IsInt(GetTenantId(command)); + } + + protected override void ExecuteSpecificOperation(string command) + { + string commandWithoutPrefix = command.Substring(prefix.Length); + + string email = new string(commandWithoutPrefix.TakeWhile(c => c != ',').Skip(1).ToArray()).Trim(); + + string userName = GetUserName(commandWithoutPrefix); + + int tenantId = int.Parse(GetTenantId(commandWithoutPrefix)); + createUser(email, userName, tenantId); + } + + public static string GetUserName(string command) + { + string commandWithoutEmail = command.Substring(command.IndexOf(',') + 1); + string userName = commandWithoutEmail.Substring(0, commandWithoutEmail.LastIndexOf(',')); + return userName.Trim(); + } + + private static string GetTenantId(string command) + { + return command.Substring(command.LastIndexOf(',') + 1).Trim(); + } + + protected override string GetMessageToPrint() + { + return string.Format("To create a new user press: create user , , "); + } + + private bool IsInt(string tenantId) + { + int number; + return int.TryParse(tenantId, out number); + } + } +} diff --git a/src/ChainOfResponssibility/Validators/UserEntities/UserMenu/Operation.cs b/src/ChainOfResponssibility/Validators/UserEntities/UserMenu/Operation.cs new file mode 100644 index 0000000..44006d1 --- /dev/null +++ b/src/ChainOfResponssibility/Validators/UserEntities/UserMenu/Operation.cs @@ -0,0 +1,49 @@ +using System; + +namespace ChainOfResponssibility.Validators.UserEntities.UserMenu +{ + public abstract class Operation + { + protected abstract bool CanExecute(string command); + + protected abstract void ExecuteSpecificOperation(string command); + + protected abstract string GetMessageToPrint(); + + public Operation Successor { get; private set; } + + public Operation SetSuccessor(Operation successor) + { + return Successor = successor; + } + + public ValidationResult Execute(string command) + { + if (CanExecute(command)) + { + ExecuteSpecificOperation(command); + return ValidationResult.GetValidResult(); + } + else + { + if (Successor != null) + return Successor.Execute(command); + else + return GetInvalidResult(command); + } + } + + public void PrintMenu() + { + Console.WriteLine(GetMessageToPrint()); + + if (Successor != null) + Successor.PrintMenu(); + } + + private static ValidationResult GetInvalidResult(string command) + { + return ValidationResult.GetInvalidResult(new CommandCouldNotBeParsedException(command)); + } + } +} diff --git a/src/ChainOfResponssibility/Validators/UserEntities/UserMenu/UpdateUserOperation.cs b/src/ChainOfResponssibility/Validators/UserEntities/UserMenu/UpdateUserOperation.cs new file mode 100644 index 0000000..8ca3927 --- /dev/null +++ b/src/ChainOfResponssibility/Validators/UserEntities/UserMenu/UpdateUserOperation.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ChainOfResponssibility.Validators.UserEntities.UserMenu +{ + public class UpdateUserOperation + { + } +} diff --git a/src/ChainOfResponssibility/Validators/UserEntities/UserProcessor.cs b/src/ChainOfResponssibility/Validators/UserEntities/UserProcessor.cs new file mode 100644 index 0000000..75fbd43 --- /dev/null +++ b/src/ChainOfResponssibility/Validators/UserEntities/UserProcessor.cs @@ -0,0 +1,76 @@ +using ChainOfResponssibility.Validators.UserEntities.Infrastructure; +using ChainOfResponssibility.Validators.UserEntities.UserMenu; +using ChainOfResponssibility.Validators.UserEntities.Validators; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ChainOfResponssibility.Validators.UserEntities +{ + public class UserProcessor + { + UserRepository userRepository; + PrincipalHelper principalHelper; + Operation operation; + ChainValidation userCreationValidation; + ChainValidation authenticateUserValidation; + public UserProcessor() + { + userRepository = new UserRepository(); + principalHelper = new PrincipalHelper(); + operation = new AuthenticateOperation(AuthenticateUser); + operation.SetSuccessor(new CreateNewUserOperation(CreateNewUser)); + + userCreationValidation = new IsAuthorisedToDoOperationsOnUser(principalHelper, Rights.Create); + userCreationValidation.SetSuccessor(new ValidateNoDuplicateEmail(userRepository)); + + authenticateUserValidation = new ValidateUserExistsInDb(userRepository); + + } + + private void CreateNewUser(string email, string userName, int tenantId) + { + User user = new User { Email = email, UserName = userName, TenantId = tenantId }; + var result = userCreationValidation.Validate(user); + + if (result.IsValid) + userRepository.Add(user); + else + Console.WriteLine(result.Exception); + } + + private void AuthenticateUser(string email) + { + var result = authenticateUserValidation.Validate(new User { Email = email }); + + if (result.IsValid) + { + principalHelper.SetAuthenticatedUser(userRepository.Get(email)); + Console.WriteLine("Authentication successful"); + } + else + Console.WriteLine(result.Exception.Message); + } + + public void DoStuff() + { + string userInput; + do + { + operation.PrintMenu(); + Console.Write(">"); + userInput = Console.ReadLine(); + + if (!IsExitCode(userInput)) + operation.Execute(userInput); + + } while (!IsExitCode(userInput)); + } + + private static bool IsExitCode(string input) + { + return "exit".Equals(input, StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/src/ChainOfResponssibility/Validators/UserEntities/Validators/IsAuthorisedToDoOperationsOnUser.cs b/src/ChainOfResponssibility/Validators/UserEntities/Validators/IsAuthorisedToDoOperationsOnUser.cs new file mode 100644 index 0000000..b14d4c7 --- /dev/null +++ b/src/ChainOfResponssibility/Validators/UserEntities/Validators/IsAuthorisedToDoOperationsOnUser.cs @@ -0,0 +1,35 @@ +using ChainOfResponssibility.Validators.UserEntities.Infrastructure; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ChainOfResponssibility.Validators.UserEntities.Validators +{ + public class IsAuthorisedToDoOperationsOnUser : ChainValidation + { + PrincipalHelper principalHelper; + Rights rights; + public IsAuthorisedToDoOperationsOnUser(PrincipalHelper principalHelper, Rights rights) + { + this.principalHelper = principalHelper; + this.rights = rights; + } + + protected override ValidationResult IsValid(User obj) + { + User authenticatedUser = principalHelper.GetAuthenticatedUser(); + + if (authenticatedUser == null) + return ValidationResult.GetInvalidResult(new ForbiddenException("Only authenticated users may create new users")); + + if (!authenticatedUser.Rights.HasFlag(rights)) + return ValidationResult.GetInvalidResult(new ForbiddenException(string.Format("Unauthorised to do {0} on user", rights))); + + if (authenticatedUser.TenantId != obj.TenantId) + return ValidationResult.GetInvalidResult(new ForbiddenException("Cannot create user for another tenant")); + + return ValidationResult.GetValidResult(); + } + } +} diff --git a/src/ChainOfResponssibility/Validators/UserEntities/Validators/ValidateNoDuplicateEmail.cs b/src/ChainOfResponssibility/Validators/UserEntities/Validators/ValidateNoDuplicateEmail.cs new file mode 100644 index 0000000..24927d0 --- /dev/null +++ b/src/ChainOfResponssibility/Validators/UserEntities/Validators/ValidateNoDuplicateEmail.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ChainOfResponssibility.Validators.UserEntities +{ + public class ValidateNoDuplicateEmail : ChainValidation + { + UserRepository userRepository; + public ValidateNoDuplicateEmail(UserRepository userRepository) + { + this.userRepository = userRepository; + + } + protected override ValidationResult IsValid(User obj) + { + if (userRepository.Exists(obj.Email)) + return ValidationResult.GetInvalidResult(new DuplicateRecordException(obj.Email)); + else + return ValidationResult.GetValidResult(); + } + } +} diff --git a/src/ChainOfResponssibility/Validators/UserEntities/Validators/ValidateUserExistsInDb.cs b/src/ChainOfResponssibility/Validators/UserEntities/Validators/ValidateUserExistsInDb.cs new file mode 100644 index 0000000..743ae66 --- /dev/null +++ b/src/ChainOfResponssibility/Validators/UserEntities/Validators/ValidateUserExistsInDb.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ChainOfResponssibility.Validators.UserEntities +{ + public class ValidateUserExistsInDb : ChainValidation + { + UserRepository userRepository; + public ValidateUserExistsInDb(UserRepository userRepository) + { + this.userRepository = userRepository; + } + + protected override ValidationResult IsValid(User obj) + { + bool userExists = userRepository.Exists(obj.Email); + + if (userExists) + return ValidationResult.GetValidResult(); + else + return ValidationResult.GetInvalidResult(new NotFoundException(obj.ID)); + + } + } +} diff --git a/src/ChainOfResponssibility/project.json b/src/ChainOfResponssibility/project.json new file mode 100644 index 0000000..ed8608d --- /dev/null +++ b/src/ChainOfResponssibility/project.json @@ -0,0 +1,13 @@ +{ + "version": "1.0.0-*", + + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-24027" + }, + + "frameworks": { + "netstandard1.5": { + "imports": "dnxcore50" + } + } +} diff --git a/src/CommandPattern/Class1.cs b/src/CommandPattern/Class1.cs new file mode 100644 index 0000000..1ece8d3 --- /dev/null +++ b/src/CommandPattern/Class1.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace CommandPattern +{ + // This project can output the Class library as a NuGet Package. + // To enable this option, right-click on the project and select the Properties menu item. In the Build tab select "Produce outputs on build". + public class Class1 + { + public Class1() + { + } + } +} diff --git a/src/CommandPattern/CommandPattern.xproj b/src/CommandPattern/CommandPattern.xproj new file mode 100644 index 0000000..cf279a6 --- /dev/null +++ b/src/CommandPattern/CommandPattern.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + ccc23c3a-7a67-40bd-80fb-c4d2c0342e50 + CommandPattern + .\obj + .\bin\ + v4.6.1 + + + + 2.0 + + + diff --git a/src/CommandPattern/Properties/AssemblyInfo.cs b/src/CommandPattern/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9dd60d9 --- /dev/null +++ b/src/CommandPattern/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Hewlett-Packard Company")] +[assembly: AssemblyProduct("CommandPattern")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ccc23c3a-7a67-40bd-80fb-c4d2c0342e50")] diff --git a/src/CommandPattern/project.json b/src/CommandPattern/project.json new file mode 100644 index 0000000..ed8608d --- /dev/null +++ b/src/CommandPattern/project.json @@ -0,0 +1,13 @@ +{ + "version": "1.0.0-*", + + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-24027" + }, + + "frameworks": { + "netstandard1.5": { + "imports": "dnxcore50" + } + } +}