feat!: создание проекта web api

создание проекта для web api, конфигурации бд и тп, была ошибка с чтением конфигурации из json, решилась
This commit is contained in:
2025-05-01 17:25:49 +04:00
parent 1854214aa9
commit fa9dbb3f60
20 changed files with 590 additions and 0 deletions

View File

@@ -14,4 +14,9 @@
<ProjectReference Include="..\BankContracts\BankContracts.csproj" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="BankWebApi" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,18 @@
using BankContracts.AdapterContracts.OperationResponses;
using BankContracts.BindingModels;
namespace BankContracts.AdapterContracts;
/// <summary>
/// контракт адаптера для клерка
/// </summary>
public interface IClerkAdapter
{
ClerkOperationResponse GetList();
ClerkOperationResponse GetElement(string data);
ClerkOperationResponse RegisterClerk(ClerkBindingModel clerkDataModel);
ClerkOperationResponse ChangeClerkInfo(ClerkBindingModel clerkDataModel);
}

View File

@@ -0,0 +1,24 @@
using BankContracts.Infrastructure;
using BankContracts.ViewModels;
namespace BankContracts.AdapterContracts.OperationResponses;
public class ClerkOperationResponse : OperationResponse
{
public static ClerkOperationResponse OK(List<ClerkViewModel> data) =>
OK<ClerkOperationResponse, List<ClerkViewModel>>(data);
public static ClerkOperationResponse OK(ClerkViewModel data) =>
OK<ClerkOperationResponse, ClerkViewModel>(data);
public static ClerkOperationResponse NoContent() => NoContent<ClerkOperationResponse>();
public static ClerkOperationResponse NotFound(string message) =>
NotFound<ClerkOperationResponse>(message);
public static ClerkOperationResponse BadRequest(string message) =>
BadRequest<ClerkOperationResponse>(message);
public static ClerkOperationResponse InternalServerError(string message) =>
InternalServerError<ClerkOperationResponse>(message);
}

View File

@@ -6,4 +6,10 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.3.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,23 @@
namespace BankContracts.BindingModels;
/// <summary>
/// модель ответа от клиента для клерка
/// </summary>
public class ClerkBindingModel
{
public string? Id { get; set; }
public string? Name { get; set; }
public string? Surname { get; set; }
public string? MiddleName { get; set; }
public string? Login { get; set; }
public string? Password { get; set; }
public string? Email { get; set; }
public string? PhoneNumber { get; set; }
}

View File

@@ -0,0 +1,49 @@
using System.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace BankContracts.Infrastructure;
/// <summary>
/// класс для http ответов
/// </summary>
public class OperationResponse
{
protected HttpStatusCode StatusCode { get; set; }
protected object? Result { get; set; }
public IActionResult GetResponse(HttpRequest request, HttpResponse response)
{
ArgumentNullException.ThrowIfNull(request);
ArgumentNullException.ThrowIfNull(response);
response.StatusCode = (int)StatusCode;
if (Result is null)
{
return new StatusCodeResult((int)StatusCode);
}
return new ObjectResult(Result);
}
protected static TResult OK<TResult, TData>(TData data)
where TResult : OperationResponse, new() =>
new() { StatusCode = HttpStatusCode.OK, Result = data };
protected static TResult NoContent<TResult>()
where TResult : OperationResponse, new() => new() { StatusCode = HttpStatusCode.NoContent };
protected static TResult BadRequest<TResult>(string? errorMessage = null)
where TResult : OperationResponse, new() =>
new() { StatusCode = HttpStatusCode.BadRequest, Result = errorMessage };
protected static TResult NotFound<TResult>(string? errorMessage = null)
where TResult : OperationResponse, new() =>
new() { StatusCode = HttpStatusCode.NotFound, Result = errorMessage };
protected static TResult InternalServerError<TResult>(string? errorMessage = null)
where TResult : OperationResponse, new() =>
new() { StatusCode = HttpStatusCode.InternalServerError, Result = errorMessage };
}

View File

