Skip to content
Browse files

Episode 17 Sample Code Projects

  • Loading branch information...
1 parent 537a8ad commit f279c4820f69e91d8d418447a6130541e4117873 Kerry Street committed
Showing with 15,485 additions and 6 deletions.
  1. +1 −1 E014-car-factory-tests/sample-csharp/E014.Domain/Contracts/Messages.ddd
  2. +1 −1 E014-car-factory-tests/sample-csharp/E014.Domain/Contracts/Messages.ddd.E14.with.extra.notes
  3. +2 −2 E015-bits-that-keep-on-giving/sample-csharp/E015.Domain.Test.Console/Properties/AssemblyInfo.cs
  4. +1 −1 E015-bits-that-keep-on-giving/sample-csharp/E015.Domain/Contracts/Messages.ddd
  5. +1 −1 E015-bits-that-keep-on-giving/sample-csharp/E015.Domain/Contracts/Messages.ddd.E15.comments
  6. +266 −0 E017-view-projections/sample-csharp/E017.Domain.Test.Console/ConsoleActions.cs
  7. +139 −0 E017-view-projections/sample-csharp/E017.Domain.Test.Console/ConsoleEnvironment.cs
  8. +64 −0 E017-view-projections/sample-csharp/E017.Domain.Test.Console/E017.Domain.Test.Console.csproj
  9. +421 −0 E017-view-projections/sample-csharp/E017.Domain.Test.Console/ILogger.cs
  10. +56 −0 E017-view-projections/sample-csharp/E017.Domain.Test.Console/Program.cs
  11. +36 −0 E017-view-projections/sample-csharp/E017.Domain.Test.Console/Properties/AssemblyInfo.cs
  12. +41 −0 ...jections/sample-csharp/E017.Domain.Test/ApplicationServices/Factory/assign_employee_to_factory.cs
  13. +109 −0 E017-view-projections/sample-csharp/E017.Domain.Test/ApplicationServices/Factory/factory_spec.cs
  14. +25 −0 E017-view-projections/sample-csharp/E017.Domain.Test/ApplicationServices/Factory/open_factory.cs
  15. +85 −0 E017-view-projections/sample-csharp/E017.Domain.Test/ApplicationServices/Factory/produce_a_car.cs
  16. +57 −0 ...tions/sample-csharp/E017.Domain.Test/ApplicationServices/Factory/receive_shipment_in_cargo_bay.cs
  17. +65 −0 ...ctions/sample-csharp/E017.Domain.Test/ApplicationServices/Factory/unpack_shipment_in_cargo_bay.cs
  18. +313 −0 E017-view-projections/sample-csharp/E017.Domain.Test/ApplicationServices/application_service_spec.cs
  19. +958 −0 E017-view-projections/sample-csharp/E017.Domain.Test/CompareObjects.cs
  20. +226 −0 E017-view-projections/sample-csharp/E017.Domain.Test/DocumentFixture.cs
  21. +31 −0 E017-view-projections/sample-csharp/E017.Domain.Test/DomainServices/TestBlueprintLibrary.cs
  22. +79 −0 E017-view-projections/sample-csharp/E017.Domain.Test/E017.Domain.Test.csproj
  23. BIN E017-view-projections/sample-csharp/E017.Domain.Test/Factory_diagram_by_Graphviz.png
  24. +72 −0 E017-view-projections/sample-csharp/E017.Domain.Test/Runner.cs
  25. +173 −0 E017-view-projections/sample-csharp/E017.Domain.Test/TestMessageSerialization.cs
  26. +128 −0 E017-view-projections/sample-csharp/E017.Domain/AbstractIdentity.cs
  27. +186 −0 E017-view-projections/sample-csharp/E017.Domain/ApplicationServices/Factory/FactoryAggregate.cs
  28. +101 −0 ...ew-projections/sample-csharp/E017.Domain/ApplicationServices/Factory/FactoryApplicationService.cs
  29. +161 −0 E017-view-projections/sample-csharp/E017.Domain/ApplicationServices/Factory/FactoryState.cs
  30. +62 −0 E017-view-projections/sample-csharp/E017.Domain/Contracts/Concepts.cs
  31. +26 −0 E017-view-projections/sample-csharp/E017.Domain/Contracts/ICarBlueprintLibrary.cs
  32. +63 −0 E017-view-projections/sample-csharp/E017.Domain/Contracts/Messages.ToString.cs
  33. +277 −0 E017-view-projections/sample-csharp/E017.Domain/Contracts/Messages.cs
  34. +175 −0 E017-view-projections/sample-csharp/E017.Domain/Contracts/Messages.ddd
  35. +49 −0 E017-view-projections/sample-csharp/E017.Domain/Contracts/Messages.ddd.E17.clean.no.comments
  36. +176 −0 E017-view-projections/sample-csharp/E017.Domain/Contracts/Messages.ddd.E17.comments
  37. +78 −0 E017-view-projections/sample-csharp/E017.Domain/E017.Domain.csproj
  38. +89 −0 E017-view-projections/sample-csharp/E017.Domain/FactoryLanguageAndRules.md
  39. +76 −0 E017-view-projections/sample-csharp/E017.Domain/Interfaces.cs
  40. +38 −0 E017-view-projections/sample-csharp/E017.Domain/Projections/ActiveFactoriesProjection.cs
  41. +37 −0 E017-view-projections/sample-csharp/E017.Domain/Projections/InventoryProjection.cs
  42. +19 −0 E017-view-projections/sample-csharp/E017.Domain/Projections/WorkerRegistryProjection.cs
  43. BIN E017-view-projections/sample-csharp/Library/NUnit/nunit.framework.dll
  44. +10,407 −0 E017-view-projections/sample-csharp/Library/NUnit/nunit.framework.xml
  45. BIN E017-view-projections/sample-csharp/Library/dsl/Antlr3.Runtime.dll
  46. BIN E017-view-projections/sample-csharp/Library/dsl/Dsl.exe
  47. +77 −0 E017-view-projections/sample-csharp/README.md
  48. +2 −0 E017-view-projections/sample-csharp/dsl.cmd
  49. +36 −0 _misc/BTWCSharp.sln
View
2 E014-car-factory-tests/sample-csharp/E014.Domain/Contracts/Messages.ddd
@@ -17,7 +17,7 @@
// Note that in the podcast audio of Episode 12, the "namespace" and "extern" keywords below were mentioned as
-// "Episode zero one one", but this was later changed to E014 because that audio became Episoide 12, not Episode 11.
+// "Episode zero one one", but this was later changed to E012 because that audio became Episoide 12, not Episode 11.
// 'namespace' DSL keyword literally defines the exact C# namespace that the generated code will be placed in.
View
2 E014-car-factory-tests/sample-csharp/E014.Domain/Contracts/Messages.ddd.E14.with.extra.notes
@@ -17,7 +17,7 @@
// Note that in the podcast audio of Episode 12, the "namespace" and "extern" keywords below were mentioned as
-// "Episode zero one one", but this was later changed to E014 because that audio became Episoide 12, not Episode 11.
+// "Episode zero one one", but this was later changed to E012 because that audio became Episoide 12, not Episode 11.
// 'namespace' DSL keyword literally defines the exact C# namespace that the generated code will be placed in.
View
4 ...its-that-keep-on-giving/sample-csharp/E015.Domain.Test.Console/Properties/AssemblyInfo.cs
@@ -5,11 +5,11 @@
// 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: AssemblyTitle("E015.Btw.Portable")]
+[assembly: AssemblyTitle("E015.Domain.Test.Console")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("E015.Btw.Portable")]
+[assembly: AssemblyProduct("E015.Domain.Test.Console")]
[assembly: AssemblyCopyright("Copyright © 2012")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
View
2 E015-bits-that-keep-on-giving/sample-csharp/E015.Domain/Contracts/Messages.ddd
@@ -17,7 +17,7 @@
// Note that in the podcast audio of Episode 12, the "namespace" and "extern" keywords below were mentioned as
-// "Episode zero one one", but this was later changed to E015 because that audio became Episoide 12, not Episode 11.
+// "Episode zero one one", but this was later changed to E012 because that audio became Episoide 12, not Episode 11.
// 'namespace' DSL keyword literally defines the exact C# namespace that the generated code will be placed in.
View
2 E015-bits-that-keep-on-giving/sample-csharp/E015.Domain/Contracts/Messages.ddd.E15.comments
@@ -17,7 +17,7 @@
// Note that in the podcast audio of Episode 12, the "namespace" and "extern" keywords below were mentioned as
-// "Episode zero one one", but this was later changed to E015 because that audio became Episoide 12, not Episode 11.
+// "Episode zero one one", but this was later changed to E012 because that audio became Episoide 12, not Episode 11.
// 'namespace' DSL keyword literally defines the exact C# namespace that the generated code will be placed in.
View
266 E017-view-projections/sample-csharp/E017.Domain.Test.Console/ConsoleActions.cs
@@ -0,0 +1,266 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using E017;
+using E017.Contracts;
+
+namespace E017.Domain.Test.Console
+{
+ public static class ConsoleActions
+ {
+ public static IDictionary<string,IShellAction> Actions = new Dictionary<string,IShellAction>();
+
+ static ConsoleActions()
+ {
+ Register(new OpenFactoryAction());
+ Register(new RegisterBlueprintAction());
+ Register(new HireEmployeeAction());
+ Register(new RecieveShipmentAction());
+ Register(new UnpackShipmentsAction());
+ Register(new HelpAction());
+ Register(new ExitAction());
+ Register(new StoryAction());
+ Register(new ListFactoriesAction());
+ Register(new ListWorkersAction());
+ Register(new InventoryAction());
+ }
+ static void Register(IShellAction cmd)
+ {
+ Actions.Add(cmd.Usage.Split(new[]{' '},2).First(), cmd);
+ }
+ }
+
+ public interface IShellAction
+ {
+ string Usage { get; }
+ void Execute(ConsoleEnvironment env, string[] args);
+ }
+
+ public class OpenFactoryAction : IShellAction
+ {
+ public string Keyword { get { return "open"; } }
+ public string Usage { get { return "open <factoryId> - opens new factory"; } }
+
+ public void Execute(ConsoleEnvironment env, string[] args)
+ {
+ if (args.Length != 1)
+ throw new ArgumentException("Expected at least 2 args");
+ var id = int.Parse(args[0]);
+ env.FactoryAppService.When(new OpenFactory(new FactoryId(id)));
+ }
+ }
+ public class RegisterBlueprintAction : IShellAction
+ {
+ public string Keyword { get { return "reg"; } }
+ public string Usage { get { return "reg <design> [<part>, <part>...]"; } }
+ public void Execute(ConsoleEnvironment env, string[] args)
+ {
+ if (args.Length < 2)
+ {
+ throw new ArgumentException("Expected at least 2 args");
+ }
+
+ var design = args[0];
+ var parts = args.Skip(1).GroupBy(s => s).Select(g => new CarPart(g.Key, g.Count())).ToArray();
+ env.Blueprints.Register(design, parts);
+ }
+ }
+
+ public class HireEmployeeAction : IShellAction
+ {
+ public string Keyword { get { return "hire"; } }
+ public string Usage { get { return "hire <factoryid> <employeeName>"; } }
+ public void Execute(ConsoleEnvironment env, string[] args)
+ {
+ if (args.Length < 2)
+ {
+ throw new ArgumentException("Expected at least 2 args");
+ }
+ var id = int.Parse(args[0]);
+ var name = string.Join(" ", args.Skip(1));
+ env.FactoryAppService.When(new AssignEmployeeToFactory(new FactoryId(id), name));
+ }
+ }
+
+ public class RecieveShipmentAction : IShellAction
+ {
+ public string Keyword { get { return "ship"; } }
+ public string Usage { get { return "ship <factoryId> <shipment> [<part>,<part>...]"; } }
+ public void Execute(ConsoleEnvironment env, string[] args)
+ {
+ if (args.Length < 2)
+ throw new ArgumentException("Expected at least 2 args");
+
+ var id = int.Parse(args[0]);
+ var name = args[1];
+ var parts = args.Skip(2).GroupBy(s => s).Select(g => new CarPart(g.Key, g.Count())).ToArray();
+ env.FactoryAppService.When(new ReceiveShipmentInCargoBay(new FactoryId(id), name, parts));
+ }
+ }
+
+ public class UnpackShipmentsAction : IShellAction
+ {
+ public string Keyword { get { return "unpack"; } }
+ public string Usage { get { return "unpack <factoryId> <shipment>"; } }
+ public void Execute(ConsoleEnvironment env, string[] args)
+ {
+ if (args.Length < 2)
+ {
+ throw new ArgumentException("Expected at least 2 args");
+ }
+ var id = int.Parse(args[0]);
+ var employee = string.Join(" ", args.Skip(1));
+ env.FactoryAppService.When(new UnpackAndInventoryShipmentInCargoBay(new FactoryId(id), employee));
+
+ }
+ }
+
+ public class HelpAction : IShellAction
+ {
+ public string Keyword { get { return "help"; } }
+ public string Usage { get { return "help [<command>]"; } }
+ public void Execute(ConsoleEnvironment env, string[] args)
+ {
+ if (args.Length > 0)
+ {
+ IShellAction value;
+ if (!env.Handlers.TryGetValue(args[0], out value))
+ {
+ env.Log.Error("Can't find help for '{0}'", args[0]);
+ return;
+ }
+ env.Log.Info(value.Usage ?? "No Help available");
+ return;
+ }
+ env.Log.Info("Available commands");
+ foreach (var handler in env.Handlers.OrderBy(h => h.Key))
+ {
+ env.Log.Info(" {0}", handler.Key.ToUpperInvariant());
+ if (!string.IsNullOrWhiteSpace(handler.Value.Usage))
+ {
+ env.Log.Info(" {0}", handler.Value.Usage);
+ }
+ }
+
+ }
+ }
+
+ public class InventoryAction : IShellAction
+ {
+ public string Keyword { get { return "inventory"; } }
+ public string Usage { get { return Keyword; } }
+ public void Execute(ConsoleEnvironment env, string[] args)
+ {
+ var inv = env.Inventory.Parts;
+ if (!inv.Any())
+ {
+ env.Log.Info("No car parts are in inventory");
+ return;
+ }
+
+ foreach (var source in inv.OrderBy(i => i.Key).Where(i => i.Value > 0))
+ {
+ env.Log.Info("{0,20} - {1} pcs available", source.Key, source.Value);
+ }
+ }
+ }
+
+ public class ExitAction : IShellAction
+ {
+ public string Keyword { get { return "exit"; } }
+ public string Usage { get { return "exit"; } }
+ public void Execute(ConsoleEnvironment env, string[] args)
+ {
+ Environment.Exit(0);
+ }
+ }
+
+ public class ListWorkersAction : IShellAction
+ {
+ public string Keyword { get { return "workers"; } }
+ public string Usage { get { return Keyword; } }
+ public void Execute(ConsoleEnvironment env, string[] args)
+ {
+ var registry = env.WorkerRegistry.List;
+
+
+ if (!registry.Any())
+ {
+ env.Log.Info("No workers found");
+ return;
+ }
+
+ foreach (var groups in registry.GroupBy(r => r.Item2.Id))
+ {
+ env.Log.Info("Factory " + groups.Key);
+
+ foreach (var tuple in groups)
+ {
+ env.Log.Info(" " + tuple.Item1);
+ }
+ }
+ }
+ }
+
+ public class ListFactoriesAction : IShellAction
+ {
+ public string Keyword { get { return "factories"; } }
+ public string Usage { get { return Keyword; } }
+ public void Execute(ConsoleEnvironment env, string[] args)
+ {
+ var factories = env.ActiveFactories.Factories.ToList();
+ if (!factories.Any())
+ {
+ env.Log.Info("No factories opened yet");
+ return;
+ }
+
+ foreach (var pair in factories)
+ {
+ var info = pair.Value;
+ env.Log.Info("Factory {0} with {1} workers and {2} parts in cargo", pair.Key.Id, info.WorkerCount, info.PartsInCargoBay);
+ }
+ }
+ }
+
+ public class StoryAction : IShellAction
+ {
+ public string Keyword { get { return "story"; } }
+ public string Usage { get { return "story [<factoryId=1>]"; } }
+ public void Execute(ConsoleEnvironment env, string[] args)
+ {
+ int factoryId = 1;
+ if (args.Length>0)
+ {
+ int.TryParse(args[0], out factoryId);
+ }
+ var id = new FactoryId(factoryId);
+ var story = new List<ICommand>
+ {
+ new OpenFactory(id),
+ new AssignEmployeeToFactory(id, "Luke"),
+ new AssignEmployeeToFactory(id, "Han"),
+ new ReceiveShipmentInCargoBay(id, "from uncle Ben", new[]
+ {
+ new CarPart("wheel", 10),
+ new CarPart("light saber", 2),
+ new CarPart("C3P0", 1),
+ }),
+ new ReceiveShipmentInCargoBay(id, "from Yoda", new[]
+ {
+ new CarPart("engine", 2),
+ new CarPart("chassis", 1),
+ }),
+ new UnpackAndInventoryShipmentInCargoBay(id, "Han"),
+ new ProduceACar(id, "Luke", "model-t")
+ };
+
+ foreach (var command in story)
+ {
+ env.FactoryAppService.Execute(command);
+ }
+ }
+
+
+ }
+}
View
139 E017-view-projections/sample-csharp/E017.Domain.Test.Console/ConsoleEnvironment.cs
@@ -0,0 +1,139 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using E017;
+using E017.ApplicationServices.Factory;
+using E017.Contracts;
+using E017.Projections;
+using Microsoft.CSharp.RuntimeBinder;
+using Platform;
+
+namespace E017.Domain.Test.Console
+{
+ // This class is acting as sort of a mini-Inversion of Control (IoC) container for us.
+ // One class that contains instances of oour EventStore, FactoryApplication Service, car blueprint library, and a logger.
+ public class ConsoleEnvironment
+ {
+ public IEventStore Events;
+ public FactoryApplicationService FactoryAppService;
+ public IDictionary<string, IShellAction> Handlers;
+ public InMemoryBlueprintLibrary Blueprints;
+ public ILogger Log = LogManager.GetLoggerFor<ConsoleEnvironment>();
+ public ActiveFactoriesProjection ActiveFactories;
+ public WorkerRegistryProjection WorkerRegistry;
+ public InventoryProjection Inventory;
+
+ public static ConsoleEnvironment BuildEnvironment()
+ {
+ var handler = new SynchronousEventHandler();
+ var store = new InMemoryStore(handler);
+
+ var blueprints = new InMemoryBlueprintLibrary();
+ blueprints.Register("model-t", new CarPart("wheel",4), new CarPart("engine",1), new CarPart("chassis",1));
+ var fas = new FactoryApplicationService(store, blueprints);
+
+
+
+ // wire projections
+ var activeFactories = new ActiveFactoriesProjection();
+ handler.RegisterHandler(activeFactories);
+
+ var workerRegistry = new WorkerRegistryProjection();
+ handler.RegisterHandler(workerRegistry);
+
+ var inventory = new InventoryProjection();
+ handler.RegisterHandler(inventory);
+
+ return new ConsoleEnvironment
+ {
+ Events = store,
+ FactoryAppService = fas,
+ Handlers = ConsoleActions.Actions,
+ Blueprints = blueprints,
+ ActiveFactories = activeFactories,
+ WorkerRegistry = workerRegistry,
+ Inventory = inventory
+ };
+ }
+ }
+
+ public sealed class InMemoryStore : IEventStore
+ {
+ readonly ConcurrentDictionary<string, IList<IEvent>> _store = new ConcurrentDictionary<string, IList<IEvent>>();
+ readonly SynchronousEventHandler _handler;
+
+ static ILogger Log = LogManager.GetLoggerFor<InMemoryStore>();
+ public InMemoryStore(SynchronousEventHandler handler)
+ {
+ _handler = handler;
+ }
+
+ public EventStream LoadEventStream(string id)
+ {
+ var stream = _store.GetOrAdd(id, new IEvent[0]).ToList();
+
+ return new EventStream()
+ {
+ Events = stream,
+ StreamVersion = stream.Count
+ };
+ }
+
+ public void AppendEventsToStream(string id, long expectedVersion, ICollection<IEvent> events)
+ {
+ _store.AddOrUpdate(id, events.ToList(), (s, list) => list.Concat(events).ToList());
+
+ foreach (var @event in events)
+ {
+ Log.Info("{0}", @event);
+
+ _handler.Handle(@event);
+ }
+ }
+ }
+
+ public sealed class SynchronousEventHandler
+ {
+ readonly IList<object> _handlers = new List<object>();
+ public void Handle(IEvent @event)
+ {
+ foreach (var handler in _handlers)
+ {
+ try
+ {
+ ((dynamic)handler).When((dynamic)@event);
+ }
+ catch (RuntimeBinderException e)
+ {
+ // binding failure. Ignore
+ }
+ }
+ }
+ public void RegisterHandler(object projection)
+ {
+ _handlers.Add(projection);
+ }
+ }
+
+ public sealed class InMemoryBlueprintLibrary : ICarBlueprintLibrary
+ {
+ readonly IDictionary<string,CarBlueprint> _bluePrints = new Dictionary<string, CarBlueprint>(StringComparer.InvariantCultureIgnoreCase);
+
+ static ILogger Log = LogManager.GetLoggerFor<InMemoryBlueprintLibrary>();
+ public CarBlueprint TryToGetBlueprintForModelOrNull(string modelName)
+ {
+ CarBlueprint value;
+ if (_bluePrints.TryGetValue(modelName,out value))
+ {
+ return value;
+ }
+ return null;
+ }
+ public void Register(string modelName, params CarPart[] parts)
+ {
+ Log.Debug("Adding new design {0}: {1}", modelName, string.Join(", ", parts.Select(p => string.Format("{0} x {1}", p.Quantity, p.Name))));
+ _bluePrints[modelName] = new CarBlueprint(modelName, parts);
+ }
+ }
+}
View
64 E017-view-projections/sample-csharp/E017.Domain.Test.Console/E017.Domain.Test.Console.csproj
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{570C9B79-BB76-459C-B320-84163D7155E2}</ProjectGuid>
+ <OutputType>Exe</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>E017.Domain.Test.Console</RootNamespace>
+ <AssemblyName>E017.Domain.Test.Console</AssemblyName>
+ <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="ConsoleActions.cs" />
+ <Compile Include="ConsoleEnvironment.cs" />
+ <Compile Include="ILogger.cs" />
+ <Compile Include="Program.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\E017.Domain\E017.Domain.csproj">
+ <Project>{6C7E9365-3F36-43E1-90EA-A1342850B902}</Project>
+ <Name>E017.Domain</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
View
421 E017-view-projections/sample-csharp/E017.Domain.Test.Console/ILogger.cs
@@ -0,0 +1,421 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Platform
+{
+ public interface ILogger
+ {
+ void Fatal(string text);
+
+ void Error(string text);
+
+ void Info(string text);
+
+ void Debug(string text);
+
+ void Trace(string text);
+
+ void Fatal(string format, params object[] args);
+
+ void Error(string format, params object[] args);
+
+ void Info(string format, params object[] args);
+
+ void Debug(string format, params object[] args);
+
+ void Trace(string format, params object[] args);
+
+ void FatalException(Exception exc, string text);
+
+ void ErrorException(Exception exc, string text);
+
+ void InfoException(Exception exc, string text);
+
+ void DebugException(Exception exc, string text);
+
+ void TraceException(Exception exc, string text);
+
+ void FatalException(Exception exc, string format, params object[] args);
+
+ void ErrorException(Exception exc, string format, params object[] args);
+
+ void InfoException(Exception exc, string format, params object[] args);
+
+ void DebugException(Exception exc, string format, params object[] args);
+
+ void TraceException(Exception exc, string format, params object[] args);
+ }
+
+ /// <summary>
+ /// Static class that is responsible for wiring in
+ /// proper logger infrastructure (NLOG in this case)
+ /// </summary>
+ public static class LogManager
+ {
+ private static bool _initialized;
+
+ //public static string LogsDirectory
+ //{
+ // get
+ // {
+ // EnsureInitialized();
+ // return Environment.GetEnvironmentVariable("EVENTSTORE_LOGSDIR");
+ // }
+ //}
+
+ public static ILogger GetLoggerFor<T>()
+ {
+ return GetLogger(typeof(T).Name);
+ }
+
+ public static ILogger GetLogger(string logName)
+ {
+ return _logFactory(logName);
+ }
+
+
+ static Func<string, ILogger> _logFactory = s => ConsoleLoggerFactory.GetLogFor(s);
+ static Action _finalizer;
+
+
+
+
+
+
+ public static void Init(Func<string,ILogger> logger, Action dispose)
+ {
+ //Ensure.NotNull(componentName, "componentName");
+ if (_initialized)
+ throw new InvalidOperationException("Cannot initialize twice");
+
+ _initialized = true;
+ _logFactory = logger;
+ _finalizer = dispose;
+ //SetLogsDirectoryIfNeeded(logsDirectory);
+ //SetComponentName(componentName);
+ RegisterGlobalExceptionHandler();
+ }
+
+ public static void Finish()
+ {
+ _finalizer();
+ //NLog.LogManager.Configuration = null;
+ }
+
+ private static void EnsureInitialized()
+ {
+ if (!_initialized)
+ throw new InvalidOperationException("Init method must be called");
+ }
+
+ //private static void SetLogsDirectoryIfNeeded(string logsDirectory)
+ //{
+ // const string logsDirEnvVar = "EVENTSTORE_LOGSDIR";
+ // var directory = Environment.GetEnvironmentVariable(logsDirEnvVar);
+ // if (directory == null)
+ // {
+ // directory = logsDirectory;
+ // Environment.SetEnvironmentVariable(logsDirEnvVar, directory, EnvironmentVariableTarget.Process);
+ // }
+ //}
+
+ //private static void SetComponentName(string componentName)
+ //{
+ // Environment.SetEnvironmentVariable("EVENTSTORE_INT-COMPONENT-NAME", componentName, EnvironmentVariableTarget.Process);
+ //}
+
+ private static void RegisterGlobalExceptionHandler()
+ {
+ var globalLogger = GetLogger("GLOBAL-LOGGER");
+ AppDomain.CurrentDomain.UnhandledException += (s, e) =>
+ {
+ var exc = e.ExceptionObject as Exception;
+ if (exc != null)
+ {
+ globalLogger.FatalException(exc, "Global Unhandled Exception occured.");
+ }
+ else
+ {
+ globalLogger.Fatal("Global Unhandled Exception object: {0}.", e.ExceptionObject);
+ }
+ };
+ }
+ }
+
+ class LazyLogger : ILogger
+ {
+ private readonly Lazy<ILogger> _logger;
+
+ public LazyLogger(Func<ILogger> factory)
+ {
+ //Ensure.NotNull(factory, "factory");
+ _logger = new Lazy<ILogger>(factory);
+ }
+
+ public void Fatal(string text)
+ {
+ _logger.Value.Fatal(text);
+ }
+
+ public void Error(string text)
+ {
+ _logger.Value.Error(text);
+ }
+
+ public void Info(string text)
+ {
+ _logger.Value.Info(text);
+ }
+
+ public void Debug(string text)
+ {
+ _logger.Value.Debug(text);
+ }
+
+ public void Trace(string text)
+ {
+ _logger.Value.Trace(text);
+ }
+
+ public void Fatal(string format, params object[] args)
+ {
+ _logger.Value.Fatal(format, args);
+ }
+
+ public void Error(string format, params object[] args)
+ {
+ _logger.Value.Error(format, args);
+ }
+
+ public void Info(string format, params object[] args)
+ {
+ _logger.Value.Info(format, args);
+ }
+
+ public void Debug(string format, params object[] args)
+ {
+ _logger.Value.Debug(format, args);
+ }
+
+ public void Trace(string format, params object[] args)
+ {
+ _logger.Value.Trace(format, args);
+ }
+
+ public void FatalException(Exception exc, string format)
+ {
+ _logger.Value.FatalException(exc, format);
+ }
+
+ public void ErrorException(Exception exc, string format)
+ {
+ _logger.Value.ErrorException(exc, format);
+ }
+
+ public void InfoException(Exception exc, string format)
+ {
+ _logger.Value.InfoException(exc, format);
+ }
+
+ public void DebugException(Exception exc, string format)
+ {
+ _logger.Value.DebugException(exc, format);
+ }
+
+ public void TraceException(Exception exc, string format)
+ {
+ _logger.Value.TraceException(exc, format);
+ }
+
+ public void FatalException(Exception exc, string format, params object[] args)
+ {
+ _logger.Value.FatalException(exc, format, args);
+ }
+
+ public void ErrorException(Exception exc, string format, params object[] args)
+ {
+ _logger.Value.ErrorException(exc, format, args);
+ }
+
+ public void InfoException(Exception exc, string format, params object[] args)
+ {
+ _logger.Value.InfoException(exc, format, args);
+ }
+
+ public void DebugException(Exception exc, string format, params object[] args)
+ {
+ _logger.Value.DebugException(exc, format, args);
+ }
+
+ public void TraceException(Exception exc, string format, params object[] args)
+ {
+ _logger.Value.TraceException(exc, format, args);
+ }
+ }
+
+ public static class ConsoleLoggerFactory
+ {
+ static BlockingCollection<Tuple<ConsoleColor, string>> _queue;
+ static Thread _thread;
+ static readonly object _lock = new object();
+
+ public static ILogger GetLogFor(string name)
+ {
+ lock (_lock)
+ {
+ if (_thread == null)
+ {
+ _queue = new BlockingCollection<Tuple<ConsoleColor, string>>();
+ _thread = new Thread(PerformLogging)
+ {
+ IsBackground = true,
+ Name = "Console Logger"
+ };
+ _thread.Start();
+ }
+ }
+ return new ConsoleLogger(_queue);
+ }
+ static void PerformLogging()
+ {
+ foreach (var tuple in _queue.GetConsumingEnumerable())
+ {
+ var old = Console.ForegroundColor;
+ Console.ForegroundColor = tuple.Item1;
+ Console.WriteLine(tuple.Item2);
+ Console.ForegroundColor = old;
+ }
+ }
+ }
+
+ /// <summary>
+ /// This should be used as singleton, which polls log messages from multiple sources.
+ /// Replace with NLog in production
+ /// </summary>
+ public sealed class ConsoleLogger : ILogger
+ {
+ // TODO: reuse object pool, if needed.
+ // TODO: better queue for Mono
+
+ readonly BlockingCollection<Tuple<ConsoleColor, string>> _queue;
+
+ public ConsoleLogger(BlockingCollection<Tuple<ConsoleColor, string>> queue)
+ {
+ _queue = queue;
+ }
+
+
+ static readonly ConsoleColor FatalColor = ConsoleColor.Red;
+ static readonly ConsoleColor ErrorColor = ConsoleColor.DarkRed;
+ static readonly ConsoleColor InfoColor = ConsoleColor.Green;
+ static readonly ConsoleColor DebugColor = ConsoleColor.Gray;
+ static readonly ConsoleColor TraceColor = ConsoleColor.DarkGray;
+ public void Fatal(string text)
+ {
+ _queue.Add(Tuple.Create(FatalColor, text));
+ }
+
+ public void Error(string text)
+ {
+ _queue.Add(Tuple.Create(ErrorColor, text));
+ }
+
+ public void Info(string text)
+ {
+ _queue.Add(Tuple.Create(InfoColor, text));
+ }
+
+ public void Debug(string text)
+ {
+ _queue.Add(Tuple.Create(DebugColor, text));
+ }
+
+ public void Trace(string text)
+ {
+ _queue.Add(Tuple.Create(TraceColor, text));
+ }
+
+ public void Fatal(string format, params object[] args)
+ {
+ Fatal(string.Format(format, args));
+ }
+
+ public void Error(string format, params object[] args)
+ {
+ Error(string.Format(format, args));
+ }
+
+ public void Info(string format, params object[] args)
+ {
+ Info(string.Format(format, args));
+ }
+
+ public void Debug(string format, params object[] args)
+ {
+ Debug(string.Format(format, args));
+ }
+
+ public void Trace(string format, params object[] args)
+ {
+ Trace(string.Format(format, args));
+ }
+
+ public void FatalException(Exception exc, string text)
+ {
+ Fatal(text);
+ Fatal(exc.ToString());
+ }
+
+ public void ErrorException(Exception exc, string text)
+ {
+ Error(text);
+ Error(exc.ToString());
+ }
+
+ public void InfoException(Exception exc, string text)
+ {
+ Info(text);
+ Info(exc.ToString());
+ }
+
+ public void DebugException(Exception exc, string text)
+ {
+ Debug(text);
+ Debug(exc.ToString());
+ }
+
+ public void TraceException(Exception exc, string text)
+ {
+ Trace(text);
+ Trace(exc.ToString());
+ }
+
+ public void FatalException(Exception exc, string format, params object[] args)
+ {
+ FatalException(exc, string.Format(format, args));
+ }
+
+ public void ErrorException(Exception exc, string format, params object[] args)
+ {
+ ErrorException(exc, string.Format(format, args));
+ }
+
+ public void InfoException(Exception exc, string format, params object[] args)
+ {
+ InfoException(exc, string.Format(format, args));
+ }
+
+ public void DebugException(Exception exc, string format, params object[] args)
+ {
+ DebugException(exc, string.Format(format, args));
+ }
+
+ public void TraceException(Exception exc, string format, params object[] args)
+ {
+ TraceException(exc, string.Format(format, args));
+ }
+ }
+
+}
View
56 E017-view-projections/sample-csharp/E017.Domain.Test.Console/Program.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Linq;
+using System.Threading;
+using E017;
+
+namespace E017.Domain.Test.Console
+{
+ class Program
+ {
+ // This class is just a loop that wraps a simple interactive console (command-line shell)
+ // which accepts command-line input and tries to parse it through the ConsoleActions "Handlers" and execute the command.
+ static void Main(string[] args)
+ {
+ var env = ConsoleEnvironment.BuildEnvironment();
+ env.Log.Info("Starting Being The Worst interactive shell :)");
+ env.Log.Info("Type 'help' to get more info");
+
+
+ // TODO: add distance-based suggestions
+ while(true)
+ {
+ Thread.Sleep(300);
+ System.Console.Write("> ");
+ var line = System.Console.ReadLine();
+ if (string.IsNullOrWhiteSpace(line))
+ {
+ continue;
+ }
+ var split = line.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries);
+ IShellAction value;
+ if (!env.Handlers.TryGetValue(split[0],out value))
+ {
+ env.Log.Error("Unknown command '{0}'. Type 'help' for help", line);
+ continue;
+ }
+ try
+ {
+ value.Execute(env, split.Skip(1).ToArray());
+ }
+ catch (DomainError ex)
+ {
+ env.Log.Error("{0}: {1}", ex.Name, ex.Message);
+ }
+ catch(ArgumentException ex)
+ {
+ env.Log.Error("Invalid usage of '{0}': {1}",split[0], ex.Message);
+ env.Log.Debug(value.Usage);
+ }
+ catch (Exception ex)
+ {
+ env.Log.ErrorException(ex, "Failure while processing command '{0}'", split[0]);
+ }
+ }
+ }
+ }
+}
View
36 E017-view-projections/sample-csharp/E017.Domain.Test.Console/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+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: AssemblyTitle("E017.Domain.Test.Console")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("E017.Domain.Test.Console")]
+[assembly: AssemblyCopyright("Copyright © 2012")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 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("2711627d-de0c-4e88-96b3-3d6dc42f477f")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
View
41 .../sample-csharp/E017.Domain.Test/ApplicationServices/Factory/assign_employee_to_factory.cs
@@ -0,0 +1,41 @@
+using E017.Contracts;
+using NUnit.Framework;
+// ReSharper disable InconsistentNaming
+namespace E017.Domain.ApplicationServices.Factory
+{
+ public class assign_employee_to_factory : factory_application_service_spec
+ {
+ static readonly FactoryId Id = new FactoryId(11);
+
+ [Test]
+ public void empty_factory_allows_any_employee_not_bender_to_be_assigned()
+ {
+ Given(new FactoryOpened(Id));
+ When(new AssignEmployeeToFactory(Id, "fry"));
+ Expect(new EmployeeAssignedToFactory(Id, "fry"));
+ }
+
+ [Test]
+ public void duplicate_employee_name_is_assigned_but_not_allowed()
+ {
+ Given(new FactoryOpened(Id),
+ new EmployeeAssignedToFactory(Id,"fry"));
+ When(new AssignEmployeeToFactory(Id, "fry"));
+ ExpectError("employee-name-already-taken");
+ }
+ [Test]
+ public void no_employee_named_bender_is_allowed_to_be_assigned()
+ {
+ Given(new FactoryOpened(Id));
+ When(new AssignEmployeeToFactory(Id, "bender"));
+ ExpectError("bender-employee");
+ }
+
+ [Test]
+ public void cant_assign_employee_to_unopened_factory()
+ {
+ When(new AssignEmployeeToFactory(Id, "fry"));
+ ExpectError("factory-is-not-open");
+ }
+ }
+}
View
109 ...ew-projections/sample-csharp/E017.Domain.Test/ApplicationServices/Factory/factory_spec.cs
@@ -0,0 +1,109 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using E017.ApplicationServices.Factory;
+using E017.Contracts;
+using E017.Domain.DomainServices;
+// ReSharper disable InconsistentNaming
+namespace E017.Domain.ApplicationServices.Factory
+{
+ /// <summary>
+ /// Base class that acts as execution environment (container) for our application
+ /// service. It will be responsible for wiring in test version of services to
+ /// factory and executing commands
+ /// </summary>
+ public abstract class factory_application_service_spec : application_service_spec
+ {
+ public TestBlueprintLibrary Library;
+
+ protected override void SetupServices()
+ {
+ Library = new TestBlueprintLibrary();
+ }
+
+ protected override IEvent[] ExecuteCommand(IEvent[] given, ICommand cmd)
+ {
+ var store = new SingleCommitMemoryStore();
+ foreach (var e in given.OfType<IFactoryEvent>())
+ {
+ store.Preload(e.Id.ToString(),e);
+ }
+ new FactoryApplicationService(store, Library).Execute(cmd);
+ return store.Appended ?? new IEvent[0];
+ }
+
+
+ protected void When(IFactoryCommand when)
+ {
+ WhenMessage(when);
+ }
+ protected void Given(params IFactoryEvent[] given)
+ {
+ GivenMessages(given);
+ }
+ protected void GivenSetup(params SpecSetupEvent[] setup)
+ {
+ GivenMessages(setup);
+ }
+ protected void Expect(params IFactoryEvent[] given)
+ {
+ ExpectMessages(given);
+ }
+
+ // additional helper builders
+
+ protected static InventoryShipment NewShipment(string name, params string[] partDescriptions)
+ {
+ return new InventoryShipment(name, NewCarPartList(partDescriptions));
+ }
+
+ protected static CarPart[] NewCarPartList(params string[] partDescriptions)
+ {
+ var parts = new List<CarPart>();
+
+ foreach (var description in partDescriptions)
+ {
+ var items = description.Split(new char[] {' '}, 2);
+
+ if (items.Length == 1)
+ {
+ parts.Add(new CarPart(items[0], 1));
+ }
+ else
+ {
+ parts.Add(new CarPart(items[1], int.Parse(items[0])));
+ }
+ }
+ var carParts = parts.ToArray();
+ return carParts;
+ }
+ }
+
+ sealed class SingleCommitMemoryStore : IEventStore
+ {
+ public readonly IList<Tuple<string, IEvent>> Store = new List<Tuple<string, IEvent>>();
+ public IEvent[] Appended = null;
+ public void Preload(string id, IEvent e)
+ {
+ Store.Add(Tuple.Create(id, e));
+ }
+
+ EventStream IEventStore.LoadEventStream(string id)
+ {
+ var events = Store.Where(i => id.Equals(i.Item1)).Select(i => i.Item2).ToList();
+ return new EventStream
+ {
+ Events = events,
+ StreamVersion = events.Count
+ };
+ }
+
+ void IEventStore.AppendEventsToStream(string id, long expectedVersion, ICollection<IEvent> events)
+ {
+ if (Appended != null)
+ throw new InvalidOperationException("Only one commit it allowed");
+ Appended = events.ToArray();
+ }
+ }
+
+}
View
25 ...ew-projections/sample-csharp/E017.Domain.Test/ApplicationServices/Factory/open_factory.cs
@@ -0,0 +1,25 @@
+using E017.Contracts;
+using NUnit.Framework;
+// ReSharper disable InconsistentNaming
+namespace E017.Domain.ApplicationServices.Factory
+{
+ public class open_factory : factory_application_service_spec
+ {
+ readonly FactoryId Id = new FactoryId(42);
+
+ [Test]
+ public void open_factory_correclty_with_factory_id()
+ {
+ When(new OpenFactory(Id));
+ Expect(new FactoryOpened(Id));
+ }
+
+ [Test]
+ public void attempt_to_open_more_than_once_is_not_allowed()
+ {
+ Given(new FactoryOpened(Id));
+ When(new OpenFactory(Id));
+ ExpectError("factory-already-created");
+ }
+ }
+}
View
85 ...w-projections/sample-csharp/E017.Domain.Test/ApplicationServices/Factory/produce_a_car.cs
@@ -0,0 +1,85 @@
+using E017.Contracts;
+using NUnit.Framework;
+// ReSharper disable InconsistentNaming
+namespace E017.Domain.ApplicationServices.Factory
+{
+ public class produce_a_car : factory_application_service_spec
+ {
+ readonly FactoryId Id = new FactoryId(17);
+
+ [Test]
+ public void assigning_employee_not_in_factory_is_an_error()
+ {
+ Given(new FactoryOpened(Id));
+ When(new ProduceACar(Id, "fry", "Ford"));
+
+ ExpectError("unknown-employee");
+ }
+
+ [Test]
+ public void missing_required_car_part_is_an_error()
+ {
+ GivenSetup(Library.RecordBlueprint("Ford", new CarPart("chassis", 1)));
+ Given(
+ new FactoryOpened(Id),
+ new EmployeeAssignedToFactory(Id, "fry")
+ );
+ When(new ProduceACar(Id, "fry", "Ford"));
+ ExpectError("required-part-not-found");
+ }
+
+ [Test]
+ public void car_model_not_in_blueprint_library_is_an_error()
+ {
+ Given(
+ new FactoryOpened(Id),
+ new EmployeeAssignedToFactory(Id, "fry")
+ );
+ When(new ProduceACar(Id, "fry", "Volvo"));
+ ExpectError("car-model-not-found");
+ }
+
+ [Test]
+ public void car_produced_announcment_received()
+ {
+ GivenSetup(
+ Library.RecordBlueprint("death star", new CarPart("magic box", 10)),
+ Library.RecordBlueprint("Ford", NewCarPartList("chassis", "4 wheels", "engine")));
+ Given(
+ new FactoryOpened(Id),
+ new EmployeeAssignedToFactory(Id, "fry"),
+ new ShipmentUnpackedInCargoBay(Id, "fry", new[] { NewShipment("ship1", "chassis", "4 wheels", "engine") })
+ );
+
+ When(new ProduceACar(Id, "fry", "Ford"));
+
+ Expect(new CarProduced(Id, "fry", "Ford", NewCarPartList("chassis", "4 wheels", "engine")));
+ }
+
+
+ [Test]
+ public void an_employee_who_has_already_produced_a_car_today_cant_be_assigned()
+ {
+ GivenSetup(Library.RecordBlueprint("Ford", NewCarPartList("chassis", "4 wheels", "engine")));
+ Given(
+ new FactoryOpened(Id),
+ new EmployeeAssignedToFactory(Id, "fry"),
+ new ShipmentUnpackedInCargoBay(Id, "fry", new[] { NewShipment("ship-1", "chassis", "4 wheels", "engine") }),
+ new CarProduced(Id, "fry", "Ford", NewCarPartList("chassis", "4 wheels", "engine")),
+ new ShipmentUnpackedInCargoBay(Id, "fry", new[] { NewShipment("ship-2", "chassis", "4 wheels", "engine") })
+ );
+
+ When(new ProduceACar(Id, "fry", "Ford"));
+
+ ExpectError("employee-already-produced-car-today");
+ }
+
+
+ [Test]
+ public void when_factory_not_open_is_an_error()
+ {
+ When(new ProduceACar(Id, "fry", "Ford"));
+ ExpectError("factory-is-not-open");
+ }
+ }
+}
View
57 ...mple-csharp/E017.Domain.Test/ApplicationServices/Factory/receive_shipment_in_cargo_bay.cs
@@ -0,0 +1,57 @@
+using E017.Contracts;
+using NUnit.Framework;
+// ReSharper disable InconsistentNaming
+namespace E017.Domain.ApplicationServices.Factory
+{
+ public class receive_shipment_in_cargo_bay : factory_application_service_spec
+ {
+ static readonly FactoryId Id = new FactoryId(52);
+
+ [Test]
+ public void a_shipment_received_announcement_is_made_with_correct_car_parts_list()
+ {
+ Given(new FactoryOpened(Id),
+ new EmployeeAssignedToFactory(Id, "yoda"));
+ When(new ReceiveShipmentInCargoBay(Id, "shipment-777", NewCarPartList("engine")));
+ Expect(new ShipmentReceivedInCargoBay(Id, NewShipment("shipment-777","engine")));
+ }
+
+
+ [Test]
+ public void empty_shipment_is_not_allowed()
+ {
+ Given(new FactoryOpened(Id),
+ new EmployeeAssignedToFactory(Id, "yoda"));
+ When(new ReceiveShipmentInCargoBay(Id, "some shipment", new CarPart[0]));
+ ExpectError("empty-InventoryShipments");
+ }
+
+ [Test]
+ public void an_empty_shipment_that_comes_to_factory_with_no_employees_is_not_received()
+ {
+ Given(new FactoryOpened(Id));
+ When(new ReceiveShipmentInCargoBay(Id, "some shipment", NewCarPartList("chassis")));
+ ExpectError("unknown-employee");
+ }
+ [Test]
+ public void there_are_already_two_shipments_in_cargo_bay_so_no_new_shipments_allowed()
+ {
+ Given(
+ new FactoryOpened(Id),
+ new EmployeeAssignedToFactory(Id, "chubakka"),
+ new ShipmentReceivedInCargoBay(Id, NewShipment("shipmt-11", "3 engine")),
+ new ShipmentReceivedInCargoBay(Id, NewShipment("shipmt-12", "40 wheel"))
+ );
+
+ When(new ReceiveShipmentInCargoBay(Id, "shipmt-13", NewCarPartList("20 bmw-6")));
+ ExpectError("more-than-two-InventoryShipments");
+ }
+
+ [Test]
+ public void when_factory_not_open_is_an_error()
+ {
+ When(new ReceiveShipmentInCargoBay(Id, "some shipment", new CarPart[0]));
+ ExpectError("factory-is-not-open");
+ }
+ }
+}
View
65 ...ample-csharp/E017.Domain.Test/ApplicationServices/Factory/unpack_shipment_in_cargo_bay.cs
@@ -0,0 +1,65 @@
+using E017.Contracts;
+using NUnit.Framework;
+
+// ReSharper disable InconsistentNaming
+namespace E017.Domain.ApplicationServices.Factory
+{
+
+
+ public class unpack_shipment_in_cargo_bay : factory_application_service_spec
+ {
+ static readonly FactoryId Id = new FactoryId(25);
+
+ [Test]
+ public void an_unpacked_announcement_is_made_with_correct_inventory_list()
+ {
+ var shipment = NewShipment("ship-1", "chassis");
+
+ Given(new FactoryOpened(Id),
+ new EmployeeAssignedToFactory(Id, "fry"),
+ new ShipmentReceivedInCargoBay(Id, shipment));
+
+ When(new UnpackAndInventoryShipmentInCargoBay(Id, "fry"));
+ Expect(new ShipmentUnpackedInCargoBay(Id, "fry", new[] { shipment }));
+ }
+
+ [Test]
+ public void assigning_employee_not_in_factory_is_an_error()
+ {
+ Given(new FactoryOpened(Id),
+ new EmployeeAssignedToFactory(Id, "ben"),
+ new ShipmentReceivedInCargoBay(Id, NewShipment("ship-1", "chassis")));
+ When(new UnpackAndInventoryShipmentInCargoBay(Id, "fry"));
+ ExpectError("unknown-employee");
+ }
+
+ [Test]
+ public void an_employee_asked_to_unpack_more_than_once_a_day_is_not_allowed()
+ {
+ var shipment = NewShipment("ship-1", "chassis");
+
+ Given(new FactoryOpened(Id),
+ new EmployeeAssignedToFactory(Id, "fry"),
+ new ShipmentReceivedInCargoBay(Id, shipment),
+ new ShipmentUnpackedInCargoBay(Id, "fry", new[] { shipment }));
+ When(new UnpackAndInventoryShipmentInCargoBay(Id, "fry"));
+ ExpectError("employee-already-unpacked-cargo");
+ }
+
+ [Test]
+ public void it_is_an_error_if_there_are_no_shipments_to_unpack()
+ {
+ Given(new FactoryOpened(Id),
+ new EmployeeAssignedToFactory(Id, "fry"));
+ When(new UnpackAndInventoryShipmentInCargoBay(Id, "fry"));
+ ExpectError("empty-InventoryShipments");
+ }
+
+ [Test]
+ public void when_factory_not_open_is_an_error()
+ {
+ When(new UnpackAndInventoryShipmentInCargoBay(Id, "fry"));
+ ExpectError("factory-is-not-open");
+ }
+ }
+}
View
313 ...rojections/sample-csharp/E017.Domain.Test/ApplicationServices/application_service_spec.cs
@@ -0,0 +1,313 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using NUnit.Framework;
+// ReSharper disable InconsistentNaming
+namespace E017.Domain.ApplicationServices
+{
+
+ /// <summary>
+ /// Base class for testing application services that host aggregates with
+ /// event sourcing
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ public abstract class application_service_spec : IListSpecifications
+ {
+ readonly List<IEvent> _given = new List<IEvent>();
+
+ ICommand _when;
+ readonly List<IEvent> _then = new List<IEvent>();
+ readonly List<IEvent> _givenEvents = new List<IEvent>();
+ bool _thenWasCalled;
+
+ protected static DateTime Date(int year, int month = 1, int day = 1, int hour = 0)
+ {
+ return new DateTime(year, month, day, hour, 0, 0, DateTimeKind.Unspecified);
+ }
+
+ protected static DateTime Time(int hour, int minute = 0, int second = 0)
+ {
+ return new DateTime(2011, 1, 1, hour, minute, second, DateTimeKind.Unspecified);
+ }
+
+ protected abstract void SetupServices();
+
+ protected class ExceptionThrown : IEvent, IAmFakeEventForTesting
+ {
+
+ public string Name { get; set; }
+
+ public string FakeType { get { return Name; } }
+
+ public ExceptionThrown(string name)
+ {
+ Name = name;
+ }
+
+ public override string ToString()
+ {
+ return string.Format("Domain error '{0}'", Name);
+ }
+ }
+
+ protected void GivenMessages(params IEvent[] g)
+ {
+ _given.AddRange(g);
+ foreach (var @event in g)
+ {
+ var setup = @event as SpecSetupEvent;
+ if (setup != null)
+ {
+ setup.Apply();
+ }
+ else _givenEvents.Add(@event);
+ }
+ }
+
+ protected void WhenMessage(ICommand command)
+ {
+ _when = command;
+ }
+
+ [SetUp]
+ public void SetUpSpecification()
+ {
+ _when = null;
+ _given.Clear();
+ _then.Clear();
+ _thenWasCalled = false;
+ _givenEvents.Clear();
+ SetupServices();
+ }
+
+ public Func<string> GetSpecificationName = () => TestContext.CurrentContext.Test.Name;
+
+ [TearDown]
+ public void TeardownSpecification()
+ {
+ if (!_thenWasCalled)
+ Assert.Fail("THEN was not called from the unit test");
+ }
+
+ protected void PrintSpecification()
+ {
+ Console.WriteLine("Fixture: {0}", GetType().Name.Replace("_", " "));
+ Console.WriteLine("Specification: {0}", GetSpecificationName().Replace("_", " "));
+
+ Console.WriteLine();
+ if (_given.Any())
+ {
+ Console.WriteLine("GIVEN:");
+
+ for (var i = 0; i < _given.Count; i++)
+ {
+ PrintAdjusted(" " + (i + 1) + ". ", _given[i].ToString().Trim());
+ }
+ }
+ else
+ {
+ Console.WriteLine("GIVEN no events");
+ }
+
+ if (_when != null)
+ {
+ Console.WriteLine();
+ Console.WriteLine("WHEN:");
+ PrintAdjusted(" ", _when.ToString().Trim());
+ }
+
+ Console.WriteLine();
+
+ if (_then.Any())
+ {
+ Console.WriteLine("THEN:");
+ for (int i = 0; i < _then.Count; i++)
+ {
+ PrintAdjusted(" " + (i + 1) + ". ", _then[i].ToString().Trim());
+ }
+ }
+ else
+ {
+ Console.WriteLine("THEN nothing.");
+ }
+ }
+
+ protected void PrintResults(ICollection<ExpectResult> exs)
+ {
+ var results = exs.ToArray();
+ var failures = results.Where(f => f.Failure != null).ToArray();
+ if (!failures.Any())
+ {
+ Console.WriteLine();
+ Console.WriteLine("Results: [Passed]");
+ return;
+ }
+ Console.WriteLine();
+ Console.WriteLine("Results: [Failed]");
+
+ for (int i = 0; i < results.Length; i++)
+ {
+ PrintAdjusted(" " + (i + 1) + ". ", results[i].Expectation);
+ PrintAdjusted(" ", results[i].Failure ?? "PASS");
+ }
+ }
+
+ protected abstract IEvent[] ExecuteCommand(IEvent[] store, ICommand cmd);
+
+ public void ExpectError(string error)
+ {
+ ExpectMessages(new ExceptionThrown(error));
+ }
+
+ bool _dontExecuteOnExpect;
+
+ public void ExpectMessages(params IEvent[] g)
+ {
+ _thenWasCalled = true;
+ _then.AddRange(g);
+
+ IEnumerable<IEvent> actual;
+ var givenEvents = _givenEvents.ToArray();
+
+ if (_dontExecuteOnExpect) return;
+ try
+ {
+ actual = ExecuteCommand(givenEvents, _when);
+ }
+ catch (DomainError e)
+ {
+ actual = new IEvent[] { new ExceptionThrown(e.Name) };
+ }
+
+ var results = CompareAssert(_then.ToArray(), actual.ToArray()).ToArray();
+
+ PrintSpecification();
+ PrintResults(results);
+
+ if (results.Any(r => r.Failure != null))
+ Assert.Fail("Specification failed");
+ }
+
+ public static string GetAdjusted(string adj, string text)
+ {
+ var first = true;
+ var builder = new StringBuilder();
+ foreach (var s in text.Split(new[] { Environment.NewLine }, StringSplitOptions.None))
+ {
+ builder.Append(first ? adj : new string(' ', adj.Length));
+ builder.AppendLine(s);
+ first = false;
+ }
+ return builder.ToString();
+ }
+
+ static void PrintAdjusted(string adj, string text)
+ {
+ Console.Write(GetAdjusted(adj, text));
+ }
+
+ protected static IEnumerable<ExpectResult> CompareAssert(
+ IEvent[] expected,
+ IEvent[] actual)
+ {
+ var max = Math.Max(expected.Length, actual.Length);
+
+ for (int i = 0; i < max; i++)
+ {
+ var ex = expected.Skip(i).FirstOrDefault();
+ var ac = actual.Skip(i).FirstOrDefault();
+
+ var expectedString = ex == null ? "No event expected" : ex.ToString();
+ var actualString = ac == null ? "No event actually" : ac.ToString();
+
+ var result = new ExpectResult { Expectation = expectedString };
+
+ var realDiff = CompareObjects.FindDifferences(ex, ac);
+ if (!string.IsNullOrEmpty(realDiff))
+ {
+ var stringRepresentationsDiffer = expectedString != actualString;
+
+ result.Failure = stringRepresentationsDiffer ?
+ GetAdjusted("Was: ", actualString) :
+ GetAdjusted("Diff: ", realDiff);
+ }
+
+ yield return result;
+ }
+ }
+
+ public class ExpectResult
+ {
+ public string Failure;
+ public string Expectation;
+ }
+
+
+ public IEnumerable<SpecificationInfo> ListSpecifications()
+ {
+ var type = GetType();
+
+ if (type.IsAbstract)
+ yield break;
+ _dontExecuteOnExpect = true;
+
+ var myMethods = GetType().GetMethods().Where(m => m.IsDefined(typeof(TestAttribute), true)).ToArray();
+ foreach (var method in myMethods)
+ {
+ SetUpSpecification();
+ method.Invoke(this, null);
+ yield return new SpecificationInfo
+ {
+ CaseName = method.Name.Replace("_", " "),
+ GroupName = type.Name.Replace("_", " "),
+ Given = _givenEvents.Cast<IEvent>().ToArray(),
+ When = _when,
+ Then = _then.Cast<IEvent>().ToArray()
+ };
+ }
+ }
+ }
+
+ /// <summary>
+ /// Helper event, which allows us to turn test domain services into light-weight
+ /// event-sourced classes
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ public sealed class SpecSetupEvent : IEvent, IAmFakeEventForTesting
+ {
+ public string FakeType { get { return null; } }
+
+ readonly string _describe;
+ public readonly Action Apply;
+
+ public SpecSetupEvent(Action apply, string describe, params object[] args)
+ {
+ Apply = apply;
+ _describe = string.Format(describe, args);
+ }
+ public override string ToString()
+ {
+ return _describe;
+ }
+ }
+
+ public interface IListSpecifications
+ {
+ IEnumerable<SpecificationInfo> ListSpecifications();
+ }
+
+ public class SpecificationInfo
+ {
+ public string GroupName;
+ public string CaseName;
+ public IEvent[] Given;
+ public ICommand When;
+ public IEvent[] Then;
+ }
+
+ public interface IAmFakeEventForTesting
+ {
+ string FakeType { get; }
+ }
+}
View
958 E017-view-projections/sample-csharp/E017.Domain.Test/CompareObjects.cs
@@ -0,0 +1,958 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text;
+
+namespace E017.Domain
+{
+ /// <summary>
+ /// Class that allows comparison of two objects of the same type to each other. Supports classes, lists, arrays, dictionaries, child comparison and more.
+ /// Source code is taken from http://comparenetobjects.codeplex.com/
+ /// All rights are reserved for the author.
+ /// </summary>
+ public class CompareObjects
+ {
+ public static string FindDifferences(object expected, object actual)
+ {
+ var compare = new CompareObjects
+ {
+ MaxDifferences = 10
+ };
+
+
+ if (compare.Compare(expected, actual))
+ return null;
+
+ return compare.DifferencesString
+ .Trim('\r', '\n')
+ .Replace("object1", "expected")
+ .Replace("object2", "actual");
+ }
+
+ #region Class Variables
+
+ /// <summary>
+ /// Keep track of parent objects in the object hiearchy
+ /// </summary>
+ readonly List<object> _parents = new List<object>();
+
+ /// <summary>
+ /// Reflection Cache for property info
+ /// </summary>
+ readonly Dictionary<Type, PropertyInfo[]> _propertyCache = new Dictionary<Type, PropertyInfo[]>();
+
+ /// <summary>
+ /// Reflection Cache for field info
+ /// </summary>
+ readonly Dictionary<Type, FieldInfo[]> _fieldCache = new Dictionary<Type, FieldInfo[]>();
+
+ #endregion
+
+ #region Properties
+
+ /// <summary>
+ /// Ignore classes, properties, or fields by name during the comparison.
+ /// Case sensitive.
+ /// </summary>
+ /// <example>ElementsToIgnore.Add("CreditCardNumber")</example>
+ public List<string> ElementsToIgnore { get; set; }
+
+ /// <summary>
+ /// If true, private properties and fields will be compared. The default is false.
+ /// </summary>
+ public bool ComparePrivateProperties { get; set; }
+
+ /// <summary>
+ /// If true, private fields will be compared. The default is false.
+ /// </summary>
+ public bool ComparePrivateFields { get; set; }
+
+ /// <summary>
+ /// If true, static properties will be compared. The default is true.
+ /// </summary>
+ public bool CompareStaticProperties { get; set; }
+
+ /// <summary>
+ /// If true, static fields will be compared. The default is true.
+ /// </summary>
+ public bool CompareStaticFields { get; set; }
+
+ /// <summary>
+ /// If true, child objects will be compared. The default is true.
+ /// If false, and a list or array is compared list items will be compared but not their children.
+ /// </summary>
+ public bool CompareChildren { get; set; }
+
+ /// <summary>
+ /// If true, compare read only properties (only the getter is implemented).
+ /// The default is true.
+ /// </summary>
+ public bool CompareReadOnly { get; set; }
+
+ /// <summary>
+ /// If true, compare fields of a class (see also CompareProperties).
+ /// The default is true.
+ /// </summary>
+ public bool CompareFields { get; set; }
+
+ /// <summary>
+ /// If true, compare properties of a class (see also CompareFields).
+ /// The default is true.
+ /// </summary>
+ public bool CompareProperties { get; set; }
+
+ /// <summary>
+ /// The maximum number of differences to detect
+ /// </summary>
+ /// <remarks>
+ /// Default is 1 for performance reasons.
+ /// </remarks>
+ public int MaxDifferences { get; set; }
+
+ /// <summary>
+ /// The differences found during the compare
+ /// </summary>
+ public List<String> Differences { get; set; }
+
+ /// <summary>
+ /// The differences found in a string suitable for a textbox
+ /// </summary>
+ public string DifferencesString
+ {
+ get
+ {
+ StringBuilder sb = new StringBuilder(4096);
+
+ sb.Append("\r\nBegin Differences:\r\n");
+
+ foreach (string item in Differences)
+ {
+ sb.AppendFormat("{0}\r\n", item);
+ }
+
+ sb.AppendFormat("End Differences (Maximum of {0} differences shown).", MaxDifferences);
+
+ return sb.ToString();
+ }
+ }
+
+ /// <summary>
+ /// Reflection properties and fields are cached. By default this cache is cleared after each compare. Set to false to keep the cache for multiple compares.
+ /// </summary>
+ /// <seealso cref="Caching"/>
+ /// <seealso cref="ClearCache"/>
+ public bool AutoClearCache { get; set; }
+
+ /// <summary>
+ /// By default properties and fields for types are cached for each compare. By default this cache is cleared after each compare.
+ /// </summary>
+ /// <seealso cref="AutoClearCache"/>
+ /// <seealso cref="ClearCache"/>
+ public bool Caching { get; set; }
+
+ /// <summary>
+ /// A list of attributes to ignore a class, property or field
+ /// </summary>
+ /// <example>AttributesToIgnore.Add(typeof(XmlIgnoreAttribute));</example>
+ public List<Type> AttributesToIgnore { get; set; }
+
+ #endregion
+
+ #region Constructor
+
+ /// <summary>
+ /// Set up defaults for the comparison
+ /// </summary>
+ public CompareObjects()
+ {
+ Differences = new List<string>();
+ ElementsToIgnore = new List<string>();
+ AttributesToIgnore = new List<Type>();
+ CompareStaticFields = true;
+ CompareStaticProperties = true;
+ ComparePrivateProperties = false;
+ ComparePrivateFields = false;
+ CompareChildren = true;
+ CompareReadOnly = true;
+ CompareFields = true;
+ CompareProperties = true;
+ Caching = true;
+ AutoClearCache = true;
+ MaxDifferences = 1;
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ /// <summary>
+ /// Compare two objects of the same type to each other.
+ /// </summary>
+ /// <remarks>
+ /// Check the Differences or DifferencesString Properties for the differences.
+ /// Default MaxDifferences is 1 for performance
+ /// </remarks>
+ /// <param name="object1"></param>
+ /// <param name="object2"></param>
+ /// <returns>True if they are equal</returns>
+ public bool Compare(object object1, object object2)
+ {
+ string defaultBreadCrumb = string.Empty;
+
+ Differences.Clear();
+ Compare(object1, object2, defaultBreadCrumb);
+
+ if (AutoClearCache)
+ ClearCache();
+
+ return Differences.Count == 0;
+ }
+
+ /// <summary>
+ /// Reflection properties and fields are cached. By default this cache is cleared automatically after each compare.
+ /// </summary>
+ /// <seealso cref="AutoClearCache"/>
+ /// <seealso cref="Caching"/>
+ public void ClearCache()
+ {
+ _propertyCache.Clear();
+ _fieldCache.Clear();
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ /// <summary>
+ /// Compare two objects
+ /// </summary>
+ /// <param name="object1"></param>
+ /// <param name="object2"></param>
+ /// <param name="breadCrumb">Where we are in the object hiearchy</param>
+ void Compare(object object1, object object2, string breadCrumb)
+ {
+ //If both null return true
+ if (object1 == null && object2 == null)
+ return;
+
+ //Check if one of them is null
+ if (object1 == null)
+ {
+ Differences.Add(string.Format("object1{0} == null && object2{0} != null ((null),{1})", breadCrumb,
+ cStr(object2)));
+ return;
+ }
+
+ if (object2 == null)
+ {
+ Differences.Add(string.Format("object1{0} != null && object2{0} == null ({1},(null))", breadCrumb,
+ cStr(object1)));
+ return;
+ }
+
+ Type t1 = object1.GetType();
+ Type t2 = object2.GetType();
+
+ //Objects must be the same type
+ if (t1 != t2)
+ {
+ Differences.Add(string.Format("Different Types: object1{0}.GetType() != object2{0}.GetType()",
+ breadCrumb));
+ return;
+ }
+
+ else if (IsIList(t1)) //This will do arrays, multi-dimensional arrays and generic lists
+ {
+ CompareIList(object1, object2, breadCrumb);
+ }
+ else if (IsIDictionary(t1))
+ {
+ CompareIDictionary(object1, object2, breadCrumb);
+ }
+ else if (IsEnum(t1))
+ {
+ CompareEnum(object1, object2, breadCrumb);
+ }
+ else if (IsPointer(t1))
+ {
+ ComparePointer(object1, object2, breadCrumb);
+ }
+ else if (IsSimpleType(t1))
+ {
+ CompareSimpleType(object1, object2, breadCrumb);
+ }
+ else if (IsClass(t1))
+ {
+ CompareClass(object1, object2, breadCrumb);
+ }
+ else if (IsTimespan(t1))
+ {
+ CompareTimespan(object1, object2, breadCrumb);
+ }
+ else if (IsStruct(t1))
+ {
+ CompareStruct(object1, object2, breadCrumb);
+ }
+ else
+ {
+ throw new NotImplementedException("Cannot compare object of type " + t1.Name);
+ }
+ }
+
+
+ /// <summary>
+ /// Check if any type has attributes that should be bypassed
+ /// </summary>
+ /// <returns></returns>
+ bool IgnoredByAttribute(Type type)
+ {
+ foreach (Type attributeType in AttributesToIgnore)
+ {
+ if (type.GetCustomAttributes(attributeType, false).Length > 0)
+ return true;
+ }
+
+ return false;
+ }
+
+
+ bool IsTimespan(Type t)
+ {
+ return t == typeof(TimeSpan);
+ }
+
+ bool IsPointer(Type t)
+ {
+ return t == typeof(IntPtr) || t == typeof(UIntPtr);
+ }
+
+ bool IsEnum(Type t)
+ {
+ return t.IsEnum;
+ }
+
+ bool IsStruct(Type t)
+ {
+ return t.IsValueType && !IsSimpleType(t);
+ }
+
+ bool IsSimpleType(Type t)
+ {
+ if (t.IsGenericType && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
+ {
+ t = Nullable.GetUnderlyingType(t);
+ }
+
+ return t.IsPrimitive
+ || t == typeof(DateTime)
+ || t == typeof(decimal)
+ || t == typeof(string)
+ || t == typeof(Guid);
+ }
+
+ bool ValidStructSubType(Type t)
+ {
+ return IsSimpleType(t)
+ || IsEnum(t)
+ || IsArray(t)
+ || IsClass(t)
+ || IsIDictionary(t)
+ || IsTimespan(t)
+ || IsIList(t);
+ }
+
+ bool IsArray(Type t)
+ {
+ return t.IsArray;
+ }
+
+ bool IsClass(Type t)
+ {
+ return t.IsClass;
+ }
+
+ bool IsIDictionary(Type t)
+ {
+ return t.GetInterface("System.Collections.IDictionary", true) != null;
+ }
+
+
+ bool IsIList(Type t)
+ {
+ return t.GetInterface("System.Collections.IList", true) != null;
+ }
+
+ bool IsChildType(Type t)
+ {
+ return !IsSimpleType(t)
+ && (IsClass(t)
+ || IsArray(t)
+ || IsIDictionary(t)
+ || IsIList(t)
+ || IsStruct(t));
+ }
+
+ /// <summary>
+ /// Compare a timespan struct
+ /// </summary>
+ /// <param name="object1"></param>
+ /// <param name="object2"></param>
+ /// <param name="breadCrumb"></param>
+ void CompareTimespan(object object1, object object2, string breadCrumb)
+ {
+ if (((TimeSpan)object1).Ticks != ((TimeSpan)object2).Ticks)
+ {
+ Differences.Add(string.Format("object1{0}.Ticks != object2{0}.Ticks", breadCrumb));
+ }
+ }
+
+ /// <summary>
+ /// Compare a pointer struct
+ /// </summary>
+ /// <param name="object1"></param>
+ /// <param name="object2"></param>
+ /// <param name="breadCrumb"></param>
+ void ComparePointer(object object1, object object2, string breadCrumb)
+ {
+ if (
+ (object1.GetType() == typeof(IntPtr) && object2.GetType() == typeof(IntPtr) &&
+ ((IntPtr)object1) != ((IntPtr)object2)) ||
+ (object1.GetType() == typeof(UIntPtr) && object2.GetType() == typeof(UIntPtr) &&
+ ((UIntPtr)object1) != ((UIntPtr)object2))
+ )
+ {
+ Differences.Add(string.Format("object1{0} != object2{0}", breadCrumb));
+ }
+ }
+
+ /// <summary>
+ /// Compare an enumeration
+ /// </summary>
+ /// <param name="object1"></param>
+ /// <param name="object2"></param>
+ /// <param name="breadCrumb"></param>
+ void CompareEnum(object object1, object object2, string breadCrumb)
+ {
+ if (object1.ToString() != object2.ToString())
+ {
+ string currentBreadCrumb = AddBreadCrumb(breadCrumb, object1.GetType().Name, string.Empty, -1);
+ Differences.Add(string.Format("object1{0} != object2{0} ({1},{2})", currentBreadCrumb, object1, object2));
+ }
+ }
+
+ /// <summary>
+ /// Compare a simple type
+ /// </summary>
+ /// <param name="object1"></param>
+ /// <param name="object2"></param>
+ /// <param name="breadCrumb"></param>
+ void CompareSimpleType(object object1, object object2, string breadCrumb)
+ {
+ if (object2 == null) //This should never happen, null check happens one level up
+ throw new ArgumentNullException("object2");
+
+ var valOne = object1 as IComparable;
+
+ if (valOne == null) //This should never happen, null check happens one level up
+ throw new ArgumentNullException("object1");
+
+ if (valOne.CompareTo(object2) != 0)
+ {
+ Differences.Add(string.Format("object1{0} != object2{0} ({1},{2})", breadCrumb, object1, object2));
+ }
+ }
+
+
+ /// <summary>
+ /// Compare a struct
+ /// </summary>
+ /// <param name="object1"></param>
+ /// <param name="object2"></param>
+ /// <param name="breadCrumb"></param>
+ void CompareStruct(object object1, object object2, string breadCrumb)
+ {
+ try
+ {
+ _parents.Add(object1);
+ _parents.Add(object2);
+
+ Type t1 = object1.GetType();
+
+ //Compare the fields
+ IEnumerable<FieldInfo> currentFields = GetFieldInfo(t1);
+
+ foreach (FieldInfo item in currentFields)
+ {
+ //Only compare simple types within structs (Recursion Problems)
+ if (!ValidStructSubType(item.FieldType))
+ {
+ continue;
+ }
+
+ string currentCrumb = AddBreadCrumb(breadCrumb, item.Name, string.Empty, -1);
+
+ Compare(item.GetValue(object1), item.GetValue(object2), currentCrumb);
+
+ if (Differences.Count >= MaxDifferences)
+ return;
+ }
+
+ PerformCompareProperties(t1, object1, object2, breadCrumb);
+ }
+ finally
+ {
+ _parents.Remove(object1);
+ _parents.Remove(object2);
+ }
+ }
+
+ /// <summary>
+ /// Compare the properties, fields of a class
+ /// </summary>
+ /// <param name="object1"></param>
+ /// <param name="object2"></param>
+ /// <param name="breadCrumb"></param>
+ void CompareClass(object object1, object object2, string breadCrumb)
+ {
+ try
+ {
+ _parents.Add(object1);
+ _parents.Add(object2);
+
+ Type t1 = object1.GetType();
+
+ //We ignore the class name
+ if (ElementsToIgnore.Contains(t1.Name) || IgnoredByAttribute(t1))
+ return;
+
+ //Compare the properties
+ if (CompareProperties)
+ PerformCompareProperties(t1, object1, object2, breadCrumb);
+
+ //Compare the fields
+ if (CompareFields)
+ PerformCompareFields(t1, object1, object2, breadCrumb);
+ }
+ finally
+ {
+ _parents.Remove(object1);
+ _parents.Remove(object2);
+ }
+ }
+
+
+ /// <summary>
+ /// Compare the fields of a class
+ /// </summary>
+ /// <param name="t1"></param>
+ /// <param name="object1"></param>
+ /// <param name="object2"></param>
+ /// <param name="breadCrumb"></param>
+ void PerformCompareFields(Type t1,
+ object object1,
+ object object2,