This commit is contained in:
Christian Kauer 2023-12-27 17:20:35 +01:00
parent 6c090e29d0
commit c17f94ad73
22 changed files with 498 additions and 20 deletions

View File

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.1" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2" />
<PackageReference Include="NUnit.Analyzers" Version="3.6.1" />
<PackageReference Include="coverlet.collector" Version="3.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GameData\GameData.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1 @@
global using NUnit.Framework;

View File

@ -0,0 +1,21 @@
using GameData.Repository;
using GameModel;
using System.Data.Common;
namespace GameData.UnitTests
{
public class Tests
{
[SetUp]
public void Setup()
{
}
[Test]
public void Test1()
{
var client = new ExpenseRepository();
var test = client.GetAll();
}
}
}

260
GameData/ApiClient.cs Normal file
View File

@ -0,0 +1,260 @@
using GameData.Repository;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Headers;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Web;
namespace GameData
{
public sealed class ApiClient : IDisposable
{
private class Wrapper<T>
{
public T[] Data { get; set; }
}
private readonly HttpClient client;
private readonly string _apiKey = "9wIqngBjwR_l2f89D0EEAN1ldrcXUl6c";
private readonly string Api = "https://directus.straso.com/";
public ApiClient()
{
client = new HttpClient
{
Timeout = TimeSpan.FromMinutes(1)
};
client.DefaultRequestHeaders
.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders
.Authorization = new AuthenticationHeaderValue("Bearer", _apiKey);
//client.DefaultRequestHeaders.Add("Api-AppInfo", "KrahApp;" + Assembly.GetExecutingAssembly().GetName().Version.ToString());
if (!Api.EndsWith("/"))
{
Api += "/";
}
}
public void Dispose()
{
client.Dispose();
}
public string UrlEncode(string s)
{
return HttpUtility.UrlEncode(s);
}
private string GetFullUrl(string url)
{
if (url[0].Equals('/'))
{
url = url.Remove(0, 1);
}
url = Api + url;
return url;
}
private HttpRequestMessage GetHttpRequestMessage(HttpMethod method, string serviceUrl, object payload = null)
{
StringContent content = null;
if (payload != null)
{
var payloadString = JsonConvert.SerializeObject(payload);
content = new StringContent(payloadString, Encoding.UTF8, "application/json");
}
var url = serviceUrl;
if (!serviceUrl.StartsWith("http", StringComparison.InvariantCultureIgnoreCase))
{
url = GetFullUrl(serviceUrl);
}
var res = new HttpRequestMessage
{
Content = content,
Method = method,
RequestUri = new Uri(url),
};
//res.Headers.Add("Authorization: Bearer", _apiKey);
return res;
}
public async Task<bool> DownloadFile(string serviceUrl, string targetFile)
{
var res = false;
try
{
var request = GetHttpRequestMessage(HttpMethod.Get, serviceUrl);
request.Headers.Add("Accept", "application/json");
request.Headers.Add("Accept", "application/octet-stream");
request.Headers.Add("Accept", "file/download");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
using (var responseStream = await response.Content.ReadAsStreamAsync())
{
using (var fileStream = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true))
{
await responseStream.CopyToAsync(fileStream);
res = true;
}
}
}
else
{
var errorResult = await response.Content.ReadAsStringAsync();
//_logger.LogError("error downloading from service '{service}' with code '{statuscode}' - Result: '{result}', TargetFile: '{payload}'", serviceUrl, response.StatusCode, errorResult, targetFile);
}
}
catch (Exception ex)
{
//_logger.LogError(ex, "error at DownloadFile");
}
return res;
}
public async Task<T> Post<T>(object payload, string serviceUrl, bool suppressExceptions = true)
{
T res = default;
try
{
var response = await client.SendAsync(GetHttpRequestMessage(HttpMethod.Post, serviceUrl, payload));
if (response.IsSuccessStatusCode)
{
using (var responseStream = await response.Content.ReadAsStreamAsync())
{
using (var streamReader = new StreamReader(responseStream))
{
using (var jsonTextReader = new JsonTextReader(streamReader))
{
var serializer = new JsonSerializer();
res = serializer.Deserialize<T>(jsonTextReader);
}
}
}
}
else
{
var errorResult = await response.Content.ReadAsStringAsync();
//_logger.LogError("error posting to service '{service}' with code '{statuscode}' - Result: '{result}', Payload: '{payload}'", serviceUrl, response.StatusCode, errorResult, JsonConvert.SerializeObject(payload));
}
}
catch (Exception ex)
{
//_logger.LogError(ex, "error at post");
if (!suppressExceptions)
{
throw ex;
}
}
return res;
}
public async Task<T> Delete<T>(object payload, string serviceUrl, bool suppressExceptions = true)
{
T res = default;
try
{
var response = await client.SendAsync(GetHttpRequestMessage(HttpMethod.Delete, serviceUrl, payload));
if (response.IsSuccessStatusCode)
{
using (var responseStream = await response.Content.ReadAsStreamAsync())
{
using (var streamReader = new StreamReader(responseStream))
{
using (var jsonTextReader = new JsonTextReader(streamReader))
{
var serializer = new JsonSerializer();
res = serializer.Deserialize<T>(jsonTextReader);
}
}
}
}
else
{
var errorResult = await response.Content.ReadAsStringAsync();
//_logger.LogError("error deleting to service '{service}' with code '{statuscode}' - Result: '{result}', Payload: '{payload}'", serviceUrl, response.StatusCode, errorResult, JsonConvert.SerializeObject(payload));
}
}
catch (Exception ex)
{
//_logger.LogError(ex, "error at Delete");
if (!suppressExceptions)
{
throw ex;
}
}
return res;
}
public T[] Get<T>(string serviceUrl, bool suppressExceptions = true)
{
Wrapper<T> res = default;
try
{
//_logger.LogDebug($"calling service url {serviceUrl}");
var response = client.Send(GetHttpRequestMessage(HttpMethod.Get, serviceUrl));
if (response.IsSuccessStatusCode)
{
using (var responseStream = response.Content.ReadAsStream())
{
using (var streamReader = new StreamReader(responseStream))
{
string content;
if (typeof(T).Equals(typeof(string)))
{
content = streamReader.ReadToEnd();
res = (Wrapper<T>)Convert.ChangeType(content, typeof(T));
}
else
{
using (var jsonTextReader = new JsonTextReader(streamReader))
{
var serializer = new JsonSerializer();
//serializer.
res = serializer.Deserialize<Wrapper<T>>(jsonTextReader);
}
}
}
}
}
else
{
var errorResult = response.Content.ReadAsStringAsync();
//_logger.LogError("error getting from service '{service}' with code '{statuscode}' - Result: '{result}'", serviceUrl, response.StatusCode, errorResult);
}
}
catch (Exception ex)
{
//_logger.LogError(ex, "error at Get");
if (!suppressExceptions)
{
throw ex;
}
}
return res.Data;
}
}
}

