feat: Cookie-basierte Lokalisierung implementiert, ToLocal Erweiterungsmethoden hinzugefügt und Translation-Service entfernt. Stattdessen wird IStringLocalizer<T> verwendet, abhängig von der Situation wie Cookie-basierter Kultur oder Kultur basierend auf der Route.

This commit is contained in:
Developer 02 2024-04-29 13:31:37 +02:00
parent 1281d37239
commit 8188fa759f
15 changed files with 148 additions and 184 deletions

View File

@ -0,0 +1,59 @@
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.Localization;
using System.Globalization;
namespace DigitalData.Core.API
{
/// <summary>
/// Provides extension methods for configuring localization services in an ASP.NET Core application.
/// These methods simplify the integration of cookie-based localization by setting up resource paths
/// and defining supported cultures.
/// </summary>
public static class LocalizationExtensions
{
/// <summary>
/// Adds localized resources and view localization services to the application.
/// </summary>
/// <param name="services">The IServiceCollection to add services to.</param>
/// <param name="resourcesPath">The path to the resource files used for localization.</param>
/// <returns>The IServiceCollection for chaining.</returns>
public static IServiceCollection AddCookieBasedLocalizer(this IServiceCollection services, string resourcesPath)
{
// Adds localization services with the specified resources path.
services.AddLocalization(options => options.ResourcesPath = resourcesPath);
// Adds MVC services with view localization and data annotations localization.
services.AddMvc().AddViewLocalization(Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization();
return services;
}
/// <summary>
/// Configures the application to use cookie-based localization with support for multiple cultures.
/// </summary>
/// <param name="app">The IApplicationBuilder to configure.</param>
/// <param name="supportedCultureNames">A params array of supported culture names.</param>
/// <returns>The IApplicationBuilder for chaining.</returns>
public static IApplicationBuilder UseCookieBasedLocalizer(this IApplicationBuilder app, params string[] supportedCultureNames)
{
// Converts supported culture names into CultureInfo objects and checks for null or empty array.
IList<CultureInfo> supportedCultures = supportedCultureNames.Select(cn => new CultureInfo(cn)).ToList();
var defaultCultureInfo = supportedCultures.FirstOrDefault() ??
throw new ArgumentNullException(nameof(supportedCultureNames), "Supported cultures cannot be empty.");
// Configures localization options including default and supported cultures.
var options = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture(culture: defaultCultureInfo.Name, uiCulture: defaultCultureInfo.Name),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures
};
options.RequestCultureProviders.Add(new QueryStringRequestCultureProvider());
// Applies the localization settings to the application.
app.UseRequestLocalization(options);
return app;
}
}
}

View File

@ -1,7 +1,8 @@
using AutoMapper;
using DigitalData.Core.Contracts.Application;
using DigitalData.Core.Contracts.Infrastructure;
using DigitalData.Core.Contracts.CultureServices;
using Microsoft.Extensions.Localization;
namespace DigitalData.Core.Application
{
@ -28,8 +29,8 @@ namespace DigitalData.Core.Application
/// <param name="repository">The CRUD repository for accessing and manipulating entity data.</param>
/// <param name="translationService">The service used for key-based text translations, facilitating localization.</param>
/// <param name="mapper">The AutoMapper instance for mapping between DTOs and entities.</param>
public BasicCRUDService(TCRUDRepository repository, IKeyTranslationService translationService, IMapper mapper) :
base(repository, translationService, mapper)
public BasicCRUDService(TCRUDRepository repository, IStringLocalizer defaultLocalizer, IMapper mapper) :
base(repository, defaultLocalizer, mapper)
{
}
}

View File

