mirror of
https://github.com/alex289/CleanArchitecture.git
synced 2025-06-30 02:31:08 +00:00
Add initial infra
This commit is contained in:
parent
e9f66ddf0b
commit
d884b03336
@ -7,7 +7,23 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" />
|
||||
<ProjectReference Include="..\CleanArchitecture.Infrastructure\CleanArchitecture.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
68
CleanArchitecture.Api/Controllers/ApiController.cs
Normal file
68
CleanArchitecture.Api/Controllers/ApiController.cs
Normal file
@ -0,0 +1,68 @@
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using CleanArchitecture.Api.Models;
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
using CleanArchitecture.Domain.Notifications;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace CleanArchitecture.Api.Controllers;
|
||||
|
||||
public class ApiController : ControllerBase
|
||||
{
|
||||
private readonly DomainNotificationHandler _notifications;
|
||||
|
||||
protected ApiController(INotificationHandler<DomainNotification> notifications)
|
||||
{
|
||||
_notifications = (DomainNotificationHandler)notifications;
|
||||
}
|
||||
|
||||
protected new IActionResult Response(object? resultData = null)
|
||||
{
|
||||
if (!_notifications.HasNotifications())
|
||||
{
|
||||
return Ok(
|
||||
new ResponseMessage<object>
|
||||
{
|
||||
Success = true,
|
||||
Data = resultData
|
||||
});
|
||||
}
|
||||
|
||||
var message = new ResponseMessage<object>
|
||||
{
|
||||
Success = false,
|
||||
Errors = _notifications.GetNotifications().Select(n => n.Value),
|
||||
DetailedErrors = _notifications.GetNotifications().Select(n => new DetailedError
|
||||
{
|
||||
Code = n.Code,
|
||||
Data = n.Data
|
||||
})
|
||||
};
|
||||
|
||||
return new ObjectResult(message)
|
||||
{
|
||||
StatusCode = (int)GetErrorStatusCode()
|
||||
};
|
||||
}
|
||||
|
||||
protected HttpStatusCode GetStatusCode()
|
||||
{
|
||||
if (!_notifications.GetNotifications().Any())
|
||||
{
|
||||
return HttpStatusCode.OK;
|
||||
}
|
||||
|
||||
return GetErrorStatusCode();
|
||||
}
|
||||
|
||||
protected HttpStatusCode GetErrorStatusCode()
|
||||
{
|
||||
if (_notifications.GetNotifications().Any(n => n.Code == ErrorCodes.ObjectNotFound))
|
||||
{
|
||||
return HttpStatusCode.NotFound;
|
||||
}
|
||||
|
||||
return HttpStatusCode.BadRequest;
|
||||
}
|
||||
}
|
45
CleanArchitecture.Api/Controllers/UserController.cs
Normal file
45
CleanArchitecture.Api/Controllers/UserController.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using CleanArchitecture.Domain.Notifications;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace CleanArchitecture.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("[controller]")]
|
||||
public class UserController : ApiController
|
||||
{
|
||||
public UserController(NotificationHandler<DomainNotification> notifications) : base(notifications)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public string GetAllUsersAsync()
|
||||
{
|
||||
return "test";
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public string GetUserByIdAsync([FromRoute] Guid id)
|
||||
{
|
||||
return "test";
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public string CreateUserAsync()
|
||||
{
|
||||
return "test";
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public string DeleteUserAsync([FromRoute] Guid id)
|
||||
{
|
||||
return "test";
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
public string UpdateUserAsync()
|
||||
{
|
||||
return "test";
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CleanArchitecture.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("[controller]")]
|
||||
public class WeatherForecastController : ControllerBase
|
||||
{
|
||||
private readonly ILogger<WeatherForecastController> _logger;
|
||||
|
||||
public WeatherForecastController(ILogger<WeatherForecastController> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet(Name = "GetWeatherForecast")]
|
||||
public string Get()
|
||||
{
|
||||
return "test";
|
||||
}
|
||||
}
|
12
CleanArchitecture.Api/Models/DetailedError.cs
Normal file
12
CleanArchitecture.Api/Models/DetailedError.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace CleanArchitecture.Api.Models;
|
||||
|
||||
public sealed class DetailedError
|
||||
{
|
||||
[JsonPropertyName("code")]
|
||||
public string Code { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("data")]
|
||||
public object? Data { get; init; }
|
||||
}
|
20
CleanArchitecture.Api/Models/ResponseMessage.cs
Normal file
20
CleanArchitecture.Api/Models/ResponseMessage.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace CleanArchitecture.Api.Models;
|
||||
|
||||
public sealed class ResponseMessage<T>
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public IEnumerable<string>? Errors { get; init; }
|
||||
|
||||
[JsonPropertyName("detailedErrors")]
|
||||
public IEnumerable<DetailedError> DetailedErrors { get; init; } = Enumerable.Empty<DetailedError>();
|
||||
|
||||
[JsonPropertyName("data")]
|
||||
public T? Data { get; init; }
|
||||
}
|
@ -1,16 +1,28 @@
|
||||
using CleanArchitecture.Infrastructure.Database;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
|
||||
builder.Services.AddControllers();
|
||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
builder.Services.AddDbContext<ApplicationDbContext>(options =>
|
||||
{
|
||||
options.UseLazyLoadingProxies();
|
||||
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"),
|
||||
b => b.MigrationsAssembly("netgo.centralhub.TenantService.Infrastructure"));
|
||||
});
|
||||
|
||||
builder.Services.AddMediatR(cfg =>
|
||||
{
|
||||
cfg.RegisterServicesFromAssemblies(typeof(Program).Assembly);
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
@ -26,4 +38,7 @@ app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
app.Run();
|
||||
|
||||
// Needed for integration tests webapplication factory
|
||||
public partial class Program { }
|
@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2"/>
|
||||
<PackageReference Include="xunit" Version="2.4.2"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
9
CleanArchitecture.Application.Tests/UnitTest1.cs
Normal file
9
CleanArchitecture.Application.Tests/UnitTest1.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace CleanArchitecture.Application.Tests;
|
||||
|
||||
public class UnitTest1
|
||||
{
|
||||
[Fact]
|
||||
public void Test1()
|
||||
{
|
||||
}
|
||||
}
|
1
CleanArchitecture.Application.Tests/Usings.cs
Normal file
1
CleanArchitecture.Application.Tests/Usings.cs
Normal file
@ -0,0 +1 @@
|
||||
global using Xunit;
|
@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Queries" />
|
||||
<Folder Include="ViewModels" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -0,0 +1,22 @@
|
||||
using CleanArchitecture.Application.Interfaces;
|
||||
using CleanArchitecture.Application.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace CleanArchitecture.Application.Extensions;
|
||||
|
||||
public static class ServiceCollectionExtension
|
||||
{
|
||||
public static IServiceCollection AddServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IUserService, UserService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddQueryHandlers(this IServiceCollection services)
|
||||
{
|
||||
// services.AddScoped<IQueryHandler<GetUserByIdQuery, User>, GetUserByIdQueryHandler>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
6
CleanArchitecture.Application/Interfaces/IUserService.cs
Normal file
6
CleanArchitecture.Application/Interfaces/IUserService.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace CleanArchitecture.Application.Interfaces;
|
||||
|
||||
public interface IUserService
|
||||
{
|
||||
|
||||
}
|
7
CleanArchitecture.Application/Services/UserService.cs
Normal file
7
CleanArchitecture.Application/Services/UserService.cs
Normal file
@ -0,0 +1,7 @@
|
||||
using CleanArchitecture.Application.Interfaces;
|
||||
|
||||
namespace CleanArchitecture.Application.Services;
|
||||
|
||||
public sealed class UserService : IUserService
|
||||
{
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.10.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||
<PackageReference Include="Moq" Version="4.18.4" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
90
CleanArchitecture.Domain.Tests/CommandHandlerFixtureBase.cs
Normal file
90
CleanArchitecture.Domain.Tests/CommandHandlerFixtureBase.cs
Normal file
@ -0,0 +1,90 @@
|
||||
using System.Linq.Expressions;
|
||||
using CleanArchitecture.Domain.Interfaces;
|
||||
using CleanArchitecture.Domain.Notifications;
|
||||
using Moq;
|
||||
|
||||
namespace CleanArchitecture.Domain.Tests;
|
||||
|
||||
public class CommandHandlerFixtureBase
|
||||
{
|
||||
public Mock<IMediatorHandler> Bus { get; protected set; }
|
||||
public Mock<IUnitOfWork> UnitOfWork { get; protected set; }
|
||||
public Mock<DomainNotificationHandler> NotificationHandler { get; protected set; }
|
||||
|
||||
protected CommandHandlerFixtureBase()
|
||||
{
|
||||
Bus = new Mock<IMediatorHandler>();
|
||||
UnitOfWork = new Mock<IUnitOfWork>();
|
||||
NotificationHandler = new Mock<DomainNotificationHandler>();
|
||||
|
||||
UnitOfWork.Setup(unit => unit.CommitAsync()).ReturnsAsync(true);
|
||||
}
|
||||
|
||||
public CommandHandlerFixtureBase VerifyExistingNotification(string errorCode, string message)
|
||||
{
|
||||
Bus.Verify(
|
||||
bus => bus.RaiseEventAsync(
|
||||
It.Is<DomainNotification>(not => not.Code == errorCode && not.Value == message)),
|
||||
Times.Once);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandHandlerFixtureBase VerifyAnyDomainNotification()
|
||||
{
|
||||
Bus.Verify(
|
||||
bus => bus.RaiseEventAsync(It.IsAny<DomainNotification>()),
|
||||
Times.Once);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandHandlerFixtureBase VerifyNoDomainNotification()
|
||||
{
|
||||
Bus.Verify(
|
||||
bus => bus.RaiseEventAsync(It.IsAny<DomainNotification>()),
|
||||
Times.Never);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandHandlerFixtureBase VerifyNoRaisedEvent<TEvent>()
|
||||
where TEvent : DomainEvent
|
||||
{
|
||||
Bus.Verify(
|
||||
bus => bus.RaiseEventAsync(It.IsAny<TEvent>()),
|
||||
Times.Never);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandHandlerFixtureBase VerifyNoRaisedEvent<TEvent>(Expression<Func<TEvent, bool>> checkFunction)
|
||||
where TEvent : DomainEvent
|
||||
{
|
||||
Bus.Verify(bus => bus.RaiseEventAsync(It.Is(checkFunction)), Times.Never);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandHandlerFixtureBase VerifyNoCommit()
|
||||
{
|
||||
UnitOfWork.Verify(unit => unit.CommitAsync(), Times.Never);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandHandlerFixtureBase VerifyCommit()
|
||||
{
|
||||
UnitOfWork.Verify(unit => unit.CommitAsync(), Times.Once);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandHandlerFixtureBase VerifyRaisedEvent<TEvent>(Expression<Func<TEvent, bool>> checkFunction)
|
||||
where TEvent : DomainEvent
|
||||
{
|
||||
Bus.Verify(bus => bus.RaiseEventAsync(It.Is(checkFunction)), Times.Once);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
11
CleanArchitecture.Domain.Tests/UnitTest1.cs
Normal file
11
CleanArchitecture.Domain.Tests/UnitTest1.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using Xunit;
|
||||
|
||||
namespace CleanArchitecture.Domain.Tests;
|
||||
|
||||
public class UnitTest1
|
||||
{
|
||||
[Fact]
|
||||
public void Test1()
|
||||
{
|
||||
}
|
||||
}
|
71
CleanArchitecture.Domain.Tests/ValidationTestBase.cs
Normal file
71
CleanArchitecture.Domain.Tests/ValidationTestBase.cs
Normal file
@ -0,0 +1,71 @@
|
||||
using CleanArchitecture.Domain.Commands;
|
||||
using FluentAssertions;
|
||||
using FluentValidation;
|
||||
|
||||
namespace CleanArchitecture.Domain.Tests;
|
||||
|
||||
public class ValidationTestBase<TCommand, TValidation>
|
||||
where TCommand : CommandBase
|
||||
where TValidation: AbstractValidator<TCommand>
|
||||
{
|
||||
protected readonly TValidation _validation;
|
||||
|
||||
protected ValidationTestBase(TValidation validation)
|
||||
{
|
||||
_validation = validation;
|
||||
}
|
||||
|
||||
protected void ShouldBeValid(TCommand command)
|
||||
{
|
||||
var result = _validation.Validate(command);
|
||||
|
||||
result.IsValid.Should().BeTrue();
|
||||
result.Errors.Should().BeEmpty();
|
||||
}
|
||||
|
||||
protected void ShouldHaveSingleError(
|
||||
TCommand command,
|
||||
string expectedCode)
|
||||
{
|
||||
var result = _validation.Validate(command);
|
||||
|
||||
result.IsValid.Should().BeFalse();
|
||||
|
||||
result.Errors.Count.Should().Be(1);
|
||||
|
||||
result.Errors.First().ErrorCode.Should().Be(expectedCode);
|
||||
}
|
||||
|
||||
protected void ShouldHaveSingleError(
|
||||
TCommand command,
|
||||
string expectedCode,
|
||||
string expectedMessage)
|
||||
{
|
||||
var result = _validation.Validate(command);
|
||||
|
||||
result.IsValid.Should().BeFalse();
|
||||
|
||||
result.Errors.Count.Should().Be(1);
|
||||
|
||||
result.Errors.First().ErrorCode.Should().Be(expectedCode);
|
||||
result.Errors.First().ErrorMessage.Should().Be(expectedMessage);
|
||||
}
|
||||
|
||||
protected void ShouldHaveExpectedErrors(
|
||||
TCommand command,
|
||||
params KeyValuePair<string, string>[] expectedErrors)
|
||||
{
|
||||
var result = _validation.Validate(command);
|
||||
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Count.Should().Be(expectedErrors.Length);
|
||||
|
||||
foreach (var error in expectedErrors)
|
||||
{
|
||||
result.Errors
|
||||
.Count(validation => validation.ErrorCode == error.Key && validation.ErrorMessage == error.Value)
|
||||
.Should()
|
||||
.Be(1);
|
||||
}
|
||||
}
|
||||
}
|
13
CleanArchitecture.Domain/CleanArchitecture.Domain.csproj
Normal file
13
CleanArchitecture.Domain/CleanArchitecture.Domain.csproj
Normal file
@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentValidation" Version="11.5.1" />
|
||||
<PackageReference Include="MediatR" Version="12.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
22
CleanArchitecture.Domain/Commands/CommandBase.cs
Normal file
22
CleanArchitecture.Domain/Commands/CommandBase.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using FluentValidation.Results;
|
||||
using MediatR;
|
||||
|
||||
namespace CleanArchitecture.Domain.Commands;
|
||||
|
||||
public abstract class CommandBase : IRequest
|
||||
{
|
||||
public Guid AggregateId { get; }
|
||||
public string MessageType { get; }
|
||||
public DateTime Timestamp { get; }
|
||||
public ValidationResult? ValidationResult { get; protected set; }
|
||||
|
||||
protected CommandBase(Guid aggregateId)
|
||||
{
|
||||
MessageType = GetType().Name;
|
||||
Timestamp = DateTime.Now;
|
||||
AggregateId = aggregateId;
|
||||
}
|
||||
|
||||
public abstract bool IsValid();
|
||||
}
|
14
CleanArchitecture.Domain/DomainEvent.cs
Normal file
14
CleanArchitecture.Domain/DomainEvent.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using MediatR;
|
||||
|
||||
namespace CleanArchitecture.Domain;
|
||||
|
||||
public abstract class DomainEvent : INotification
|
||||
{
|
||||
protected DomainEvent(Guid aggregateId)
|
||||
{
|
||||
Timestamp = DateTime.Now;
|
||||
}
|
||||
|
||||
private DateTime Timestamp { get; }
|
||||
}
|
61
CleanArchitecture.Domain/Entities/Entity.cs
Normal file
61
CleanArchitecture.Domain/Entities/Entity.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using System;
|
||||
|
||||
namespace CleanArchitecture.Domain.Entities;
|
||||
|
||||
public abstract class Entity
|
||||
{
|
||||
protected Entity(Guid id)
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public Guid Id { get; private set; }
|
||||
public bool Deleted { get; private set; }
|
||||
|
||||
public void SetId(Guid id)
|
||||
{
|
||||
if (id == Guid.Empty)
|
||||
{
|
||||
throw new ArgumentException($"{nameof(id)} may not be empty");
|
||||
}
|
||||
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
Deleted = true;
|
||||
}
|
||||
|
||||
public void Undelete()
|
||||
{
|
||||
Deleted = false;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
var compareTo = obj as Entity;
|
||||
|
||||
if (ReferenceEquals(this, compareTo))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (compareTo is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Id == compareTo.Id;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return GetType().GetHashCode() * 907 + Id.GetHashCode();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return GetType().Name + " [Id=" + Id + "]";
|
||||
}
|
||||
}
|
75
CleanArchitecture.Domain/Entities/User.cs
Normal file
75
CleanArchitecture.Domain/Entities/User.cs
Normal file
@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace CleanArchitecture.Domain.Entities;
|
||||
|
||||
public class User : Entity
|
||||
{
|
||||
public string Email { get; private set; }
|
||||
public string GivenName { get; private set; }
|
||||
public string Surname { get; private set; }
|
||||
|
||||
public string FullName => $"{Surname}, {GivenName}";
|
||||
|
||||
public User(
|
||||
Guid id,
|
||||
string email,
|
||||
string givenName,
|
||||
string surname) : base(id)
|
||||
{
|
||||
Email = email;
|
||||
GivenName = givenName;
|
||||
Surname = surname;
|
||||
}
|
||||
|
||||
[MemberNotNull(nameof(Email))]
|
||||
public void SetEmail(string email)
|
||||
{
|
||||
if (email == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(email));
|
||||
}
|
||||
|
||||
if (email.Length > 320)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Email may not be longer than 320 characters.");
|
||||
}
|
||||
|
||||
Email = email;
|
||||
}
|
||||
|
||||
[MemberNotNull(nameof(GivenName))]
|
||||
public void SetGivenName(string givenName)
|
||||
{
|
||||
if (givenName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(givenName));
|
||||
}
|
||||
|
||||
if (givenName.Length > 100)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Given name may not be longer than 100 characters");
|
||||
}
|
||||
|
||||
GivenName = givenName;
|
||||
}
|
||||
|
||||
[MemberNotNull(nameof(Surname))]
|
||||
public void SetSurname(string surname)
|
||||
{
|
||||
if (surname == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(surname));
|
||||
}
|
||||
|
||||
if (surname.Length > 100)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Surname may not be longer than 100 characters");
|
||||
}
|
||||
|
||||
Surname = surname;
|
||||
}
|
||||
}
|
7
CleanArchitecture.Domain/Errors/ErrorCodes.cs
Normal file
7
CleanArchitecture.Domain/Errors/ErrorCodes.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace CleanArchitecture.Domain.Errors;
|
||||
|
||||
public static class ErrorCodes
|
||||
{
|
||||
public const string CommitFailed = "COMMIT_FAILED";
|
||||
public const string ObjectNotFound = "OBJECT_NOT_FOUND";
|
||||
}
|
14
CleanArchitecture.Domain/Interfaces/IMediatorHandler.cs
Normal file
14
CleanArchitecture.Domain/Interfaces/IMediatorHandler.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Domain.Commands;
|
||||
using MediatR;
|
||||
|
||||
namespace CleanArchitecture.Domain.Interfaces;
|
||||
|
||||
public interface IMediatorHandler
|
||||
{
|
||||
Task RaiseEventAsync<T>(T @event) where T : DomainEvent;
|
||||
|
||||
Task SendCommandAsync<T>(T command) where T : CommandBase;
|
||||
|
||||
Task<TResponse> QueryAsync<TResponse>(IRequest<TResponse> query);
|
||||
}
|
9
CleanArchitecture.Domain/Interfaces/IUnitOfWork.cs
Normal file
9
CleanArchitecture.Domain/Interfaces/IUnitOfWork.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CleanArchitecture.Domain.Interfaces;
|
||||
|
||||
public interface IUnitOfWork : IDisposable
|
||||
{
|
||||
public Task<bool> CommitAsync();
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Domain.Entities;
|
||||
|
||||
namespace CleanArchitecture.Domain.Interfaces.Repositories;
|
||||
|
||||
public interface IRepository<TEntity> : IDisposable where TEntity : Entity
|
||||
{
|
||||
void Add(TEntity entity);
|
||||
|
||||
void AddRange(IEnumerable<TEntity> entities);
|
||||
|
||||
IQueryable<TEntity> GetAll();
|
||||
|
||||
IQueryable<TEntity> GetAllNoTracking();
|
||||
|
||||
Task<TEntity?> GetByIdAsync(Guid id);
|
||||
|
||||
void Update(TEntity entity);
|
||||
|
||||
Task<bool> ExistsAsync(Guid id);
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Domain.Entities;
|
||||
|
||||
namespace CleanArchitecture.Domain.Interfaces.Repositories;
|
||||
|
||||
public interface IUserRepository : IRepository<User>
|
||||
{
|
||||
Task<User?> GetByEmailAsync(string email);
|
||||
}
|
26
CleanArchitecture.Domain/Notifications/DomainNotification.cs
Normal file
26
CleanArchitecture.Domain/Notifications/DomainNotification.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System;
|
||||
|
||||
namespace CleanArchitecture.Domain.Notifications;
|
||||
|
||||
public sealed class DomainNotification : DomainEvent
|
||||
{
|
||||
public string Key { get; private set; }
|
||||
public string Value { get; private set; }
|
||||
public string Code { get; private set; }
|
||||
public object? Data { get; set; }
|
||||
|
||||
public DomainNotification(
|
||||
string key,
|
||||
string value,
|
||||
string code,
|
||||
object? data = null,
|
||||
Guid? aggregateId = null)
|
||||
: base(aggregateId ?? Guid.Empty)
|
||||
{
|
||||
Key = key ?? throw new ArgumentNullException(nameof(key));
|
||||
Value = value ?? throw new ArgumentNullException(nameof(value));
|
||||
Code = code ?? throw new ArgumentNullException(nameof(code));
|
||||
|
||||
Data = data;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediatR;
|
||||
|
||||
namespace CleanArchitecture.Domain.Notifications;
|
||||
|
||||
public class DomainNotificationHandler : INotificationHandler<DomainNotification>
|
||||
{
|
||||
private readonly List<DomainNotification> _notifications;
|
||||
|
||||
public DomainNotificationHandler()
|
||||
{
|
||||
_notifications = new List<DomainNotification>();
|
||||
}
|
||||
|
||||
public virtual List<DomainNotification> GetNotifications()
|
||||
{
|
||||
return _notifications;
|
||||
}
|
||||
|
||||
public Task Handle(DomainNotification notification, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_notifications.Add(notification);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual bool HasNotifications()
|
||||
{
|
||||
return GetNotifications().Any();
|
||||
}
|
||||
|
||||
public virtual void Clear()
|
||||
{
|
||||
_notifications.Clear();
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2"/>
|
||||
<PackageReference Include="xunit" Version="2.4.2"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
9
CleanArchitecture.Infrastructure.Tests/UnitTest1.cs
Normal file
9
CleanArchitecture.Infrastructure.Tests/UnitTest1.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace CleanArchitecture.Infrastructure.Tests;
|
||||
|
||||
public class UnitTest1
|
||||
{
|
||||
[Fact]
|
||||
public void Test1()
|
||||
{
|
||||
}
|
||||
}
|
1
CleanArchitecture.Infrastructure.Tests/Usings.cs
Normal file
1
CleanArchitecture.Infrastructure.Tests/Usings.cs
Normal file
@ -0,0 +1 @@
|
||||
global using Xunit;
|
@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MediatR" Version="12.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -0,0 +1,26 @@
|
||||
using CleanArchitecture.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace CleanArchitecture.Infrastructure.Configurations;
|
||||
|
||||
public sealed class UserConfiguration : IEntityTypeConfiguration<User>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<User> builder)
|
||||
{
|
||||
builder
|
||||
.Property(user => user.Email)
|
||||
.IsRequired()
|
||||
.HasMaxLength(320);
|
||||
|
||||
builder
|
||||
.Property(user => user.GivenName)
|
||||
.IsRequired()
|
||||
.HasMaxLength(100);
|
||||
|
||||
builder
|
||||
.Property(user => user.Surname)
|
||||
.IsRequired()
|
||||
.HasMaxLength(100);
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
using CleanArchitecture.Domain.Entities;
|
||||
using CleanArchitecture.Infrastructure.Configurations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CleanArchitecture.Infrastructure.Database;
|
||||
|
||||
public sealed class ApplicationDbContext : DbContext
|
||||
{
|
||||
public DbSet<User> Users { get; set; } = null!;
|
||||
|
||||
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
builder.ApplyConfiguration(new UserConfiguration());
|
||||
}
|
||||
}
|
34
CleanArchitecture.Infrastructure/InMemoryBus.cs
Normal file
34
CleanArchitecture.Infrastructure/InMemoryBus.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Domain;
|
||||
using CleanArchitecture.Domain.Commands;
|
||||
using CleanArchitecture.Domain.Interfaces;
|
||||
using MediatR;
|
||||
|
||||
namespace CleanArchitecture.Infrastructure;
|
||||
|
||||
public sealed class InMemoryBus : IMediatorHandler
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
|
||||
public InMemoryBus(IMediator mediator)
|
||||
{
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
public Task<TResponse> QueryAsync<TResponse>(IRequest<TResponse> query)
|
||||
{
|
||||
return _mediator.Send(query);
|
||||
}
|
||||
|
||||
public async Task RaiseEventAsync<T>(T @event) where T : DomainEvent
|
||||
{
|
||||
// await _domainEventStore.SaveAsync(@event);
|
||||
|
||||
await _mediator.Publish(@event);
|
||||
}
|
||||
|
||||
public Task SendCommandAsync<T>(T command) where T : CommandBase
|
||||
{
|
||||
return _mediator.Send(command);
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Domain.Entities;
|
||||
using CleanArchitecture.Domain.Interfaces;
|
||||
using CleanArchitecture.Domain.Interfaces.Repositories;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CleanArchitecture.Infrastructure.Repositories;
|
||||
|
||||
public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : Entity
|
||||
{
|
||||
protected readonly DbContext _dbContext;
|
||||
protected readonly DbSet<TEntity> _dbSet;
|
||||
|
||||
public BaseRepository(DbContext context)
|
||||
{
|
||||
_dbContext = context;
|
||||
_dbSet = _dbContext.Set<TEntity>();
|
||||
}
|
||||
|
||||
public void Add(TEntity entity)
|
||||
{
|
||||
_dbSet.Add(entity);
|
||||
}
|
||||
|
||||
public void AddRange(IEnumerable<TEntity> entities)
|
||||
{
|
||||
_dbSet.AddRange(entities);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public virtual IQueryable<TEntity> GetAll()
|
||||
{
|
||||
return _dbSet;
|
||||
}
|
||||
|
||||
public virtual IQueryable<TEntity> GetAllNoTracking()
|
||||
{
|
||||
return _dbSet.AsNoTracking();
|
||||
}
|
||||
|
||||
public virtual async Task<TEntity?> GetByIdAsync(Guid id)
|
||||
{
|
||||
return await _dbSet.FindAsync(id);
|
||||
}
|
||||
|
||||
public int SaveChanges()
|
||||
{
|
||||
return _dbContext.SaveChanges();
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_dbContext.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Update(TEntity entity)
|
||||
{
|
||||
_dbSet.Update(entity);
|
||||
}
|
||||
|
||||
public Task<bool> ExistsAsync(Guid id)
|
||||
{
|
||||
return _dbSet.AnyAsync(entity => entity.Id == id);
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Domain.Entities;
|
||||
using CleanArchitecture.Domain.Interfaces.Repositories;
|
||||
using CleanArchitecture.Infrastructure.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CleanArchitecture.Infrastructure.Repositories;
|
||||
|
||||
public sealed class UserRepository : BaseRepository<User>, IUserRepository
|
||||
{
|
||||
public UserRepository(ApplicationDbContext context) : base(context)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<User?> GetByEmailAsync(string email)
|
||||
{
|
||||
return await _dbSet.SingleOrDefaultAsync(user => user.Email == email);
|
||||
}
|
||||
}
|
47
CleanArchitecture.Infrastructure/UnitOfWork.cs
Normal file
47
CleanArchitecture.Infrastructure/UnitOfWork.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Domain.Interfaces;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CleanArchitecture.Infrastructure;
|
||||
|
||||
public class UnitOfWork<TContext> : IUnitOfWork where TContext : DbContext
|
||||
{
|
||||
private readonly TContext _context;
|
||||
private readonly ILogger<UnitOfWork<TContext>> _logger;
|
||||
|
||||
public UnitOfWork(TContext context, ILogger<UnitOfWork<TContext>> logger)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<bool> CommitAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
catch (DbUpdateException dbUpdateException)
|
||||
{
|
||||
_logger.LogError(dbUpdateException, "An error occured during commiting changes");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_context.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2"/>
|
||||
<PackageReference Include="xunit" Version="2.4.2"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
9
CleanArchitecture.IntegrationTests/UnitTest1.cs
Normal file
9
CleanArchitecture.IntegrationTests/UnitTest1.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace CleanArchitecture.IntegrationTests;
|
||||
|
||||
public class UnitTest1
|
||||
{
|
||||
[Fact]
|
||||
public void Test1()
|
||||
{
|
||||
}
|
||||
}
|
1
CleanArchitecture.IntegrationTests/Usings.cs
Normal file
1
CleanArchitecture.IntegrationTests/Usings.cs
Normal file
@ -0,0 +1 @@
|
||||
global using Xunit;
|
@ -2,6 +2,20 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CleanArchitecture.Api", "CleanArchitecture.Api\CleanArchitecture.Api.csproj", "{CD720672-0ED9-4FDD-AD69-A416CB394318}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CleanArchitecture.Application", "CleanArchitecture.Application\CleanArchitecture.Application.csproj", "{859B50AF-9C8D-4489-B64A-EEBDF756A012}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CleanArchitecture.Domain", "CleanArchitecture.Domain\CleanArchitecture.Domain.csproj", "{12C5BEEF-9BFD-450A-8627-6205702CA32B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CleanArchitecture.Infrastructure", "CleanArchitecture.Infrastructure\CleanArchitecture.Infrastructure.csproj", "{B6D046D8-D84A-4B7E-B05B-310B85EC8F1C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CleanArchitecture.Application.Tests", "CleanArchitecture.Application.Tests\CleanArchitecture.Application.Tests.csproj", "{6794B922-2AFD-4187-944D-7984B9973259}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CleanArchitecture.Domain.Tests", "CleanArchitecture.Domain.Tests\CleanArchitecture.Domain.Tests.csproj", "{E1F25916-EBBE-4CBD-99A2-1EB2F604D55C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CleanArchitecture.Infrastructure.Tests", "CleanArchitecture.Infrastructure.Tests\CleanArchitecture.Infrastructure.Tests.csproj", "{EC8122EB-C5E0-452F-B1CE-DA47DAEBC8F2}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CleanArchitecture.IntegrationTests", "CleanArchitecture.IntegrationTests\CleanArchitecture.IntegrationTests.csproj", "{39732BD4-909F-410C-8737-1F9FE3E269A7}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -12,5 +26,33 @@ Global
|
||||
{CD720672-0ED9-4FDD-AD69-A416CB394318}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CD720672-0ED9-4FDD-AD69-A416CB394318}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CD720672-0ED9-4FDD-AD69-A416CB394318}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{859B50AF-9C8D-4489-B64A-EEBDF756A012}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{859B50AF-9C8D-4489-B64A-EEBDF756A012}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{859B50AF-9C8D-4489-B64A-EEBDF756A012}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{859B50AF-9C8D-4489-B64A-EEBDF756A012}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{12C5BEEF-9BFD-450A-8627-6205702CA32B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{12C5BEEF-9BFD-450A-8627-6205702CA32B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{12C5BEEF-9BFD-450A-8627-6205702CA32B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{12C5BEEF-9BFD-450A-8627-6205702CA32B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B6D046D8-D84A-4B7E-B05B-310B85EC8F1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B6D046D8-D84A-4B7E-B05B-310B85EC8F1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B6D046D8-D84A-4B7E-B05B-310B85EC8F1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B6D046D8-D84A-4B7E-B05B-310B85EC8F1C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6794B922-2AFD-4187-944D-7984B9973259}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6794B922-2AFD-4187-944D-7984B9973259}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6794B922-2AFD-4187-944D-7984B9973259}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6794B922-2AFD-4187-944D-7984B9973259}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E1F25916-EBBE-4CBD-99A2-1EB2F604D55C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E1F25916-EBBE-4CBD-99A2-1EB2F604D55C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E1F25916-EBBE-4CBD-99A2-1EB2F604D55C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E1F25916-EBBE-4CBD-99A2-1EB2F604D55C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EC8122EB-C5E0-452F-B1CE-DA47DAEBC8F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EC8122EB-C5E0-452F-B1CE-DA47DAEBC8F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EC8122EB-C5E0-452F-B1CE-DA47DAEBC8F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EC8122EB-C5E0-452F-B1CE-DA47DAEBC8F2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{39732BD4-909F-410C-8737-1F9FE3E269A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{39732BD4-909F-410C-8737-1F9FE3E269A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{39732BD4-909F-410C-8737-1F9FE3E269A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{39732BD4-909F-410C-8737-1F9FE3E269A7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
Loading…
Reference in New Issue
Block a user