16
GameData/ApiSettings.cs Normal file
View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GameData
{
public class ApiSettings
{
public string BaseUrl => "https://directus.straso.com/";
public string ApiToken => "9wIqngBjwR_l2f89D0EEAN1ldrcXUl6c";
}
}

View File

@ -0,0 +1,25 @@
using Autofac;
using GameData.Dummy;
using GameData.Repository;
using GameModel.Contract;
using GameModel.Settings;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GameData
{
//public static class ContainerSetup
//{
// public static Autofac.IContainer GetContainer()
// {
// var builder = new ContainerBuilder();
// builder.RegisterType<ExpenseRepository>().As<IExpenseRepository>();
// return builder.Build();
// }
//}
}

View File

@ -1,7 +1,7 @@
using GameModel; using GameModel;
using GameModel.Contract; using GameModel.Contract;
namespace GameData namespace GameData.Dummy
{ {
public class DummyExpenseRepository : IExpenseRepository public class DummyExpenseRepository : IExpenseRepository
{ {

View File

@ -6,6 +6,11 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="Autofac" Version="7.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\GameModel\GameModel.csproj" /> <ProjectReference Include="..\GameModel\GameModel.csproj" />
</ItemGroup> </ItemGroup>

View File

@ -0,0 +1,27 @@
using GameModel;
using GameModel.Contract;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GameData.Repository
{
public class ExpenseRepository : IExpenseRepository
{
private ApiClient _client;
string Url => "items/expense";
public ExpenseRepository()
{
_client = new ApiClient();
}
public IEnumerable<Expense> GetAll()
{
return _client.Get<Expense>(Url);
}
}
}

View File

@ -65,7 +65,7 @@ namespace GameHandler.UnitTests.Extensions
var bs = BoardState.Create(ThrowMode.Decrease); var bs = BoardState.Create(ThrowMode.Decrease);
var pt = PinThrow.Create(1, PinPicture.CreateAllPins(), false, true); var pt = PinThrow.Create(1, PinPicture.CreateAllPins(), false, true);
var tirgger = pt.GetExpenseTriggers(bs); var tirgger = pt.GetExpenseTriggers(bs);
CollectionAssert.AreEqual(tirgger, new[] { ExpenseTrigger.FullSink }); CollectionAssert.AreEqual(tirgger, new[] { ExpenseTrigger.SinkAtAll });
} }
} }
} }