@@ -0,0 +1,23 @@
namespace BankContracts.ViewModels;
/// <summary>
/// модель представления для клерка
/// </summary>
public class ClerkViewModel
{
public required string Id { get; set; }
public required string Name { get; set; }
public required string Surname { get; set; }
public required string MiddleName { get; set; }
public required string Login { get; set; }
public required string Password { get; set; }
public required string Email { get; set; }
public required string PhoneNumber { get; set; }
}

View File

@@ -20,4 +20,8 @@
<InternalsVisibleTo Include="BankTests" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="BankWebApi" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,173 @@
using AutoMapper;
using BankContracts.AdapterContracts;
using BankContracts.AdapterContracts.OperationResponses;
using BankContracts.BindingModels;
using BankContracts.BusinessLogicContracts;
using BankContracts.DataModels;
using BankContracts.Exceptions;
using BankContracts.ViewModels;
namespace BankWebApi.Adapters;
public class ClerkAdapter : IClerkAdapter
{
private readonly IClerkBusinessLogicContract _clerkBusinessLogicContract;
private readonly ILogger _logger;
private readonly Mapper _mapper;
public ClerkAdapter(IClerkBusinessLogicContract clerkBusinessLogicContract, ILogger logger)
{
_clerkBusinessLogicContract = clerkBusinessLogicContract;
_logger = logger;
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ClerkBindingModel, ClerkDataModel>();
cfg.CreateMap<ClerkDataModel, ClerkViewModel>();
});
_mapper = new Mapper(config);
}
public ClerkOperationResponse GetList()
{
try
{
return ClerkOperationResponse.OK(
[
.. _clerkBusinessLogicContract
.GetAllClerks()
.Select(x => _mapper.Map<ClerkViewModel>(x)),
]
);
}
catch (NullListException)
{
_logger.LogError("NullListException");
return ClerkOperationResponse.NotFound("The list is not initialized");
}
catch (StorageException ex)
{
_logger.LogError(ex, "StorageException");
return ClerkOperationResponse.InternalServerError(
$"Error while working with data storage:{ex.InnerException!.Message}"
);
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception");
return ClerkOperationResponse.InternalServerError(ex.Message);
}
}
public ClerkOperationResponse GetElement(string data)
{
try
{
return ClerkOperationResponse.OK(
_mapper.Map<ClerkViewModel>(_clerkBusinessLogicContract.GetClerkByData(data))
);
}
catch (ArgumentNullException ex)
{
_logger.LogError(ex, "ArgumentNullException");
return ClerkOperationResponse.BadRequest("Data is empty");
}
catch (ElementNotFoundException ex)
{
_logger.LogError(ex, "ElementNotFoundException");
return ClerkOperationResponse.NotFound($"Not found element by data {data}");
}
catch (StorageException ex)
{
_logger.LogError(ex, "StorageException");
return ClerkOperationResponse.InternalServerError(
$"Error while working with data storage: {ex.InnerException!.Message}"
);
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception");
return ClerkOperationResponse.InternalServerError(ex.Message);
}
}
public ClerkOperationResponse RegisterClerk(ClerkBindingModel clerkDataModel)
{
try
{
_clerkBusinessLogicContract.InsertClerk(_mapper.Map<ClerkDataModel>(clerkDataModel));
return ClerkOperationResponse.NoContent();
}
catch (ArgumentNullException ex)
{
_logger.LogError(ex, "ArgumentNullException");
return ClerkOperationResponse.BadRequest("Data is empty");
}
catch (ValidationException ex)
{
_logger.LogError(ex, "ValidationException");
return ClerkOperationResponse.BadRequest($"Incorrect data transmitted: {ex.Message}");
}
catch (ElementExistsException ex)
{
_logger.LogError(ex, "ElementExistsException");
return ClerkOperationResponse.BadRequest(ex.Message);
}
catch (StorageException ex)
{
_logger.LogError(ex, "StorageException");
return ClerkOperationResponse.BadRequest(
$"Error while working with data storage: {ex.InnerException!.Message}"
);
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception");
return ClerkOperationResponse.InternalServerError(ex.Message);
}
}
public ClerkOperationResponse ChangeClerkInfo(ClerkBindingModel clerkDataModel)
{
try
{
_clerkBusinessLogicContract.UpdateClerk(_mapper.Map<ClerkDataModel>(clerkDataModel));
return ClerkOperationResponse.NoContent();
}
catch (ArgumentNullException ex)
{
_logger.LogError(ex, "ArgumentNullException");
return ClerkOperationResponse.BadRequest("Data is empty");
}
catch (ValidationException ex)
{
_logger.LogError(ex, "ValidationException");
return ClerkOperationResponse.BadRequest($"Incorrect data transmitted: {ex.Message}");
}
catch (ElementNotFoundException ex)
{
_logger.LogError(ex, "ElementNotFoundException");
return ClerkOperationResponse.BadRequest(
$"Not found element by Id {clerkDataModel.Id}"
);
}
catch (ElementExistsException ex)
{
_logger.LogError(ex, "ElementExistsException");
return ClerkOperationResponse.BadRequest(ex.Message);
}
catch (StorageException ex)
{
_logger.LogError(ex, "StorageException");
return ClerkOperationResponse.BadRequest(
$"Error while working with data storage: {ex.InnerException!.Message}"
);
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception");
return ClerkOperationResponse.InternalServerError(ex.Message);
}
}
}

