add csharp-es sample
This commit is contained in:
@@ -0,0 +1,93 @@
|
|||||||
|
using CsharpEs.Library;
|
||||||
|
|
||||||
|
namespace CsharpEs.Domain;
|
||||||
|
|
||||||
|
public sealed record AccountState(Guid Id);
|
||||||
|
|
||||||
|
public abstract record AccountCommand
|
||||||
|
{
|
||||||
|
public sealed record OpenAccount(Guid AccountId, Money OpeningBalance) : AccountCommand;
|
||||||
|
|
||||||
|
public sealed record WithdrawMoney(Guid AccountId, Money Amount) : AccountCommand;
|
||||||
|
|
||||||
|
public sealed record DepositMoney(Guid AccountId, Money Amount) : AccountCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract record AccountEvent(Guid AccountId)
|
||||||
|
{
|
||||||
|
public sealed record AccountOpened(Guid AccountId, Money OpeningBalance)
|
||||||
|
: AccountEvent(AccountId);
|
||||||
|
|
||||||
|
public sealed record MoneyWithdrawn(Guid AccountId, Money Amount) : AccountEvent(AccountId);
|
||||||
|
|
||||||
|
public sealed record MoneyDeposited(Guid AccountId, Money Amount) : AccountEvent(AccountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract record AccountError
|
||||||
|
{
|
||||||
|
public sealed record AccountNotFound : AccountError;
|
||||||
|
|
||||||
|
public sealed record AccountOpenAlready : AccountError;
|
||||||
|
|
||||||
|
public sealed record OpeningBalanceMustBeNonNegative : AccountError;
|
||||||
|
|
||||||
|
public sealed record InnerException(Exception exception) : AccountError;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AccountDecider
|
||||||
|
{
|
||||||
|
public static Result<AccountEvent, AccountError> Decide(
|
||||||
|
AccountState? state,
|
||||||
|
AccountCommand command
|
||||||
|
) =>
|
||||||
|
Result<AccountEvent, AccountError>.Catch(
|
||||||
|
() =>
|
||||||
|
(state, command) switch
|
||||||
|
{
|
||||||
|
// if this is a new account, check for valid opening balance
|
||||||
|
(null, AccountCommand.OpenAccount c) when c.OpeningBalance.Amount < 0m =>
|
||||||
|
Result<AccountEvent, AccountError>.Fail(
|
||||||
|
new AccountError.OpeningBalanceMustBeNonNegative()
|
||||||
|
),
|
||||||
|
|
||||||
|
// still a new account, now we can open it
|
||||||
|
(null, AccountCommand.OpenAccount c) => Result<AccountEvent, AccountError>.Ok(
|
||||||
|
new AccountEvent.AccountOpened(c.AccountId, c.OpeningBalance)
|
||||||
|
),
|
||||||
|
|
||||||
|
// if we have an account already, you can't open it
|
||||||
|
(not null, AccountCommand.OpenAccount c) => Result<
|
||||||
|
AccountEvent,
|
||||||
|
AccountError
|
||||||
|
>.Fail(new AccountError.AccountOpenAlready()),
|
||||||
|
|
||||||
|
(null, _) => Result<AccountEvent, AccountError>.Fail(
|
||||||
|
new AccountError.AccountNotFound()
|
||||||
|
),
|
||||||
|
|
||||||
|
(_, AccountCommand.WithdrawMoney c) => Result<AccountEvent, AccountError>.Ok(
|
||||||
|
new AccountEvent.MoneyWithdrawn(c.AccountId, c.Amount)
|
||||||
|
),
|
||||||
|
|
||||||
|
(_, AccountCommand.DepositMoney c) => Result<AccountEvent, AccountError>.Ok(
|
||||||
|
new AccountEvent.MoneyDeposited(c.AccountId, c.Amount)
|
||||||
|
),
|
||||||
|
|
||||||
|
_ => throw new InvalidOperationException("Unknown command."),
|
||||||
|
},
|
||||||
|
e => new AccountError.InnerException(e)
|
||||||
|
);
|
||||||
|
|
||||||
|
public static Result<AccountState?, AccountError> Evolve(
|
||||||
|
AccountState? state,
|
||||||
|
AccountEvent @event
|
||||||
|
) =>
|
||||||
|
(state, @event) switch
|
||||||
|
{
|
||||||
|
(null, AccountEvent.AccountOpened e) => Result<AccountState?, AccountError>.Ok(
|
||||||
|
new AccountState(e.AccountId)
|
||||||
|
),
|
||||||
|
|
||||||
|
_ => Result<AccountState?, AccountError>.Ok(state),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace CsharpEs.Domain;
|
||||||
|
|
||||||
|
public sealed record Money(decimal Amount);
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using CsharpEs.Domain;
|
||||||
|
using CsharpEs.Library;
|
||||||
|
|
||||||
|
namespace CsharpEs.Infrastructure;
|
||||||
|
|
||||||
|
public abstract record ReadModelError
|
||||||
|
{
|
||||||
|
public sealed record AccountDetailsNotFound : ReadModelError;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record AccountBalanceReadModel(
|
||||||
|
Func<AccountEvent, Result<AccountDetails, ReadModelError>> Project,
|
||||||
|
Func<Result<KeyValuePair<Guid, AccountDetails>[], ReadModelError>> Query
|
||||||
|
);
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum AccountFlags
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
InOverdraft = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record AccountDetails(Money Balance, AccountFlags Flags);
|
||||||
|
|
||||||
|
public static class AccountBalanceReadModelModule
|
||||||
|
{
|
||||||
|
public static Result<AccountBalanceReadModel, ReadModelError> Create()
|
||||||
|
{
|
||||||
|
// This is our read-model specific "persistent" storage
|
||||||
|
var modelData = ImmutableDictionary<Guid, AccountDetails>.Empty;
|
||||||
|
|
||||||
|
Result<AccountDetails, ReadModelError> Project(AccountEvent @event)
|
||||||
|
{
|
||||||
|
Logging.Output("rm", "Projecting event", @event);
|
||||||
|
switch (@event)
|
||||||
|
{
|
||||||
|
case AccountEvent.AccountOpened e:
|
||||||
|
modelData = modelData.SetItem(
|
||||||
|
e.AccountId,
|
||||||
|
new AccountDetails(e.OpeningBalance, AccountFlags.None)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AccountEvent.MoneyWithdrawn e:
|
||||||
|
{
|
||||||
|
if (modelData.TryGetValue(e.AccountId, out var oldDetails))
|
||||||
|
{
|
||||||
|
var newBalance = new Money(oldDetails.Balance.Amount - e.Amount.Amount);
|
||||||
|
modelData = modelData.SetItem(
|
||||||
|
e.AccountId,
|
||||||
|
oldDetails with
|
||||||
|
{
|
||||||
|
Balance = newBalance,
|
||||||
|
Flags = CalcAccountFlags(newBalance, oldDetails),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return Result<AccountDetails, ReadModelError>.Fail(
|
||||||
|
new ReadModelError.AccountDetailsNotFound()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AccountEvent.MoneyDeposited e:
|
||||||
|
{
|
||||||
|
if (modelData.TryGetValue(e.AccountId, out var oldDetails))
|
||||||
|
{
|
||||||
|
var newBalance = new Money(oldDetails.Balance.Amount + e.Amount.Amount);
|
||||||
|
modelData = modelData.SetItem(
|
||||||
|
e.AccountId,
|
||||||
|
oldDetails with
|
||||||
|
{
|
||||||
|
Balance = newBalance,
|
||||||
|
Flags = CalcAccountFlags(newBalance, oldDetails),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return Result<AccountDetails, ReadModelError>.Fail(
|
||||||
|
new ReadModelError.AccountDetailsNotFound()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read model-side "project" may not normally return any data, but for demo
|
||||||
|
// purposes we return the affected data directly to save us modeling a separate
|
||||||
|
// query side.
|
||||||
|
return Result<AccountDetails, ReadModelError>.Ok(modelData[@event.AccountId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<KeyValuePair<Guid, AccountDetails>[], ReadModelError> Query() =>
|
||||||
|
Result<KeyValuePair<Guid, AccountDetails>[], ReadModelError>.Ok(modelData.ToArray());
|
||||||
|
|
||||||
|
return Result<AccountBalanceReadModel, ReadModelError>.Ok(
|
||||||
|
new AccountBalanceReadModel(Project, Query)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AccountFlags CalcAccountFlags(Money newBalance, AccountDetails oldDetails)
|
||||||
|
{
|
||||||
|
return newBalance.Amount < 0
|
||||||
|
? oldDetails.Flags | AccountFlags.InOverdraft
|
||||||
|
: oldDetails.Flags & ~AccountFlags.InOverdraft;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace CsharpEs.Library;
|
||||||
|
|
||||||
|
public static class Logging
|
||||||
|
{
|
||||||
|
public static T LogReturn<T>(string message, T r)
|
||||||
|
{
|
||||||
|
Console.WriteLine(message);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
static string Indent(string text, int indent = 4)
|
||||||
|
{
|
||||||
|
var prefix = new string(' ', indent);
|
||||||
|
|
||||||
|
return Environment.NewLine
|
||||||
|
+ string.Join(
|
||||||
|
Environment.NewLine,
|
||||||
|
text.Split(Environment.NewLine).Select(line => prefix + line)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Format(Object? o) =>
|
||||||
|
o != null
|
||||||
|
? Indent(
|
||||||
|
JsonSerializer.Serialize(
|
||||||
|
o,
|
||||||
|
new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
WriteIndented = true,
|
||||||
|
Converters = { new JsonStringEnumConverter() },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: "(null)";
|
||||||
|
|
||||||
|
public static void Output(string src, string message, Object? x)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[{src}] {message} | {Format(x)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Output(string src, string message)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[{src}] {message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Action<T> OutputDelegate<T>(string src, string message) =>
|
||||||
|
x =>
|
||||||
|
{
|
||||||
|
Output(src, message, x);
|
||||||
|
};
|
||||||
|
|
||||||
|
public static void OutputError<T>(string src, T error)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"\e[1;31m[{src} ERROR]\e[0m {Format(error)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
namespace CsharpEs.Library;
|
||||||
|
|
||||||
|
public readonly record struct Result<T, E>
|
||||||
|
{
|
||||||
|
private readonly T _value;
|
||||||
|
|
||||||
|
private readonly E _error;
|
||||||
|
|
||||||
|
public bool IsSuccess { get; }
|
||||||
|
|
||||||
|
public bool IsFailure => !IsSuccess;
|
||||||
|
|
||||||
|
public T Value =>
|
||||||
|
IsSuccess
|
||||||
|
? _value
|
||||||
|
: throw new InvalidOperationException("No value present for failed result.");
|
||||||
|
|
||||||
|
public E Error =>
|
||||||
|
IsFailure
|
||||||
|
? _error
|
||||||
|
: throw new InvalidOperationException("No error present for successful result.");
|
||||||
|
|
||||||
|
private Result(T value)
|
||||||
|
{
|
||||||
|
_value = value;
|
||||||
|
_error = default!;
|
||||||
|
IsSuccess = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result(E error)
|
||||||
|
{
|
||||||
|
_value = default!;
|
||||||
|
_error = error;
|
||||||
|
IsSuccess = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Result<T, E> Ok(T value) => new(value);
|
||||||
|
|
||||||
|
public static Result<T, E> Fail(E error) => new(error);
|
||||||
|
|
||||||
|
public TResult Match<TResult>(Func<T, TResult> onSuccess, Func<E, TResult> onFailure) =>
|
||||||
|
IsSuccess ? onSuccess(_value) : onFailure(_error);
|
||||||
|
|
||||||
|
public void Switch(Action<T> onSuccess, Action<E> onFailure)
|
||||||
|
{
|
||||||
|
if (IsSuccess)
|
||||||
|
onSuccess(_value);
|
||||||
|
else
|
||||||
|
onFailure(_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Result<T, E> Catch(Func<T> f, Func<Exception, E> exceptionMapper)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = f();
|
||||||
|
return Result<T, E>.Ok(result);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return Result<T, E>.Fail(exceptionMapper(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Result<T, E> Catch(Func<Result<T, E>> f, Func<Exception, E> exceptionMapper)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return f();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return Result<T, E>.Fail(exceptionMapper(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ResultExtensions
|
||||||
|
{
|
||||||
|
public static Result<TOut, E> Bind<TIn, TOut, E>(
|
||||||
|
this Result<TIn, E> result,
|
||||||
|
Func<TIn, Result<TOut, E>> binder
|
||||||
|
) => result.IsSuccess ? binder(result.Value) : Result<TOut, E>.Fail(result.Error);
|
||||||
|
|
||||||
|
public static Result<TOut, E> Map<TIn, TOut, E>(
|
||||||
|
this Result<TIn, E> result,
|
||||||
|
Func<TIn, TOut> mapper
|
||||||
|
) =>
|
||||||
|
result.IsSuccess
|
||||||
|
? Result<TOut, E>.Ok(mapper(result.Value))
|
||||||
|
: Result<TOut, E>.Fail(result.Error);
|
||||||
|
|
||||||
|
public static Result<T, E> Tap<T, E>(this Result<T, E> result, Action<T> action)
|
||||||
|
{
|
||||||
|
if (result.IsSuccess)
|
||||||
|
action(result.Value);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Result<T, E> TapError<T, E>(this Result<T, E> result, Action<E> action)
|
||||||
|
{
|
||||||
|
if (result.IsFailure)
|
||||||
|
action(result.Error);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Result<T, EOut> MapError<T, EIn, EOut>(
|
||||||
|
this Result<T, EIn> result,
|
||||||
|
Func<EIn, EOut> map
|
||||||
|
) =>
|
||||||
|
result.IsSuccess
|
||||||
|
? Result<T, EOut>.Ok(result.Value)
|
||||||
|
: Result<T, EOut>.Fail(map(result.Error));
|
||||||
|
|
||||||
|
public static Result<T, E> Log<T, E>(this Result<T, E> result, string src, string msg) =>
|
||||||
|
Tap(result, Logging.OutputDelegate<T>(src, msg)).TapError(m => Logging.OutputError(src, m));
|
||||||
|
|
||||||
|
public static Result<T, E> Log<T, E>(
|
||||||
|
this Result<T, E> result,
|
||||||
|
string src,
|
||||||
|
Func<T, string> renderText
|
||||||
|
) =>
|
||||||
|
Tap(result, x => Logging.OutputDelegate<T>(src, renderText(x))(x))
|
||||||
|
.TapError(m => Logging.OutputError(src, m));
|
||||||
|
|
||||||
|
public static Result<TOut, E> Select<TIn, TOut, E>(
|
||||||
|
this Result<TIn, E> result,
|
||||||
|
Func<TIn, TOut> mapper
|
||||||
|
) => result.Map(mapper);
|
||||||
|
|
||||||
|
public static Result<TOut, E> SelectMany<TIn, TIntermediate, TOut, E>(
|
||||||
|
this Result<TIn, E> result,
|
||||||
|
Func<TIn, Result<TIntermediate, E>> binder,
|
||||||
|
Func<TIn, TIntermediate, TOut> projector
|
||||||
|
) => result.Bind(x => binder(x).Map(y => projector(x, y)));
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using CsharpEs.Domain;
|
||||||
|
using CsharpEs.Infrastructure;
|
||||||
|
using CsharpEs.Library;
|
||||||
|
|
||||||
|
public abstract record DemoError
|
||||||
|
{
|
||||||
|
public sealed record Account(AccountError Error) : DemoError;
|
||||||
|
|
||||||
|
public sealed record ReadModel(ReadModelError Error) : DemoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
private static Guid accountId;
|
||||||
|
private static ImmutableList<AccountCommand> demoCommands = ImmutableList<AccountCommand>.Empty;
|
||||||
|
|
||||||
|
static Program()
|
||||||
|
{
|
||||||
|
accountId = Guid.NewGuid();
|
||||||
|
demoCommands = demoCommands.AddRange([
|
||||||
|
new AccountCommand.OpenAccount(accountId, new Money(-200m)),
|
||||||
|
// new AccountCommand.OpenAccount(accountId, new Money(200m)),
|
||||||
|
new AccountCommand.WithdrawMoney(accountId, new Money(100m)),
|
||||||
|
new AccountCommand.WithdrawMoney(accountId, new Money(200m)),
|
||||||
|
new AccountCommand.DepositMoney(accountId, new Money(500m)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ImmutableList<AccountEvent> eventStore = ImmutableList<AccountEvent>.Empty;
|
||||||
|
|
||||||
|
static Result<AccountState?, DemoError> ApplyEvent(
|
||||||
|
AccountBalanceReadModel balanceProjection,
|
||||||
|
AccountState? state,
|
||||||
|
AccountEvent @event
|
||||||
|
)
|
||||||
|
{
|
||||||
|
eventStore = eventStore.Add(@event);
|
||||||
|
Logging.Output(
|
||||||
|
"ae",
|
||||||
|
$"Applying event {Logging.Format(@event)} to state {Logging.Format(state)}"
|
||||||
|
);
|
||||||
|
return AccountDecider
|
||||||
|
.Evolve(state, @event)
|
||||||
|
.MapError(e => (DemoError)new DemoError.Account(e))
|
||||||
|
.Bind(newState =>
|
||||||
|
balanceProjection
|
||||||
|
.Project(@event)
|
||||||
|
.MapError(e => (DemoError)new DemoError.ReadModel(e))
|
||||||
|
.Log("ae ad", "Account details")
|
||||||
|
.Map(_ => newState)
|
||||||
|
)
|
||||||
|
.Log("ae", $"Applied and projected {Logging.Format(@event)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
Logging.Output("csharp-es", "Starting money handling demo...");
|
||||||
|
|
||||||
|
AccountBalanceReadModelModule
|
||||||
|
.Create()
|
||||||
|
.MapError(e => (DemoError)new DemoError.ReadModel(e))
|
||||||
|
.Bind(readModel =>
|
||||||
|
demoCommands
|
||||||
|
.Aggregate(
|
||||||
|
Result<AccountState?, DemoError>.Ok(null),
|
||||||
|
(stateResult, command) =>
|
||||||
|
stateResult.Bind(state =>
|
||||||
|
AccountDecider
|
||||||
|
.Decide(state, command)
|
||||||
|
.MapError(e => (DemoError)new DemoError.Account(e))
|
||||||
|
.Bind(@event => ApplyEvent(readModel, state, @event))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.Bind(s =>
|
||||||
|
readModel
|
||||||
|
.Query()
|
||||||
|
.Log("query", "Read model data")
|
||||||
|
.MapError(e => (DemoError)new DemoError.ReadModel(e))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.Log("csharp-es done", "Demo completed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<RootNamespace>CsharpEs</RootNamespace>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
+15
@@ -25,6 +25,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "csharp-fp3", "csharp-fp3\cs
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "csharp-fp3.Tests", "csharp-fp3.Tests\csharp-fp3.Tests.csproj", "{B85327F8-AFA8-47E9-97AB-318F74238929}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "csharp-fp3.Tests", "csharp-fp3.Tests\csharp-fp3.Tests.csproj", "{B85327F8-AFA8-47E9-97AB-318F74238929}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "csharp-es", "csharp-es\csharp-es.csproj", "{748A2E36-A131-4EAA-A403-E54E347010C0}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -143,6 +145,18 @@ Global
|
|||||||
{B85327F8-AFA8-47E9-97AB-318F74238929}.Release|x64.Build.0 = Release|Any CPU
|
{B85327F8-AFA8-47E9-97AB-318F74238929}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{B85327F8-AFA8-47E9-97AB-318F74238929}.Release|x86.ActiveCfg = Release|Any CPU
|
{B85327F8-AFA8-47E9-97AB-318F74238929}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{B85327F8-AFA8-47E9-97AB-318F74238929}.Release|x86.Build.0 = Release|Any CPU
|
{B85327F8-AFA8-47E9-97AB-318F74238929}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{748A2E36-A131-4EAA-A403-E54E347010C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{748A2E36-A131-4EAA-A403-E54E347010C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{748A2E36-A131-4EAA-A403-E54E347010C0}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{748A2E36-A131-4EAA-A403-E54E347010C0}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{748A2E36-A131-4EAA-A403-E54E347010C0}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{748A2E36-A131-4EAA-A403-E54E347010C0}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{748A2E36-A131-4EAA-A403-E54E347010C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{748A2E36-A131-4EAA-A403-E54E347010C0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{748A2E36-A131-4EAA-A403-E54E347010C0}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{748A2E36-A131-4EAA-A403-E54E347010C0}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{748A2E36-A131-4EAA-A403-E54E347010C0}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{748A2E36-A131-4EAA-A403-E54E347010C0}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -157,5 +171,6 @@ Global
|
|||||||
{4AFF063B-7125-4783-BE84-E1E9B4E2A462} = {89C53051-77F9-4C1C-ABE5-6D54AB398471}
|
{4AFF063B-7125-4783-BE84-E1E9B4E2A462} = {89C53051-77F9-4C1C-ABE5-6D54AB398471}
|
||||||
{83AFEF87-AAC9-45F4-B803-1BCDF0443BF7} = {89C53051-77F9-4C1C-ABE5-6D54AB398471}
|
{83AFEF87-AAC9-45F4-B803-1BCDF0443BF7} = {89C53051-77F9-4C1C-ABE5-6D54AB398471}
|
||||||
{B85327F8-AFA8-47E9-97AB-318F74238929} = {89C53051-77F9-4C1C-ABE5-6D54AB398471}
|
{B85327F8-AFA8-47E9-97AB-318F74238929} = {89C53051-77F9-4C1C-ABE5-6D54AB398471}
|
||||||
|
{748A2E36-A131-4EAA-A403-E54E347010C0} = {89C53051-77F9-4C1C-ABE5-6D54AB398471}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
Reference in New Issue
Block a user