Compare commits
43 Commits
51c1f408d7
...
feat/Infra
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb9449d701 | ||
|
|
72f735272f | ||
|
|
304f5b7b4c | ||
|
|
0238310290 | ||
|
|
3ac0501231 | ||
|
|
db8a560805 | ||
|
|
e67361bfe1 | ||
|
|
91594e80bf | ||
|
|
8d98159ba8 | ||
|
|
f1f5b9e16d | ||
|
|
3955dede16 | ||
|
|
65e834784a | ||
|
|
3c1bbc1151 | ||
|
|
5465fe5b49 | ||
|
|
85787e7054 | ||
|
|
c955220310 | ||
|
|
7d2098092a | ||
|
|
e3b9d2971b | ||
|
|
3a604ede88 | ||
|
|
476c86ff0a | ||
|
|
9376fcff86 | ||
|
|
06df97597e | ||
|
|
266d03e0a1 | ||
|
|
e752c6f6ab | ||
|
|
561a751de4 | ||
|
|
35050d65a8 | ||
|
|
cf9041980d | ||
|
|
cf2ee73ca1 | ||
|
|
52f6dc161e | ||
|
|
7670898e24 | ||
|
|
f5b202c325 | ||
|
|
fffbdf752f | ||
|
|
6916e169b1 | ||
|
|
0c529b199b | ||
|
|
5427a9722d | ||
|
|
bccfae59cd | ||
|
|
4c55ecb427 | ||
|
|
a7e4291e42 | ||
|
|
352b59dfdf | ||
|
|
1b793e2b75 | ||
|
|
72603f836c | ||
|
|
162da9a16c | ||
|
|
c0524cbca2 |
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net7.0;net8.0</TargetFrameworks>
|
||||
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
|
||||
<IsPackable>true</IsPackable>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<PackageId>DigitalData.Core.Abstractions.Security</PackageId>
|
||||
<Version>1.0.0</Version>
|
||||
<AssemblyVersion>1.0.0</AssemblyVersion>
|
||||
<FileVersion>1.0.0</FileVersion>
|
||||
<Version>1.0.1</Version>
|
||||
<AssemblyVersion>1.0.1</AssemblyVersion>
|
||||
<FileVersion>1.0.1</FileVersion>
|
||||
<Authors>Digital Data GmbH</Authors>
|
||||
<Company>Digital Data GmbH</Company>
|
||||
<Product>Digital Data GmbH</Product>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net7.0;net8.0</TargetFrameworks>
|
||||
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<!-- NuGet Package Metadata -->
|
||||
@@ -17,9 +17,9 @@
|
||||
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
|
||||
<PackAsTool>False</PackAsTool>
|
||||
<PackageIcon>core_icon.png</PackageIcon>
|
||||
<Version>3.4.0</Version>
|
||||
<AssemblyVersion>3.4.0</AssemblyVersion>
|
||||
<FileVersion>3.4.0</FileVersion>
|
||||
<Version>3.4.3</Version>
|
||||
<AssemblyVersion>3.4.3</AssemblyVersion>
|
||||
<FileVersion>3.4.3</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
34
DigitalData.Core.Abstractions/Infrastructure/Extensions.cs
Normal file
34
DigitalData.Core.Abstractions/Infrastructure/Extensions.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace DigitalData.Core.Abstractions.Infrastructure;
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
#region Create
|
||||
public static Task<TEntity> CreateAsync<TEntity, TDto>(this IRepository<TEntity> repository, TDto dto, CancellationToken ct = default)
|
||||
{
|
||||
var entity = repository.Mapper.Map(dto);
|
||||
return repository.CreateAsync(entity, ct);
|
||||
}
|
||||
|
||||
public static Task<IEnumerable<TEntity>> CreateAsync<TEntity, TDto>(this IRepository<TEntity> repository, IEnumerable<TDto> dtos, CancellationToken ct = default)
|
||||
{
|
||||
var entities = dtos.Select(dto => repository.Mapper.Map(dto));
|
||||
return repository.CreateAsync(entities, ct);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Read
|
||||
public static async Task<TEntity?> ReadFirstOrDefaultAsync<TEntity>(this IRepository<TEntity> repository, Expression<Func<TEntity, bool>>? expression = null)
|
||||
=> (await repository.ReadAllAsync(expression)).FirstOrDefault();
|
||||
|
||||
public static async Task<TEntity> ReadFirstAsync<TEntity>(this IRepository<TEntity> repository, Expression<Func<TEntity, bool>>? expression = null)
|
||||
=> (await repository.ReadAllAsync(expression)).First();
|
||||
|
||||
public static async Task<TEntity?> ReadSingleOrDefaultAsync<TEntity>(this IRepository<TEntity> repository, Expression<Func<TEntity, bool>>? expression = null)
|
||||
=> (await repository.ReadAllAsync(expression)).SingleOrDefault();
|
||||
|
||||
public static async Task<TEntity> ReadSingleAsync<TEntity>(this IRepository<TEntity> repository, Expression<Func<TEntity, bool>>? expression = null)
|
||||
=> (await repository.ReadAllAsync(expression)).Single();
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
namespace DigitalData.Core.Abstractions.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines methods for mapping between entities and Data Transfer Objects (DTOs).
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">The type of the entity to be mapped.</typeparam>
|
||||
public interface IEntityMapper<TEntity>
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps an entity to a DTO.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDto">The type of the DTO to map to.</typeparam>
|
||||
/// <param name="entity">The entity to be mapped.</param>
|
||||
/// <returns>The mapped DTO.</returns>
|
||||
TDto Map<TDto>(TEntity entity);
|
||||
|
||||
/// <summary>
|
||||
/// Maps an entity list to a DTO list.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDto">The type of the DTO to map to.</typeparam>
|
||||
/// <param name="entities">The entity list to be mapped.</param>
|
||||
/// <returns>The mapped DTO list.</returns>
|
||||
IEnumerable<TDto> Map<TDto>(IEnumerable<TEntity> entities);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a DTO to an entity.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDto">The type of the DTO to be mapped.</typeparam>
|
||||
/// <param name="dto">The DTO to be mapped.</param>
|
||||
/// <returns>The mapped entity.</returns>
|
||||
TEntity Map<TDto>(TDto dto);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a DTO list to an entity list.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDto">The type of the DTO to be mapped.</typeparam>
|
||||
/// <param name="dtos">The DTO list to be mapped.</param>
|
||||
/// <returns>The mapped entity list.</returns>
|
||||
IEnumerable<TEntity> Map<TDto>(IEnumerable<TDto> dtos);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a DTO to an existing entity.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDto">The type of the DTO to be mapped.</typeparam>
|
||||
/// <param name="dto">The DTO to be mapped.</param>
|
||||
/// <param name="entity">The existing entity to be updated with the mapped values.</param>
|
||||
/// <returns>The updated entity.</returns>
|
||||
TEntity Map<TDto>(TDto dto, TEntity entity);
|
||||
}
|
||||
}
|
||||
24
DigitalData.Core.Abstractions/Infrastructure/IRepository.cs
Normal file
24
DigitalData.Core.Abstractions/Infrastructure/IRepository.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace DigitalData.Core.Abstractions.Infrastructure;
|
||||
|
||||
public interface IRepository<TEntity>
|
||||
{
|
||||
public IEntityMapper<TEntity> Mapper { get; }
|
||||
|
||||
public Task<TEntity> CreateAsync(TEntity entity, CancellationToken ct = default);
|
||||
|
||||
public Task<IEnumerable<TEntity>> CreateAsync(IEnumerable<TEntity> entities, CancellationToken ct = default);
|
||||
|
||||
public Task<IEnumerable<TEntity>> ReadAllAsync(Expression<Func<TEntity, bool>>? expression = null, CancellationToken ct = default);
|
||||
|
||||
public Task<TEntity?> ReadOrDefaultAsync(Expression<Func<TEntity, bool>> expression, bool single = true, CancellationToken ct = default);
|
||||
|
||||
public Task<IEnumerable<TDto>> ReadAllAsync<TDto>(Expression<Func<TEntity, bool>>? expression = null, CancellationToken ct = default);
|
||||
|
||||
public Task<TDto?> ReadOrDefaultAsync<TDto>(Expression<Func<TEntity, bool>> expression, bool single = true, CancellationToken ct = default);
|
||||
|
||||
public Task UpdateAsync<TDto>(TDto dto, Expression<Func<TEntity, bool>> expression, CancellationToken ct = default);
|
||||
|
||||
public Task DeleteAsync(Expression<Func<TEntity, bool>> expression, CancellationToken ct = default);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net7.0;net8.0</TargetFrameworks>
|
||||
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
@@ -14,9 +14,9 @@
|
||||
<PackageIcon>core_icon.png</PackageIcon>
|
||||
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
|
||||
<PackageTags>digital data core application clean architecture</PackageTags>
|
||||
<Version>3.2.0</Version>
|
||||
<AssemblyVersion>3.2.0</AssemblyVersion>
|
||||
<FileVersion>3.2.0</FileVersion>
|
||||
<Version>3.2.1</Version>
|
||||
<AssemblyVersion>3.2.1</AssemblyVersion>
|
||||
<FileVersion>3.2.1</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -27,7 +27,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<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" />
|
||||
@@ -38,6 +37,18 @@
|
||||
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
|
||||
<PackageReference Include="AutoMapper" Version="13.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||
<PackageReference Include="AutoMapper" Version="14.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
|
||||
<PackageReference Include="AutoMapper" Version="14.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DigitalData.Core.Abstractions\DigitalData.Core.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net7.0;net8.0</TargetFrameworks>
|
||||
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Description>This package provides HTTP client extension methods for the DigitalData.Core library, offering simplified and asynchronous methods for fetching and handling HTTP responses. It includes utility methods for sending GET requests, reading response content as text or JSON, and deserializing JSON into dynamic or strongly-typed objects using Newtonsoft.Json. These extensions facilitate efficient and easy-to-read HTTP interactions in client applications.</Description>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net7.0;net8.0</TargetFrameworks>
|
||||
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Description>This package provides Data Transfer Object (DTO) implementations and related utilities. It includes generic result handling, DTO extension methods, cookie consent settings management, and AutoMapper integration for robust object mapping, all adhering to Clean Architecture principles to ensure separation of concerns and maintainability.</Description>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace DigitalData.Core.Infrastructure.AutoMapper;
|
||||
|
||||
public static class DependencyInjection
|
||||
{
|
||||
public static EntityConfigurationOptions<TEntity> UseAutoMapper<TEntity>(this EntityConfigurationOptions<TEntity> options, params Type[] typeOfDtos)
|
||||
{
|
||||
options.AddCustomMapper<EntityAutoMapper<TEntity>>(services =>
|
||||
{
|
||||
if (typeOfDtos.Length != 0)
|
||||
{
|
||||
services.AddAutoMapper(cnf =>
|
||||
{
|
||||
foreach (var typeOfDto in typeOfDtos)
|
||||
{
|
||||
cnf.CreateMap(typeof(TEntity), typeOfDto);
|
||||
cnf.CreateMap(typeOfDto, typeof(TEntity));
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
return options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<PackageId>DigitalData.Core.Infrastructure.AutoMapper</PackageId>
|
||||
<Version>1.0.2</Version>
|
||||
<Authors>Digital Data GmbH</Authors>
|
||||
<Company>Digital Data GmbH</Company>
|
||||
<Product>DigitalData.Core.Infrastructure.AutoMapper</Product>
|
||||
<Description>This package provides AutoMapper support for the DigitalData.Core.Infrastructure module. It includes mapping configurations and abstractions used for object-to-object mapping within the infrastructure layer.</Description>
|
||||
<Copyright>Copyright 2024</Copyright>
|
||||
<PackageIcon>core_icon.png</PackageIcon>
|
||||
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
|
||||
<RepositoryType>digital data core abstractions clean architecture mapping</RepositoryType>
|
||||
<PackageTags>digital data core infrastructure clean architecture mapping</PackageTags>
|
||||
<AssemblyVersion>1.0.2</AssemblyVersion>
|
||||
<FileVersion>1.0.2</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\..\nuget-package-icons\core_icon.png">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DigitalData.Core.Abstractions\DigitalData.Core.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\DigitalData.Core.Infrastructure\DigitalData.Core.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,24 @@
|
||||
using AutoMapper;
|
||||
using DigitalData.Core.Abstractions.Infrastructure;
|
||||
|
||||
namespace DigitalData.Core.Infrastructure.AutoMapper;
|
||||
|
||||
public class EntityAutoMapper<TEntity> : IEntityMapper<TEntity>
|
||||
{
|
||||
private readonly IMapper _rootMapper;
|
||||
|
||||
public EntityAutoMapper(IMapper rootMapper)
|
||||
{
|
||||
_rootMapper = rootMapper;
|
||||
}
|
||||
|
||||
public TDto Map<TDto>(TEntity entity) => _rootMapper.Map<TDto>(entity);
|
||||
|
||||
public IEnumerable<TDto> Map<TDto>(IEnumerable<TEntity> entities) => _rootMapper.Map<IEnumerable<TDto>>(entities);
|
||||
|
||||
public TEntity Map<TDto>(TDto dto) => _rootMapper.Map<TEntity>(dto);
|
||||
|
||||
public IEnumerable<TEntity> Map<TDto>(IEnumerable<TDto> dtos) => _rootMapper.Map<IEnumerable<TEntity>>(dtos);
|
||||
|
||||
public TEntity Map<TDto>(TDto dto, TEntity entity) => _rootMapper.Map(dto, entity);
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using DigitalData.Core.Abstractions;
|
||||
using DigitalData.Core.Abstractions.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.DirectoryServices;
|
||||
|
||||
namespace DigitalData.Core.Infrastructure
|
||||
{
|
||||
public static class DIExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a CRUD repository for a specific entity type to the service collection.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">The entity type for which the repository is registered.</typeparam>
|
||||
/// <typeparam name="TId">The type of the entity's identifier.</typeparam>
|
||||
/// <typeparam name="TDbContext">The DbContext type used by the repository.</typeparam>
|
||||
/// <param name="services">The <see cref="IServiceCollection"/> to add the repository to.</param>
|
||||
/// <param name="configureRepository">An optional action to configure additional services for the repository.</param>
|
||||
/// <returns>The original <see cref="IServiceCollection"/> instance, allowing further configuration.</returns>
|
||||
public static IServiceCollection AddCleanCRUDRepository<TEntity, TId, TDbContext, TCRUDRepository>(this IServiceCollection services, Action<IServiceCollection>? configureRepository = null)
|
||||
where TCRUDRepository : CRUDRepository<TEntity, TId, TDbContext> where TEntity : class, IUnique<TId> where TDbContext : DbContext
|
||||
{
|
||||
services.AddScoped<ICRUDRepository<TEntity, TId>, TCRUDRepository>();
|
||||
configureRepository?.Invoke(services);
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
79
DigitalData.Core.Infrastructure/DbRepository.cs
Normal file
79
DigitalData.Core.Infrastructure/DbRepository.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using DigitalData.Core.Abstractions.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace DigitalData.Core.Infrastructure;
|
||||
|
||||
public class DbRepository<TDbContext, TEntity> : IRepository<TEntity> where TDbContext : DbContext where TEntity : class
|
||||
{
|
||||
protected internal readonly TDbContext Context;
|
||||
|
||||
protected internal readonly DbSet<TEntity> Entities;
|
||||
|
||||
public IEntityMapper<TEntity> Mapper { get; }
|
||||
|
||||
public DbRepository(TDbContext context, Func<TDbContext, DbSet<TEntity>> queryFactory, IEntityMapper<TEntity> mapper)
|
||||
{
|
||||
Context = context;
|
||||
Entities = queryFactory(context);
|
||||
Mapper = mapper;
|
||||
}
|
||||
|
||||
public virtual async Task<TEntity> CreateAsync(TEntity entity, CancellationToken ct = default)
|
||||
{
|
||||
Entities.Add(entity);
|
||||
await Context.SaveChangesAsync(ct);
|
||||
return entity;
|
||||
}
|
||||
|
||||
public virtual async Task<IEnumerable<TEntity>> CreateAsync(IEnumerable<TEntity> entities, CancellationToken ct = default)
|
||||
{
|
||||
Entities.AddRange(entities);
|
||||
await Context.SaveChangesAsync(ct);
|
||||
return entities;
|
||||
}
|
||||
|
||||
public virtual async Task<IEnumerable<TEntity>> ReadAllAsync(Expression<Func<TEntity, bool>>? expression = null, CancellationToken ct = default)
|
||||
=> expression is null
|
||||
? await Entities.AsNoTracking().ToListAsync(ct)
|
||||
: await Entities.AsNoTracking().Where(expression).ToListAsync(ct);
|
||||
|
||||
public virtual async Task<TEntity?> ReadOrDefaultAsync(Expression<Func<TEntity, bool>> expression, bool single = true, CancellationToken ct = default)
|
||||
=> single
|
||||
? await Entities.AsNoTracking().Where(expression).SingleOrDefaultAsync(ct)
|
||||
: await Entities.AsNoTracking().Where(expression).FirstOrDefaultAsync(ct);
|
||||
|
||||
public virtual async Task<IEnumerable<TDto>> ReadAllAsync<TDto>(Expression<Func<TEntity, bool>>? expression = null, CancellationToken ct = default)
|
||||
=> Mapper.Map<TDto>(await ReadAllAsync(expression, ct));
|
||||
|
||||
public virtual async Task<TDto?> ReadOrDefaultAsync<TDto>(Expression<Func<TEntity, bool>> expression, bool single = true, CancellationToken ct = default)
|
||||
{
|
||||
var entity = await ReadOrDefaultAsync(expression, single, ct);
|
||||
return entity is null ? default : Mapper.Map<TDto>(entity);
|
||||
}
|
||||
|
||||
public virtual async Task UpdateAsync<TDto>(TDto dto, Expression<Func<TEntity, bool>> expression, CancellationToken ct = default)
|
||||
{
|
||||
var entities = await Entities.Where(expression).ToListAsync(ct);
|
||||
|
||||
for (int i = entities.Count - 1; i >= 0; i--)
|
||||
{
|
||||
Mapper.Map(dto, entities[i]);
|
||||
Entities.Update(entities[i]);
|
||||
}
|
||||
|
||||
await Context.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
public virtual async Task DeleteAsync(Expression<Func<TEntity, bool>> expression, CancellationToken ct = default)
|
||||
{
|
||||
var entities = await Entities.Where(expression).ToListAsync(ct);
|
||||
|
||||
for (int i = entities.Count - 1; i >= 0; i--)
|
||||
{
|
||||
Entities.Remove(entities[i]);
|
||||
}
|
||||
|
||||
await Context.SaveChangesAsync(ct);
|
||||
}
|
||||
}
|
||||
19
DigitalData.Core.Infrastructure/DependencyInjection.cs
Normal file
19
DigitalData.Core.Infrastructure/DependencyInjection.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using DigitalData.Core.Abstractions.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace DigitalData.Core.Infrastructure;
|
||||
|
||||
public static class DependencyInjection
|
||||
{
|
||||
public static EntityConfigurationOptions<TEntity> AddDbRepository<TDbContext, TEntity>(this IServiceCollection services, Func<TDbContext, DbSet<TEntity>> queryFactory)
|
||||
where TDbContext : DbContext
|
||||
where TEntity : class
|
||||
{
|
||||
services
|
||||
.AddScoped<IRepository<TEntity>, DbRepository<TDbContext, TEntity>>()
|
||||
.AddSingleton(queryFactory);
|
||||
|
||||
return new EntityConfigurationOptions<TEntity>(services);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net7.0;net8.0</TargetFrameworks>
|
||||
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<PackageId>DigitalData.Core.Infrastructure</PackageId>
|
||||
<Version>2.0.0.0</Version>
|
||||
<Version>2.0.4</Version>
|
||||
<Authors>Digital Data GmbH</Authors>
|
||||
<Company>Digital Data GmbH</Company>
|
||||
<Product>DigitalData.Core.Infrastructure</Product>
|
||||
@@ -16,6 +16,8 @@
|
||||
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
|
||||
<RepositoryType>digital data core abstractions clean architecture</RepositoryType>
|
||||
<PackageTags>digital data core infrastructure clean architecture</PackageTags>
|
||||
<AssemblyVersion>2.0.4</AssemblyVersion>
|
||||
<FileVersion>2.0.4</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -25,8 +27,16 @@
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.16" />
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.20" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.15" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
using DigitalData.Core.Abstractions.Infrastructure;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace DigitalData.Core.Infrastructure;
|
||||
|
||||
public class EntityConfigurationOptions<TEntity>
|
||||
{
|
||||
private readonly IServiceCollection _services;
|
||||
|
||||
public EntityConfigurationOptions(IServiceCollection services)
|
||||
{
|
||||
_services = services;
|
||||
}
|
||||
|
||||
public EntityConfigurationOptions<TEntity> AddCustomMapper<TEntityMapper>(Action<IServiceCollection> configure, Func<IServiceProvider, TEntityMapper>? factory = null)
|
||||
where TEntityMapper : class, IEntityMapper<TEntity>
|
||||
{
|
||||
configure(_services);
|
||||
|
||||
if (factory is null)
|
||||
_services.AddSingleton<IEntityMapper<TEntity>, TEntityMapper>();
|
||||
else
|
||||
_services.AddSingleton(factory);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net7.0;net8.0</TargetFrameworks>
|
||||
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Bogus" Version="35.6.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="7.0.16" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||
<PackageReference Include="Moq" Version="4.20.70" />
|
||||
@@ -25,8 +26,24 @@
|
||||
<ProjectReference Include="..\DigitalData.Core.Application\DigitalData.Core.Application.csproj" />
|
||||
<ProjectReference Include="..\DigitalData.Core.Client\DigitalData.Core.Client.csproj" />
|
||||
<ProjectReference Include="..\DigitalData.Core.DTO\DigitalData.Core.DTO.csproj" />
|
||||
<ProjectReference Include="..\DigitalData.Core.Infrastructure.AutoMapper\DigitalData.Core.Infrastructure.AutoMapper.csproj" />
|
||||
<ProjectReference Include="..\DigitalData.Core.Infrastructure\DigitalData.Core.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\DigitalData.Core.Security\DigitalData.Core.Security.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.20" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.20" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.15" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.15" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
113
DigitalData.Core.Tests/Infrastructure/DbRepositoryTests.cs
Normal file
113
DigitalData.Core.Tests/Infrastructure/DbRepositoryTests.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
namespace DigitalData.Core.Tests.Infrastructure;
|
||||
|
||||
using DigitalData.Core.Infrastructure;
|
||||
using DigitalData.Core.Tests.Mock;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System.Reflection;
|
||||
using DigitalData.Core.Abstractions.Infrastructure;
|
||||
using DigitalData.Core.Infrastructure.AutoMapper;
|
||||
|
||||
public class DbRepositoryTests
|
||||
{
|
||||
private IHost _host;
|
||||
|
||||
private IRepository<User> _userRepo;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
var builder = Host.CreateApplicationBuilder();
|
||||
|
||||
builder.Services.AddDbContext<MockDbContext>(opt => opt.UseInMemoryDatabase("MockDB"));
|
||||
|
||||
builder.Services.AddDbRepository<MockDbContext, User>(context => context.Users).UseAutoMapper(typeof(UserCreateDto), typeof(UserReadDto), typeof(UserBase));
|
||||
|
||||
builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());
|
||||
|
||||
_host = builder.Build();
|
||||
|
||||
_userRepo = _host.Services.GetRequiredService<IRepository<User>>();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
if (_host is IDisposable disposableHost)
|
||||
disposableHost.Dispose();
|
||||
}
|
||||
|
||||
[TestCase(true, TestName = "WhenGivenMultipleUsers")]
|
||||
[TestCase(false, TestName = "WhenGivenSingleUser")]
|
||||
public void CreateAsync_ShouldNotThrow(bool multiple)
|
||||
{
|
||||
// Arrange
|
||||
var faker = Fake.CreateUserFaker();
|
||||
|
||||
// Act & Assert
|
||||
if (multiple)
|
||||
Assert.DoesNotThrowAsync(async () => await _userRepo.CreateAsync(faker.Generate(Random.Shared.Next(1, 10))));
|
||||
else
|
||||
Assert.DoesNotThrowAsync(async () => await _userRepo.CreateAsync(faker.Generate()));
|
||||
}
|
||||
|
||||
[TestCase(true, TestName = "WhenDtoUsed")]
|
||||
[TestCase(false, TestName = "WhenEntityUsed")]
|
||||
public async Task ReadAsync_ShouldReturnCreated(bool useDto)
|
||||
{
|
||||
// Act
|
||||
var createdUser = useDto
|
||||
? await _userRepo.CreateAsync(Fake.UserCreateDto)
|
||||
: await _userRepo.CreateAsync(Fake.User);
|
||||
|
||||
var readUser = await _userRepo.ReadFirstOrDefaultAsync(u => u.Id == createdUser.Id);
|
||||
|
||||
// Assert
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(readUser, Is.Not.Null);
|
||||
Assert.That(readUser, Is.EqualTo(createdUser));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ReadAsync_ShouldReturnUpdated()
|
||||
{
|
||||
// Arrange
|
||||
var createdUser = await _userRepo.CreateAsync(Fake.UserCreateDto);
|
||||
var userUpdateDto = new UserUpdateDto() { Age = 10, Email = "Bar", FirstName = "Foo" };
|
||||
|
||||
// Act
|
||||
await _userRepo.UpdateAsync(userUpdateDto, u => u.Id == createdUser!.Id);
|
||||
var upToDateUser = await _userRepo.ReadFirstOrDefaultAsync(u => u.Id == createdUser!.Id);
|
||||
|
||||
// Assert
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(upToDateUser, Is.Not.Null);
|
||||
Assert.That(upToDateUser?.FirstName, Is.EqualTo(userUpdateDto.FirstName));
|
||||
Assert.That(upToDateUser?.Email, Is.EqualTo(userUpdateDto.Email));
|
||||
Assert.That(upToDateUser?.Age, Is.EqualTo(userUpdateDto.Age));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ReadAsync_ShouldNotReturnDeleted()
|
||||
{
|
||||
// Arrange
|
||||
var createdUser = await _userRepo.CreateAsync(Fake.UserCreateDto);
|
||||
var readUser = await _userRepo.ReadFirstOrDefaultAsync(u => u.Id == createdUser.Id);
|
||||
|
||||
// Act
|
||||
await _userRepo.DeleteAsync(u => u.Id == createdUser.Id);
|
||||
var deletedUser = await _userRepo.ReadFirstOrDefaultAsync(u => u.Id == createdUser.Id);
|
||||
|
||||
// Assert
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(readUser, Is.Not.Null);
|
||||
Assert.That(deletedUser, Is.Null);
|
||||
});
|
||||
}
|
||||
}
|
||||
28
DigitalData.Core.Tests/Mock/Fake.cs
Normal file
28
DigitalData.Core.Tests/Mock/Fake.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Bogus;
|
||||
|
||||
namespace DigitalData.Core.Tests.Mock;
|
||||
|
||||
public static class Fake
|
||||
{
|
||||
public static Faker<User> CreateUserFaker(string? firstName = null, string? email = null, int? age = null) => new Faker<User>()
|
||||
.RuleFor(u => u.FirstName, f => firstName ?? f.Name.FirstName())
|
||||
.RuleFor(u => u.Email, f => email ?? f.Internet.Email())
|
||||
.RuleFor(u => u.Age, f => age ?? f.Random.Int(18, 99));
|
||||
|
||||
private static readonly Faker<User> UserFaker = CreateUserFaker();
|
||||
|
||||
public static User User => UserFaker.Generate();
|
||||
|
||||
public static List<User> Users => UserFaker.Generate(Random.Shared.Next(1, 10));
|
||||
|
||||
public static Faker<UserCreateDto> CreateUserCreateDtoFaker(string? firstName = null, string? email = null, int? age = null) => new Faker<UserCreateDto>()
|
||||
.RuleFor(u => u.FirstName, f => firstName ?? f.Name.FirstName())
|
||||
.RuleFor(u => u.Email, f => email ?? f.Internet.Email())
|
||||
.RuleFor(u => u.Age, f => age ?? f.Random.Int(18, 99));
|
||||
|
||||
private static readonly Faker<UserCreateDto> UserCreateDtoFaker = CreateUserCreateDtoFaker();
|
||||
|
||||
public static UserCreateDto UserCreateDto => UserCreateDtoFaker.Generate();
|
||||
|
||||
public static List<UserCreateDto> UserCreateDtos => UserCreateDtoFaker.Generate(Random.Shared.Next(1, 10));
|
||||
}
|
||||
16
DigitalData.Core.Tests/Mock/MappingProfile.cs
Normal file
16
DigitalData.Core.Tests/Mock/MappingProfile.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using AutoMapper;
|
||||
|
||||
namespace DigitalData.Core.Tests.Mock;
|
||||
|
||||
public class MappingProfile : Profile
|
||||
{
|
||||
public MappingProfile()
|
||||
{
|
||||
// DTO ---> Entity
|
||||
CreateMap<UserCreateDto, User>();
|
||||
CreateMap<UserUpdateDto, User>();
|
||||
|
||||
// Entity ---> DTO
|
||||
CreateMap<User, UserReadDto>();
|
||||
}
|
||||
}
|
||||
12
DigitalData.Core.Tests/Mock/MockDbContext.cs
Normal file
12
DigitalData.Core.Tests/Mock/MockDbContext.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace DigitalData.Core.Tests.Mock;
|
||||
|
||||
public class MockDbContext : DbContext
|
||||
{
|
||||
public DbSet<User> Users { get; set; }
|
||||
|
||||
public MockDbContext(DbContextOptions options) : base(options)
|
||||
{
|
||||
}
|
||||
}
|
||||
12
DigitalData.Core.Tests/Mock/User.cs
Normal file
12
DigitalData.Core.Tests/Mock/User.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace DigitalData.Core.Tests.Mock;
|
||||
|
||||
public class User : UserBase
|
||||
{
|
||||
public required int Id { get; init; }
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(Id, FirstName, Email, Age);
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> (obj is User user && user.GetHashCode() == GetHashCode())
|
||||
|| (obj is UserBase userBase && userBase.GetHashCode() == base.GetHashCode());
|
||||
}
|
||||
14
DigitalData.Core.Tests/Mock/UserBase.cs
Normal file
14
DigitalData.Core.Tests/Mock/UserBase.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace DigitalData.Core.Tests.Mock;
|
||||
|
||||
public class UserBase
|
||||
{
|
||||
public required string FirstName { get; set; }
|
||||
|
||||
public required string Email { get; set; }
|
||||
|
||||
public required int Age { get; set; }
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(FirstName, Email, Age);
|
||||
|
||||
public override bool Equals(object? obj) => obj is UserBase user && user.GetHashCode() == GetHashCode();
|
||||
}
|
||||
5
DigitalData.Core.Tests/Mock/UserCreateDto.cs
Normal file
5
DigitalData.Core.Tests/Mock/UserCreateDto.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace DigitalData.Core.Tests.Mock;
|
||||
|
||||
public class UserCreateDto : UserBase
|
||||
{
|
||||
}
|
||||
5
DigitalData.Core.Tests/Mock/UserReadDto.cs
Normal file
5
DigitalData.Core.Tests/Mock/UserReadDto.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace DigitalData.Core.Tests.Mock;
|
||||
|
||||
public class UserReadDto : UserBase
|
||||
{
|
||||
}
|
||||
5
DigitalData.Core.Tests/Mock/UserUpdateDto.cs
Normal file
5
DigitalData.Core.Tests/Mock/UserUpdateDto.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace DigitalData.Core.Tests.Mock;
|
||||
|
||||
public class UserUpdateDto : UserBase
|
||||
{
|
||||
}
|
||||
@@ -31,6 +31,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Security", "Security", "{72
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DigitalData.Core.Abstractions.Security", "DigitalData.Core.Abstractions.Security\DigitalData.Core.Abstractions.Security.csproj", "{C9266749-9504-4EA9-938F-F083357B60B7}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{EDF84A84-1E01-484E-B073-383F7139C891}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DigitalData.Core.Infrastructure.AutoMapper", "DigitalData.Core.Infrastructure.AutoMapper\DigitalData.Core.Infrastructure.AutoMapper.csproj", "{CE00E1F7-2771-4D9C-88FB-E564894E539E}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{41795B74-A757-4E93-B907-83BFF04EEE5C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -88,13 +96,31 @@ Global
|
||||
{C9266749-9504-4EA9-938F-F083357B60B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C9266749-9504-4EA9-938F-F083357B60B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C9266749-9504-4EA9-938F-F083357B60B7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CE00E1F7-2771-4D9C-88FB-E564894E539E}.Debug|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CE00E1F7-2771-4D9C-88FB-E564894E539E}.Debug|Any CPU.Build.0 = Release|Any CPU
|
||||
{CE00E1F7-2771-4D9C-88FB-E564894E539E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CE00E1F7-2771-4D9C-88FB-E564894E539E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{A765EBEA-3D1E-4F36-869B-6D72F87FF3F6} = {41795B74-A757-4E93-B907-83BFF04EEE5C}
|
||||
{DB404CD9-CBB8-4771-AB1B-FD4FDE2C28CC} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{C57B2480-F632-4691-9C4C-8CC01237203C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{B54DEF90-C30C-44EA-9875-76F1B330CBB7} = {EDF84A84-1E01-484E-B073-383F7139C891}
|
||||
{0B051A5F-BD38-47D1-BAFF-D44BA30D3FB7} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{6A80FFEC-9B83-40A7-8C78-124440B48B33} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{13E40DF1-6123-4838-9BF8-086C94E6ADF6} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{84B18026-F9A0-4366-BC69-1662D9E7342D} = {EDF84A84-1E01-484E-B073-383F7139C891}
|
||||
{E009A053-A9F4-48F2-984F-EF5C376A9B14} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{47D80C65-74A2-4EB8-96A5-D571A9108FB3} = {72CBAFBA-55CC-49C9-A484-F8F4550054CB}
|
||||
{0FA93730-8084-4907-B172-87D610323796} = {EDF84A84-1E01-484E-B073-383F7139C891}
|
||||
{9BC2DEC5-E89D-48CC-9A51-4D94496EE4A6} = {EDF84A84-1E01-484E-B073-383F7139C891}
|
||||
{72CBAFBA-55CC-49C9-A484-F8F4550054CB} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{C9266749-9504-4EA9-938F-F083357B60B7} = {72CBAFBA-55CC-49C9-A484-F8F4550054CB}
|
||||
{CE00E1F7-2771-4D9C-88FB-E564894E539E} = {41795B74-A757-4E93-B907-83BFF04EEE5C}
|
||||
{41795B74-A757-4E93-B907-83BFF04EEE5C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {8E2C3187-F848-493A-9E79-56D20DDCAC94}
|
||||
|
||||
Reference in New Issue
Block a user