@ -1,9 +1,9 @@
using DigitalData.Core.Contracts.Application;
using DigitalData.Core.Contracts.Infrastructure;
using DigitalData.Core.Contracts.CultureServices;
using AutoMapper;
using System.Reflection;
using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.Localization;
namespace DigitalData.Core.Application
{
@ -28,7 +28,7 @@ namespace DigitalData.Core.Application
/// <param name="repository">The CRUD repository for accessing the database.</param>
/// <param name="translationService">The service for translating messages based on culture.</param>
/// <param name="mapper">The AutoMapper instance for mapping between DTOs and entity objects.</param>
public CRUDService(TCRUDRepository repository, IKeyTranslationService translationService, IMapper mapper) : base(translationService)
public CRUDService(TCRUDRepository repository, IStringLocalizer defaultLocalizer, IMapper mapper) : base(defaultLocalizer)
{
_repository = repository;
_mapper = mapper;
@ -62,7 +62,7 @@ namespace DigitalData.Core.Application
var entity = await _repository.ReadByIdAsync(id);
if (entity is null)
{
var translatedMessage = _translationService.Translate(MessageKey.EntityDoesNotExist);
var translatedMessage = MessageKey.EntityDoesNotExist.LocalizedBy(_localizer);
return Failed<TReadDto>();
}
else
@ -93,7 +93,7 @@ namespace DigitalData.Core.Application
return Successful();
else
{
var translatedMessage = _translationService.Translate(MessageKey.UpdateFailed);
var translatedMessage = MessageKey.UpdateFailed.LocalizedBy(_localizer);
return Failed();
}
}
@ -109,8 +109,8 @@ namespace DigitalData.Core.Application
if (entity is null)
{
var deletionFailedMessage = _translationService.Translate(MessageKey.DeletionFailed);
var entityDoesNotExistMessage = _translationService.Translate(MessageKey.EntityDoesNotExist);
var deletionFailedMessage = MessageKey.DeletionFailed.LocalizedBy(_localizer);
var entityDoesNotExistMessage = MessageKey.EntityDoesNotExist.LocalizedBy(_localizer);
return new ServiceMessage(isSuccess: false, deletionFailedMessage, entityDoesNotExistMessage);
}
@ -120,7 +120,7 @@ namespace DigitalData.Core.Application
return Successful();
else
{
var deletionFailedMessage = _translationService.Translate(MessageKey.DeletionFailed);
var deletionFailedMessage = MessageKey.DeletionFailed.LocalizedBy(_localizer);
return Failed(deletionFailedMessage);
}
}

View File

@ -10,6 +10,7 @@
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="7.0.16" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="7.0.1" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.5.1" />

View File

@ -5,7 +5,7 @@ using System.DirectoryServices;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using System.DirectoryServices.AccountManagement;
using DigitalData.Core.Contracts.CultureServices;
using Microsoft.Extensions.Localization;
namespace DigitalData.Core.Application
{
@ -19,7 +19,7 @@ namespace DigitalData.Core.Application
private readonly DateTimeOffset _userCacheExpiration;
public Dictionary<string, string> CustomSearchFilters { get; }
public DirectorySearchService(IConfiguration configuration, ILogger<DirectorySearchService> logger, IMemoryCache memoryCache, IKeyTranslationService translationService) : base(translationService)
public DirectorySearchService(IConfiguration configuration, ILogger<DirectorySearchService> logger, IMemoryCache memoryCache, IStringLocalizer defaultLocalizer) : base(defaultLocalizer)
{
_memoryCache = memoryCache;

View File

@ -1,16 +1,15 @@
using DigitalData.Core.Contracts.Application;
using DigitalData.Core.Contracts.CultureServices;
using static System.Runtime.InteropServices.JavaScript.JSType;
using Microsoft.Extensions.Localization;
namespace DigitalData.Core.Application
{
public class ResponseService : IResponseService
{
protected readonly IKeyTranslationService _translationService;
protected readonly IStringLocalizer _localizer;
public ResponseService(IKeyTranslationService translationService)
public ResponseService(IStringLocalizer defaultLocalizer)
{
_translationService = translationService;
_localizer = defaultLocalizer;
}
#region WITHOUT_MESSAGE
@ -22,8 +21,7 @@ namespace DigitalData.Core.Application
/// <returns>A service message reflecting the operation outcome.</returns>
public IServiceMessage CreateMessage(bool isSuccess = false) => new ServiceMessage()
{
IsSuccess = isSuccess,
KeyTranslator = _translationService.Translate
IsSuccess = isSuccess
};
/// <summary>
@ -36,8 +34,7 @@ namespace DigitalData.Core.Application
public IServiceResult<T> CreateResult<T>(T? data = default, bool isSuccess = false) => new ServiceResult<T>()
{
IsSuccess = isSuccess,
Data = data,
KeyTranslator = _translationService.Translate
Data = data
};
/// <summary>

View File

@ -1,6 +1,6 @@
using AutoMapper;
using DigitalData.Core.Contracts.Application;
using DigitalData.Core.Contracts.CultureServices;
using Microsoft.Extensions.Localization;
namespace DigitalData.Core.Application
{
@ -9,7 +9,7 @@ namespace DigitalData.Core.Application
/// </summary>
public class ServiceBase : ResponseService, IServiceBase, IResponseService
{
public ServiceBase(IKeyTranslationService translationService) : base(translationService)
public ServiceBase(IStringLocalizer defaultLocalizer) : base(defaultLocalizer)
{
}
}

View File

@ -110,13 +110,6 @@ namespace DigitalData.Core.Application
[JsonIgnore]
public ICollection<string> CriticalMessages { get; init; } = new List<string>();
/// <summary>
/// A function that translates a message key from a string to its localized or transformed representation.
/// This property allows for custom translation logic to be applied based on the application's needs.
/// </summary>
[JsonIgnore]
public Func<string, string> KeyTranslator { get; init; } = key => key;
/// <summary>
/// Adds a new message to the collection of messages associated with the service operation.
/// </summary>

View File

@ -1,4 +1,5 @@
using DigitalData.Core.Contracts.Application;
using Microsoft.Extensions.Localization;
namespace DigitalData.Core.Application
{
@ -47,6 +48,24 @@ namespace DigitalData.Core.Application
public static bool HasSecurityBreachOrDataIntegrityFlag(this IServiceMessage serviceMessage) => serviceMessage.HasFlag(Flag.SecurityBreachOrDataIntegrity);
#endregion
#region StringLocalizer
/// <summary>
/// Retrieves the localized string for the specified key.
/// </summary>
/// <param name="key">The key for the localized string.</param>
/// <param name="localizer">The localizer to use for retrieving the localized string.</param>
/// <returns>The localized string associated with the specified key.</returns>
public static string LocalizedBy(this string key, IStringLocalizer localizer) => localizer[key];
/// <summary>
/// Retrieves the localized string for the specified enumeration key.
/// </summary>
/// <param name="key">The enumeration key for the localized string.</param>
/// <param name="localizer">The localizer to use for retrieving the localized string.</param>
/// <returns>The localized string associated with the specified enumeration key.</returns>
public static string LocalizedBy(this Enum key, IStringLocalizer localizer) => localizer[key.ToString()];
#endregion
#region ClientMessage
/// <summary>
/// Adds a single client message to the service message.
@ -59,18 +78,6 @@ namespace DigitalData.Core.Application
serviceMessage.ClientMessages.Add(message);
return serviceMessage;
}
/// <summary>
/// Adds a client message based on an enumeration key to the service message.
/// </summary>
/// <param name="serviceMessage">The service message to modify.</param>
/// <param name="messageKey">The enum key representing the message.</param>
/// <returns>The modified service message instance.</returns>
public static T WithClientMessageKey<T>(this T serviceMessage, Enum messageKey) where T : IServiceMessage
{
var message = serviceMessage.KeyTranslator(messageKey.ToString());
return serviceMessage.WithClientMessage(message);
}
#endregion
#region TraceMessages
@ -85,18 +92,6 @@ namespace DigitalData.Core.Application
serviceMessage.TraceMessages.Add(message);
return serviceMessage;
}
/// <summary>
/// Adds a trace message based on an enumeration key to the service message.
/// </summary>
/// <param name="serviceMessage">The service message to modify.</param>
/// <param name="messageKey">The enum key representing the trace message.</param>
/// <returns>The modified service message instance.</returns>
public static T WithTraceMessageKey<T>(this T serviceMessage, Enum messageKey) where T : IServiceMessage
{
var message = serviceMessage.KeyTranslator(messageKey.ToString());
return serviceMessage.WithTraceMessage(message);
}
#endregion
#region DebugMessages
@ -111,18 +106,6 @@ namespace DigitalData.Core.Application
serviceMessage.DebugMessages.Add(message);
return serviceMessage;
}
/// <summary>
/// Adds a debug message based on an enumeration key to the service message.
/// </summary>
/// <param name="serviceMessage">The service message to modify.</param>
/// <param name="messageKey">The enum key representing the debug message.</param>
/// <returns>The modified service message instance.</returns>
public static T WithDebugMessageKey<T>(this T serviceMessage, Enum messageKey) where T : IServiceMessage
{
var message = serviceMessage.KeyTranslator(messageKey.ToString());
return serviceMessage.WithDebugMessage(message);
}
#endregion
#region WarningMessages
@ -137,18 +120,6 @@ namespace DigitalData.Core.Application
serviceMessage.WarningMessages.Add(message);
return serviceMessage;
}
/// <summary>
/// Adds a warning message based on an enumeration key to the service message.
/// </summary>
/// <param name="serviceMessage">The service message to modify.</param>
/// <param name="messageKey">The enum key representing the warning message.</param>
/// <returns>The modified service message instance.</returns>
public static T WithWarningMessageKey<T>(this T serviceMessage, Enum messageKey) where T : IServiceMessage
{
var message = serviceMessage.KeyTranslator(messageKey.ToString());
return serviceMessage.WithWarningMessage(message);
}
#endregion
#region ErrorMessages
@ -163,18 +134,6 @@ namespace DigitalData.Core.Application
serviceMessage.ErrorMessages.Add(message);
return serviceMessage;
}
/// <summary>
/// Adds an error message based on an enumeration key to the service message.
/// </summary>
/// <param name="serviceMessage">The service message to modify.</param>
/// <param name="messageKey">The enum key representing the error message.</param>
/// <returns>The modified service message instance.</returns>
public static T WithErrorMessageKey<T>(this T serviceMessage, Enum messageKey) where T : IServiceMessage
{
var message = serviceMessage.KeyTranslator(messageKey.ToString());
return serviceMessage.WithErrorMessage(message);
}
#endregion
#region CriticalMessages
@ -189,18 +148,6 @@ namespace DigitalData.Core.Application
serviceMessage.CriticalMessages.Add(message);
return serviceMessage;
}
/// <summary>
/// Adds a critical message based on an enumeration key to the service message.
/// </summary>
/// <param name="serviceMessage">The service message to modify.</param>
/// <param name="messageKey">The enum key representing the critical message.</param>
/// <returns>The modified service message instance.</returns>
public static T WithCriticalMessageKey<T>(this T serviceMessage, Enum messageKey) where T : IServiceMessage
{
var message = serviceMessage.KeyTranslator(messageKey.ToString());
return serviceMessage.WithCriticalMessage(message);
}
#endregion
}
}

View File

@ -66,6 +66,10 @@
"target": "Package",
"version": "[7.0.0, )"
},
"Microsoft.Extensions.Localization.Abstractions": {
"target": "Package",
"version": "[7.0.16, )"
},
"Microsoft.Extensions.Logging": {
"target": "Package",
"version": "[7.0.0, )"

View File

@ -153,6 +153,19 @@
"buildTransitive/net6.0/_._": {}
}
},
"Microsoft.Extensions.Localization.Abstractions/7.0.16": {
"type": "package",
"compile": {
"lib/net7.0/Microsoft.Extensions.Localization.Abstractions.dll": {
"related": ".xml"
}
},
"runtime": {
"lib/net7.0/Microsoft.Extensions.Localization.Abstractions.dll": {
"related": ".xml"
}
}
},
"Microsoft.Extensions.Logging/7.0.0": {
"type": "package",
"dependencies": {
@ -807,6 +820,25 @@
"useSharedDesignerContext.txt"
]
},
"Microsoft.Extensions.Localization.Abstractions/7.0.16": {
"sha512": "wUwfDVcOMRUZv+zX45Vyh/MkXpdOy7nIvRRf3n2iiYoh76M0Dr/wx8Ppxk3v9H556z2e0QwLVQqqkd+oj+CGRQ==",
"type": "package",
"path": "microsoft.extensions.localization.abstractions/7.0.16",
"files": [
".nupkg.metadata",
".signature.p7s",
"Icon.png",
"THIRD-PARTY-NOTICES.TXT",
"lib/net462/Microsoft.Extensions.Localization.Abstractions.dll",
"lib/net462/Microsoft.Extensions.Localization.Abstractions.xml",
"lib/net7.0/Microsoft.Extensions.Localization.Abstractions.dll",
"lib/net7.0/Microsoft.Extensions.Localization.Abstractions.xml",
"lib/netstandard2.0/Microsoft.Extensions.Localization.Abstractions.dll",
"lib/netstandard2.0/Microsoft.Extensions.Localization.Abstractions.xml",
"microsoft.extensions.localization.abstractions.7.0.16.nupkg.sha512",
"microsoft.extensions.localization.abstractions.nuspec"
]
},
"Microsoft.Extensions.Logging/7.0.0": {
"sha512": "Nw2muoNrOG5U5qa2ZekXwudUn2BJcD41e65zwmDHb1fQegTX66UokLWZkJRpqSSHXDOWZ5V0iqhbxOEky91atA==",
"type": "package",
@ -1496,6 +1528,7 @@
"AutoMapper >= 13.0.1",
"Microsoft.Extensions.Caching.Abstractions >= 7.0.0",
"Microsoft.Extensions.Configuration >= 7.0.0",
"Microsoft.Extensions.Localization.Abstractions >= 7.0.16",
"Microsoft.Extensions.Logging >= 7.0.0",
"System.DirectoryServices.AccountManagement >= 7.0.1",
"System.IdentityModel.Tokens.Jwt >= 7.5.1",
@ -1569,6 +1602,10 @@
"target": "Package",
"version": "[7.0.0, )"
},
"Microsoft.Extensions.Localization.Abstractions": {
"target": "Package",
"version": "[7.0.16, )"
},
"Microsoft.Extensions.Logging": {
"target": "Package",
"version": "[7.0.0, )"

View File

@ -78,13 +78,6 @@ namespace DigitalData.Core.Contracts.Application
[JsonIgnore]
ICollection<string> CriticalMessages { get; init; }
/// <summary>
/// A function that translates a message key from a string to its localized or transformed representation.
/// This property allows for custom translation logic to be applied based on the application's needs.
/// </summary>
[JsonIgnore]
public Func<string, string> KeyTranslator { get; init; }
/// <summary>
/// Adds a new message to the appropriate collection based on the message type.
/// </summary>

View File

@ -1,54 +0,0 @@
namespace DigitalData.Core.Contracts.CultureServices
{
/// <summary>
/// Defines the interface for string localization, allowing retrieval of localized strings based on keys and arguments.
/// This service facilitates internationalization by providing an easy way to manage and access localized text strings
/// within an application, supporting dynamic content translation according to the current culture settings.
/// </summary>
public interface IKeyTranslationService
{
/// <summary>
/// Retrieves the localized string associated with the specified key. This method is used
/// when no additional formatting is required for the localized string.
/// </summary>
/// <param name="key">The key identifying the localized string in the resource files.</param>
/// <returns>The localized string associated with the specified key. If the key does not exist,
/// a fallback mechanism may return the key itself or a default message indicating the missing localization.</returns>
string Translate(string key);
/// <summary>
/// Retrieves the localized string for the specified enum key. This method simplifies localization
/// by allowing direct use of enum values as keys for retrieving localized strings.
/// </summary>
/// <param name="key">The enum value used as the key for the localized string.</param>
/// <returns>The localized string associated with the specified enum key. If the corresponding string does not exist,
/// a fallback mechanism may return the enum name itself or a default message indicating the missing localization.</returns>
string Translate(Enum key) => Translate(key.ToString());
/// <summary>
/// Retrieves the formatted localized string for the specified key, using the provided arguments to format
/// the string. This method is useful for localizing strings that require dynamic content insertion, such as
/// names, dates, or numbers, which may vary in placement or format based on the culture.
/// </summary>
/// <param name="key">The key identifying the localized string in the resource files.</param>
/// <param name="arguments">An object array that contains zero or more objects to format into the localized string.
/// These objects are inserted into the localized string based on the current culture's formatting rules.</param>
/// <returns>The formatted localized string associated with the specified key. If the key does not exist,
/// a fallback mechanism may return a formatted string using the key and arguments, or a default message indicating
/// the missing localization.</returns>
string Translate(string key, params object[] arguments);
/// <summary>
/// Retrieves the formatted localized string for the specified enum key, using the provided arguments to format
/// the string. This method extends the localization capabilities to enums, facilitating the use of enums as keys
/// for retrieving formatted localized strings with dynamic content.
/// </summary>
/// <param name="key">The enum value used as the key for the localized string.</param>
/// <param name="arguments">An object array that contains zero or more objects to format into the localized string.
/// These objects are inserted into the localized string based on the current culture's formatting rules.</param>
/// <returns>The formatted localized string associated with the specified enum key. If the corresponding string does not exist,
/// a fallback mechanism may return a formatted string using the enum name and arguments, or a default message indicating
/// the missing localization.</returns>
string Translate(Enum key, params object[] arguments) => Translate(key.ToString(), arguments);
}
}

View File

@ -1,27 +1,19 @@
using DigitalData.Core.Contracts.CultureServices;
using Microsoft.Extensions.Localization;
namespace DigitalData.Common.CultureServices
{
public class KeyTranslationService : IKeyTranslationService
public class KeyTranslationService<TResouce> : IKeyTranslationService
{
//private readonly IStringLocalizer _localizer;
private readonly IStringLocalizer<TResouce> _localizer;
public KeyTranslationService(/*IStringLocalizer localizer*/)
public KeyTranslationService(IStringLocalizer<TResouce> localizer)
{
//_localizer = localizer;
_localizer = localizer;
}
public string Translate(string key)
{
//return _localizer[key];
return key;
}
public string Translate(string key, params object[] arguments)
{
//return _localizer[key, arguments];
return key;
}
public string Translate(string key, params object[] arguments) => _localizer[key, arguments];
public string Translate(Enum key, params object[] arguments) => Translate(key.ToString(), arguments);
}

View File

@ -7,8 +7,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DigitalData.Core.Contracts"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DigitalData.Core.Infrastructure", "DigitalData.Core.Infrastructure\DigitalData.Core.Infrastructure.csproj", "{A765EBEA-3D1E-4F36-869B-6D72F87FF3F6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DigitalData.Core.CultureServices", "DigitalData.Core.CultureServices\DigitalData.Core.CultureServices.csproj", "{F7F64DA1-B001-4E0F-A075-62B7BF925869}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DigitalData.Core.Application", "DigitalData.Core.Application\DigitalData.Core.Application.csproj", "{DB404CD9-CBB8-4771-AB1B-FD4FDE2C28CC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DigitalData.Core.API", "DigitalData.Core.API\DigitalData.Core.API.csproj", "{C57B2480-F632-4691-9C4C-8CC01237203C}"
@ -29,10 +27,6 @@ Global
{A765EBEA-3D1E-4F36-869B-6D72F87FF3F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A765EBEA-3D1E-4F36-869B-6D72F87FF3F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A765EBEA-3D1E-4F36-869B-6D72F87FF3F6}.Release|Any CPU.Build.0 = Release|Any CPU
{F7F64DA1-B001-4E0F-A075-62B7BF925869}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F7F64DA1-B001-4E0F-A075-62B7BF925869}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F7F64DA1-B001-4E0F-A075-62B7BF925869}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F7F64DA1-B001-4E0F-A075-62B7BF925869}.Release|Any CPU.Build.0 = Release|Any CPU
{DB404CD9-CBB8-4771-AB1B-FD4FDE2C28CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DB404CD9-CBB8-4771-AB1B-FD4FDE2C28CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DB404CD9-CBB8-4771-AB1B-FD4FDE2C28CC}.Release|Any CPU.ActiveCfg = Release|Any CPU