View File

@ -29,11 +29,16 @@ namespace GameHandler.UnitTests.GameHandler
Assert.That(model.Throws.Count(), Is.EqualTo(4)); Assert.That(model.Throws.Count(), Is.EqualTo(4));
} }
[Test]
public void Update_ThrowsExceptionWhenModelIsNull()
{
Assert.That(() => _gh.Update(PinThrow.Create(2, false, true), null, BoardState.Create()), Throws.TypeOf<ArgumentNullException>());
}
[Test] [Test]
public void Update_ThrowsExceptionWhenModelIsNotInitialized() public void Update_ThrowsExceptionWhenModelIsNotInitialized()
{ {
Assert.That(() => _gh.Update(PinThrow.Create(2, false, true), BoardState.Create()), Throws.TypeOf<FreeGameThrowExcepetion>()); Assert.That(() => _gh.Update(PinThrow.Create(2, false, true), new FreeGameModel(null,0,Array.Empty<FreeGameThrow>()), BoardState.Create()), Throws.TypeOf<FreeGameThrowExcepetion>());
} }
} }
} }

View File

@ -18,7 +18,7 @@ namespace GameHandler.Extensions
{ {
if (boardState.PinPicture.AllUp && boardState.ThrowMode == ThrowMode.Decrease) if (boardState.PinPicture.AllUp && boardState.ThrowMode == ThrowMode.Decrease)
{ {
res.Add(ExpenseTrigger.FullSink); res.Add(ExpenseTrigger.SinkAtAll);
} }
else else
{ {

View File

@ -32,6 +32,11 @@ namespace GameHandler.GameHandler
public IGameModel Update(PinThrow pinThrow, IGameModel gameModel, BoardState boardStateBeforeUpdate) public IGameModel Update(PinThrow pinThrow, IGameModel gameModel, BoardState boardStateBeforeUpdate)
{ {
if (gameModel == null)
{
throw new ArgumentNullException("gameModel cannto be null");
}
var gm = gameModel as FreeGameModel; var gm = gameModel as FreeGameModel;
var throws = gm.Throws.ToList(); var throws = gm.Throws.ToList();

View File

@ -7,7 +7,7 @@ namespace GameModel.Contract
static Expense[] TestData = new[] { static Expense[] TestData = new[] {
Expense.Create(ExpenseType.Monetary,new[] { ExpenseTrigger.Sink }, "Gosse", 0.5m, false), Expense.Create(ExpenseType.Monetary,new[] { ExpenseTrigger.Sink }, "Gosse", 0.5m, false),
Expense.Create(ExpenseType.Monetary,new[] { ExpenseTrigger.Sink }, "Gosse2", 1.5m, false), Expense.Create(ExpenseType.Monetary,new[] { ExpenseTrigger.Sink }, "Gosse2", 1.5m, false),
Expense.Create(ExpenseType.Monetary,new[] { ExpenseTrigger.FullSink }, "Gosse bei Anwurf", 1m, false), Expense.Create(ExpenseType.Monetary,new[] { ExpenseTrigger.SinkAtAll }, "Gosse bei Anwurf", 1m, false),
Expense.Create(ExpenseType.Monetary,new[] { ExpenseTrigger.Circle }, "Kranz", 1m, true), Expense.Create(ExpenseType.Monetary,new[] { ExpenseTrigger.Circle }, "Kranz", 1m, true),
Expense.Create(ExpenseType.Monetary,new[] { ExpenseTrigger.NinePins }, "alle Neune", 1m, true), Expense.Create(ExpenseType.Monetary,new[] { ExpenseTrigger.NinePins }, "alle Neune", 1m, true),
Expense.Create(ExpenseType.Monetary,new[] { ExpenseTrigger.Eliminated }, "Ausgeschieden", 0.5m, false), Expense.Create(ExpenseType.Monetary,new[] { ExpenseTrigger.Eliminated }, "Ausgeschieden", 0.5m, false),

View File

@ -6,7 +6,7 @@ using System.Threading.Tasks;
namespace GameModel namespace GameModel
{ {
public record Expense(Guid Guid, ExpenseType ExpenseType, ExpenseTrigger[] ExpenseTriggers, string Name, decimal Price, bool IsInverse) public record Expense(Guid Id, ExpenseType ExpenseType, ExpenseTrigger[] ExpenseTriggers, string Name, decimal Price, bool IsInverse)
{ {
public static Expense Create(ExpenseType ExpenseType, ExpenseTrigger[] ExpenseTriggers, string Name, decimal Price, bool IsInverse) public static Expense Create(ExpenseType ExpenseType, ExpenseTrigger[] ExpenseTriggers, string Name, decimal Price, bool IsInverse)
{ {

View File

@ -17,7 +17,7 @@ namespace GameModel
/// <summary> /// <summary>
/// Sink at first throw in round /// Sink at first throw in round
/// </summary> /// </summary>
FullSink, // Gosse beim Anwurf SinkAtAll, // Gosse beim Anwurf
Eliminated, // Ausgeschieden Eliminated, // Ausgeschieden
Absent, // Am Spieltag nicht teilgenommen Absent, // Am Spieltag nicht teilgenommen
ExpensePoint // Strafpunkt, z.B. im "Scheißspiel" ExpensePoint // Strafpunkt, z.B. im "Scheißspiel"

View File

@ -6,11 +6,11 @@ using System.Threading.Tasks;
namespace GameModel namespace GameModel
{ {
public record MemberExpense(Guid ExpenseGuid, int MemberId, ExpenseType ExpenseType, DateTime Created, decimal Price, string Name) public record MemberExpense(Guid Id, int MemberId, Guid ExpenseId, ExpenseType ExpenseType, DateTime Created, decimal Price, string Name)
{ {
public static MemberExpense Create(int MemberId, Expense Expense) public static MemberExpense Create(int MemberId, Expense Expense)
{ {
return new MemberExpense(Guid.NewGuid(), MemberId, Expense.ExpenseType, DateTime.Now, Expense.Price, Expense.Name); return new MemberExpense(Guid.NewGuid(), MemberId, Expense.Id, Expense.ExpenseType, DateTime.Now, Expense.Price, Expense.Name);
} }
} }
} }

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GameModel.Settings
{
public class AppSettings
{
public string Test { get; set; }
}
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
@ -7,8 +7,22 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<None Remove="appsettings.json" />
</ItemGroup>
<ItemGroup>
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" /> <PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.0" />
<PackageReference Include="Spectre.Console" Version="0.48.0" /> <PackageReference Include="Spectre.Console" Version="0.48.0" />
<PackageReference Include="Spectre.Console.Json" Version="0.48.0" /> <PackageReference Include="Spectre.Console.Json" Version="0.48.0" />
</ItemGroup> </ItemGroup>

View File

@ -2,18 +2,75 @@
using Autofac; using Autofac;
using CommandLine; using CommandLine;
using GameData; using GameData;
using GameData.Repository;
using GameHandler; using GameHandler;
using GameHandler.DeathGame; using GameHandler.DeathGame;
using GameHandler.Extensions; using GameHandler.Extensions;
using GameModel; using GameModel;
using GameModel.Contract; using GameModel.Contract;
using GameModel.DeathGame; using GameModel.DeathGame;
using GameModel.Settings;
using KoogleCli.Model; using KoogleCli.Model;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Spectre.Console; using Spectre.Console;
using Spectre.Console.Json; using Spectre.Console.Json;
using static System.Runtime.InteropServices.JavaScript.JSType; using static System.Runtime.InteropServices.JavaScript.JSType;
const string RootLifetimeTag = "MyIsolatedRoot";
AnsiConsole.MarkupLine("Welcome to [green]koogle[/]"); AnsiConsole.MarkupLine("Welcome to [green]koogle[/]");
var container = Register();
var serviceCollection = new ServiceCollection();
//serviceCollection.AddLogging();
var scope = container.BeginLifetimeScope(RootLifetimeTag, b =>
{
//b.Populate(serviceCollection, RootLifetimeTag);
});
Autofac.IContainer Register()
{
var configurationBuilder = new ConfigurationBuilder()
.SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
//.AddXmlFile("appsettings.xml", optional: true, reloadOnChange: true)
//.AddEnvironmentVariables()
//.AddCommandLine(args);
;
// Add your custom configuration provider here if needed
// .Add(new CustomConfigurationSource());
IConfiguration configuration = configurationBuilder.Build();
var someSettings = configuration.GetSection(typeof(AppSettings).Name).Get<AppSettings>();
var builder = new ContainerBuilder();
builder.RegisterInstance<AppSettings>(someSettings);
builder.RegisterType<ExpenseRepository>().As<IExpenseRepository>();
return builder.Build();
}
var test = container.Resolve<AppSettings>();
Console.WriteLine( test.Test);
Console.ReadLine();
ShowMainMenu(); ShowMainMenu();
void ShowMainMenu() void ShowMainMenu()
{ {
@ -78,7 +135,7 @@ void NewGameAction()
void StartGameAction(string gameName) void StartGameAction(string gameName)
{ {
_gs = new GameService(GetContainer()); _gs = new GameService();
var bs = _gs.Start(new[] { 1,2,3,4}, GetGameSettings(gameName), gameName ); var bs = _gs.Start(new[] { 1,2,3,4}, GetGameSettings(gameName), gameName );
@ -120,13 +177,6 @@ void StartGameAction(string gameName)
ShowMainMenu(); ShowMainMenu();
} }
IContainer GetContainer()
{
var builder = new ContainerBuilder();
builder.RegisterType<DummyExpenseRepository>().As<IExpenseRepository>();
return builder.Build();
}
IGameSettings GetGameSettings(string gameName) IGameSettings GetGameSettings(string gameName)
{ {
if (gameName == DeathGameHandler.GAMENAME_DEATHBOX) if (gameName == DeathGameHandler.GAMENAME_DEATHBOX)

View File

@ -0,0 +1,5 @@
{
"AppSettings": {
"Test": "value"
}
}

View File

@ -13,10 +13,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameModel.UnitTests", "Game
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KoogleCli", "KoogleCli\KoogleCli.csproj", "{3FF45A02-42F9-4E75-993B-6582DD2A22BF}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KoogleCli", "KoogleCli\KoogleCli.csproj", "{3FF45A02-42F9-4E75-993B-6582DD2A22BF}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameData", "GameData\GameData.csproj", "{D026F84B-06F5-4BA4-8AB7-D1D385F0611C}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameData", "GameData\GameData.csproj", "{D026F84B-06F5-4BA4-8AB7-D1D385F0611C}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{5DC1FCEB-EF9B-4BFD-9AEA-56B6B81E204B}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{5DC1FCEB-EF9B-4BFD-9AEA-56B6B81E204B}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameData.UnitTests", "GameData.UnitTests\GameData.UnitTests.csproj", "{910C00EB-06CE-4EBC-9B76-DD290DC03AE8}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -47,6 +49,10 @@ Global
{D026F84B-06F5-4BA4-8AB7-D1D385F0611C}.Debug|Any CPU.Build.0 = Debug|Any CPU {D026F84B-06F5-4BA4-8AB7-D1D385F0611C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D026F84B-06F5-4BA4-8AB7-D1D385F0611C}.Release|Any CPU.ActiveCfg = Release|Any CPU {D026F84B-06F5-4BA4-8AB7-D1D385F0611C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D026F84B-06F5-4BA4-8AB7-D1D385F0611C}.Release|Any CPU.Build.0 = Release|Any CPU {D026F84B-06F5-4BA4-8AB7-D1D385F0611C}.Release|Any CPU.Build.0 = Release|Any CPU
{910C00EB-06CE-4EBC-9B76-DD290DC03AE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{910C00EB-06CE-4EBC-9B76-DD290DC03AE8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{910C00EB-06CE-4EBC-9B76-DD290DC03AE8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{910C00EB-06CE-4EBC-9B76-DD290DC03AE8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -54,6 +60,7 @@ Global
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{E2F3CE36-0051-4C9A-B3FF-0BB44292B756} = {5DC1FCEB-EF9B-4BFD-9AEA-56B6B81E204B} {E2F3CE36-0051-4C9A-B3FF-0BB44292B756} = {5DC1FCEB-EF9B-4BFD-9AEA-56B6B81E204B}
{C752388E-815A-4911-AC75-B6C27337D81A} = {5DC1FCEB-EF9B-4BFD-9AEA-56B6B81E204B} {C752388E-815A-4911-AC75-B6C27337D81A} = {5DC1FCEB-EF9B-4BFD-9AEA-56B6B81E204B}
{910C00EB-06CE-4EBC-9B76-DD290DC03AE8} = {5DC1FCEB-EF9B-4BFD-9AEA-56B6B81E204B}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {00DAFA57-4F14-4807-886E-392D4E67BA46} SolutionGuid = {00DAFA57-4F14-4807-886E-392D4E67BA46}