View File

@@ -0,0 +1,16 @@
using Microsoft.IdentityModel.Tokens;
using System.Text;
namespace BankWebApi;
/// <summary>
/// настройки для авторизации
/// переделаем потом если не будет впадлу
/// </summary>
public class AuthOptions
{
public const string ISSUER = "Bank_AuthServer"; // издатель токена
public const string AUDIENCE = "Bank_AuthClient"; // потребитель токена
const string KEY = "banksuperpupersecret_secretsecretsecretkey!"; // ключ для шифрации
public static SymmetricSecurityKey GetSymmetricSecurityKey() => new(Encoding.UTF8.GetBytes(KEY));
}

View File

@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.4" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BankBusinessLogic\BankBusinessLogic.csproj" />
<ProjectReference Include="..\BankContracts\BankContracts.csproj" />
<ProjectReference Include="..\BankDatabase\BankDatabase.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,6 @@
@BankWebApi_HostAddress = http://localhost:5189
GET {{BankWebApi_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@@ -0,0 +1,39 @@
using BankContracts.AdapterContracts;
using BankContracts.BindingModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace BankWebApi.Controllers;
[Authorize]
[Route("api/[controller]")]
[ApiController]
[Produces("application/json")]
public class ClerksController(IClerkAdapter adapter) : ControllerBase
{
private readonly IClerkAdapter _adapter = adapter;
[HttpGet]
public IActionResult GetAllRecords()
{
return _adapter.GetList().GetResponse(Request, Response);
}
[HttpGet("{data}")]
public IActionResult GetRecord(string data)
{
return _adapter.GetElement(data).GetResponse(Request, Response);
}
[HttpPost]
public IActionResult Register([FromBody] ClerkBindingModel model)
{
return _adapter.RegisterClerk(model).GetResponse(Request, Response);
}
[HttpPut]
public IActionResult ChangeInfo([FromBody] ClerkBindingModel model)
{
return _adapter.ChangeClerkInfo(model).GetResponse(Request, Response);
}
}

View File

@@ -0,0 +1,14 @@
using BankContracts.Infrastructure;
namespace BankWebApi.Infrastructure;
public class ConfigurationDatabase(IConfiguration configuration) : IConfigurationDatabase
{
private readonly Lazy<DataBaseSettings> _dataBaseSettings = new(() =>
{
return configuration.GetSection("DataBaseSettings").Get<DataBaseSettings>()
?? throw new InvalidDataException("DataBaseSettings section is missing or invalid.");
});
public string ConnectionString => _dataBaseSettings.Value.ConnectionString;
}

View File

@@ -0,0 +1,6 @@
namespace BankWebApi.Infrastructure;
public class DataBaseSettings
{
public required string ConnectionString { get; set; }
}

View File

@@ -0,0 +1,93 @@
using BankBusinessLogic.Implementations;
using BankContracts.AdapterContracts;
using BankContracts.BusinessLogicContracts;
using BankContracts.Infrastructure;
using BankContracts.StorageContracts;
using BankDatabase;
using BankDatabase.Implementations;
using BankWebApi;
using BankWebApi.Adapters;
using BankWebApi.Infrastructure;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Serilog;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
using var loggerFactory = new LoggerFactory();
loggerFactory.AddSerilog(new LoggerConfiguration().ReadFrom.Configuration(builder.Configuration).CreateLogger());
builder.Services.AddSingleton(loggerFactory.CreateLogger("Any"));
builder.Services.AddAuthorization();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = AuthOptions.ISSUER,
ValidateAudience = true,
ValidAudience = AuthOptions.AUDIENCE,
ValidateLifetime = true,
IssuerSigningKey = AuthOptions.GetSymmetricSecurityKey(),
ValidateIssuerSigningKey = true,
};
});
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();
builder.Services.AddSingleton<IConfigurationDatabase, ConfigurationDatabase>();
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
builder.Services.AddTransient<IClerkBusinessLogicContract, ClerkBusinessLogicContract>();
// <20><>
builder.Services.AddTransient<BankDbContext>();
builder.Services.AddTransient<IClerkStorageContract, ClerkStorageContract>();
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
builder.Services.AddTransient<IClerkAdapter, ClerkAdapter>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
if (app.Environment.IsProduction())
{
var dbContext = app.Services.GetRequiredService<BankDbContext>();
if (dbContext.Database.CanConnect())
{
dbContext.Database.EnsureCreated();
dbContext.Database.Migrate();
}
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.Map("/login/{username}", (string username) =>
{
return new JwtSecurityTokenHandler().WriteToken(new JwtSecurityToken(
issuer: AuthOptions.ISSUER,
audience: AuthOptions.AUDIENCE,
claims: [new(ClaimTypes.Name, username)],
expires: DateTime.UtcNow.Add(TimeSpan.FromMinutes(2)),
signingCredentials: new SigningCredentials(AuthOptions.GetSymmetricSecurityKey(), SecurityAlgorithms.HmacSha256)));
});
app.MapControllers();
app.Run();

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5189",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://localhost:7204;http://localhost:5189",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,28 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"Serilog": {
"Using": [ "Serilog.Sinks.File" ],
"MinimumLevel": {
"Default": "Information"
},
"WriteTo": [
{
"Name": "File",
"Args": {
"path": "../logs/bank-.log",
"rollingInterval": "Day",
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {CorrelationId} {Level:u3} {Username} {Message:lj} {Exception} {NewLine},"
}
}
]
},
"DataBaseSettings": {
"ConnectionString": "Host=127.0.0.1;Port=5432;Database=BankTest;Username=postgres;Password=admin123;"
},
"AllowedHosts": "*"
}

View File

@@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BankDatabase", "BankDatabas
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BankTests", "BankTests\BankTests.csproj", "{7C898FC8-6BC2-41E8-9538-D42ACC263A97}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BankWebApi", "BankWebApi\BankWebApi.csproj", "{C8A3A3BB-A096-429F-A763-5465C5CB735F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -33,6 +35,10 @@ Global
{7C898FC8-6BC2-41E8-9538-D42ACC263A97}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7C898FC8-6BC2-41E8-9538-D42ACC263A97}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7C898FC8-6BC2-41E8-9538-D42ACC263A97}.Release|Any CPU.Build.0 = Release|Any CPU
{C8A3A3BB-A096-429F-A763-5465C5CB735F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C8A3A3BB-A096-429F-A763-5465C5CB735F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C8A3A3BB-A096-429F-A763-5465C5CB735F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C8A3A3BB-A096-429F-A763-5465C5CB735F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE