From 0a769f764fbdc064909b52983b58ed2af4733207 Mon Sep 17 00:00:00 2001 From: Donistr Date: Sun, 22 Feb 2026 19:31:47 +0400 Subject: [PATCH 01/15] =?UTF-8?q?feat:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20ServiceDefaults?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Extensions.cs | 127 ++++++++++++++++++ ...ResidentialBuilding.ServiceDefaults.csproj | 26 ++++ 2 files changed, 153 insertions(+) create mode 100644 ResidentialBuilding.ServiceDefaults/Extensions.cs create mode 100644 ResidentialBuilding.ServiceDefaults/ResidentialBuilding.ServiceDefaults.csproj diff --git a/ResidentialBuilding.ServiceDefaults/Extensions.cs b/ResidentialBuilding.ServiceDefaults/Extensions.cs new file mode 100644 index 0000000..5e59484 --- /dev/null +++ b/ResidentialBuilding.ServiceDefaults/Extensions.cs @@ -0,0 +1,127 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using OpenTelemetry; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace ResidentialBuilding.ServiceDefaults; + +// Adds common Aspire services: service discovery, resilience, health checks, and OpenTelemetry. +// This project should be referenced by each service project in your solution. +// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults +public static class Extensions +{ + private const string HealthEndpointPath = "/health"; + private const string AlivenessEndpointPath = "/alive"; + + public static TBuilder AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.ConfigureOpenTelemetry(); + + builder.AddDefaultHealthChecks(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); + + // Turn on service discovery by default + http.AddServiceDiscovery(); + }); + + // Uncomment the following to restrict the allowed schemes for service discovery. + // builder.Services.Configure(options => + // { + // options.AllowedSchemes = ["https"]; + // }); + + return builder; + } + + public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddRuntimeInstrumentation(); + }) + .WithTracing(tracing => + { + tracing.AddSource(builder.Environment.ApplicationName) + .AddAspNetCoreInstrumentation(tracing => + // Exclude health check requests from tracing + tracing.Filter = context => + !context.Request.Path.StartsWithSegments(HealthEndpointPath) + && !context.Request.Path.StartsWithSegments(AlivenessEndpointPath) + ) + // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package) + //.AddGrpcClientInstrumentation() + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.AddOpenTelemetry().UseOtlpExporter(); + } + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) + //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) + //{ + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + //} + + return builder; + } + + public static TBuilder AddDefaultHealthChecks(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Adding health checks endpoints to applications in non-development environments has security implications. + // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. + if (app.Environment.IsDevelopment()) + { + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks(HealthEndpointPath); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + } + + return app; + } +} diff --git a/ResidentialBuilding.ServiceDefaults/ResidentialBuilding.ServiceDefaults.csproj b/ResidentialBuilding.ServiceDefaults/ResidentialBuilding.ServiceDefaults.csproj new file mode 100644 index 0000000..051b99a --- /dev/null +++ b/ResidentialBuilding.ServiceDefaults/ResidentialBuilding.ServiceDefaults.csproj @@ -0,0 +1,26 @@ + + + + net8.0 + enable + enable + true + + + + + + + + + + + + + + + + + + + From 69e6dbafb1160f90e3d4b7fc4f722760f728a85a Mon Sep 17 00:00:00 2001 From: Donistr Date: Sun, 22 Feb 2026 19:34:23 +0400 Subject: [PATCH 02/15] =?UTF-8?q?feat:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D1=81=D0=B5=D1=80=D0=B2=D0=B8=D1=81=20Generator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ResidentialBuildingController.cs | 31 +++++++ .../DTO/ResidentialBuildingDto.cs | 57 ++++++++++++ .../Generator/ResidentialBuildingGenerator.cs | 93 +++++++++++++++++++ ResidentialBuilding.Generator/Program.cs | 46 +++++++++ .../Properties/launchSettings.json | 38 ++++++++ .../Service/IResidentialBuildingService.cs | 21 +++++ .../Service/ResidentialBuildingService.cs | 86 +++++++++++++++++ .../appsettings.Development.json | 11 +++ .../appsettings.json | 9 ++ 9 files changed, 392 insertions(+) create mode 100644 ResidentialBuilding.Generator/Controller/ResidentialBuildingController.cs create mode 100644 ResidentialBuilding.Generator/DTO/ResidentialBuildingDto.cs create mode 100644 ResidentialBuilding.Generator/Generator/ResidentialBuildingGenerator.cs create mode 100644 ResidentialBuilding.Generator/Program.cs create mode 100644 ResidentialBuilding.Generator/Properties/launchSettings.json create mode 100644 ResidentialBuilding.Generator/Service/IResidentialBuildingService.cs create mode 100644 ResidentialBuilding.Generator/Service/ResidentialBuildingService.cs create mode 100644 ResidentialBuilding.Generator/appsettings.Development.json create mode 100644 ResidentialBuilding.Generator/appsettings.json diff --git a/ResidentialBuilding.Generator/Controller/ResidentialBuildingController.cs b/ResidentialBuilding.Generator/Controller/ResidentialBuildingController.cs new file mode 100644 index 0000000..5d921d2 --- /dev/null +++ b/ResidentialBuilding.Generator/Controller/ResidentialBuildingController.cs @@ -0,0 +1,31 @@ +using Generator.DTO; +using Generator.Service; +using Microsoft.AspNetCore.Mvc; + +namespace Generator.Controller; + +/// +/// Контроллер для объектов жилого строительства. +/// +[Route("api/residential-building")] +[ApiController] +public class ResidentialBuildingController(ILogger logger, IResidentialBuildingService residentialBuildingService) : ControllerBase +{ + /// + /// Получение объекта жилого строительства по id. + /// + [HttpGet("{id:int}")] + public async Task> GetResidentialBuilding(int id) + { + if (id <= 0) + { + return BadRequest("id must be >= 0"); + } + + logger.LogInformation("Getting residential building with Id={id}.", id); + var result = await residentialBuildingService.GetByIdAsync(id); + logger.LogInformation("Residential building with Id={id} successfully received.", id); + + return Ok(result); + } +} \ No newline at end of file diff --git a/ResidentialBuilding.Generator/DTO/ResidentialBuildingDto.cs b/ResidentialBuilding.Generator/DTO/ResidentialBuildingDto.cs new file mode 100644 index 0000000..2c9eac0 --- /dev/null +++ b/ResidentialBuilding.Generator/DTO/ResidentialBuildingDto.cs @@ -0,0 +1,57 @@ +namespace Generator.DTO; + +/// +/// DTO объекта жилого строительства +/// +public class ResidentialBuildingDto +{ + /// + /// Идентификатор в системе + /// + public int Id { get; set; } + + /// + /// Адрес + /// + public string Address { get; set; } = string.Empty; + + /// + /// Тип недвижимости + /// + public string PropertyType { get; set; } = string.Empty; + + /// + /// Год постройки + /// + public int BuildYear { get; set; } + + /// + /// Общая площадь + /// + public double TotalArea { get; set; } + + /// + /// Жилая площадь + /// + public double LivingArea { get; set; } + + /// + /// Этаж + /// + public int? Floor { get; set; } + + /// + /// Этажность + /// + public int TotalFloors { get; set; } + + /// + /// Кадастровый номер + /// + public string CadastralNumber { get; set; } = string.Empty; + + /// + /// Кадастровая стоимость + /// + public decimal CadastralValue { get; set; } +} \ No newline at end of file diff --git a/ResidentialBuilding.Generator/Generator/ResidentialBuildingGenerator.cs b/ResidentialBuilding.Generator/Generator/ResidentialBuildingGenerator.cs new file mode 100644 index 0000000..1d5a534 --- /dev/null +++ b/ResidentialBuilding.Generator/Generator/ResidentialBuildingGenerator.cs @@ -0,0 +1,93 @@ +using Bogus; +using Generator.DTO; + +namespace Generator.Generator; + +/// +/// Генератор объектов жилого строительства на основе Bogus +/// +public class ResidentialBuildingGenerator(ILogger logger) +{ + private static readonly string[] _propertyTypes = + [ + "Квартира", + "ИЖС", + "Апартаменты", + "Офис" + ]; + + private const int MinBuildYear = 1900; + + private const double MinTotalArea = 10.0; + + private const double MaxTotalArea = 1000.0; + + private const double MinLivingAreaPartOfTotalArea = 0.5; + + private const double MaxLivingAreaPartOfTotalArea = 0.85; + + private const int MinTotalFloors = 1; + + private const int MaxTotalFloors = 100; + + private const double MinPricePerM2 = 35; + + private const double MaxPricePerM2 = 200; + + /// + /// Генерирует объект жилого строительства для заданного идентификатора + /// + /// Идентификатор объекта жилого строительства + /// Сгенерированный объект жилого строительства + public ResidentialBuildingDto Generate(int id) + { + logger.LogInformation("Generating Residential Building for Id={id}", id); + + var faker = new Faker("ru") + .RuleFor(x => x.Id, _ => id) + .RuleFor(x => x.Address, f => f.Address.FullAddress()) + .RuleFor(x => x.PropertyType, f => f.PickRandom(_propertyTypes)) + .RuleFor(x => x.BuildYear, f => f.Random.Int(MinBuildYear, DateTime.Today.Year)) + .RuleFor(x => x.TotalArea, f => Math.Round(f.Random.Double(MinTotalArea, MaxTotalArea), 2)) + .RuleFor(x => x.LivingArea, (f, dto) => + { + var livingAreaPartOfTotalArea = + f.Random.Double(MinLivingAreaPartOfTotalArea, MaxLivingAreaPartOfTotalArea); + return Math.Round(livingAreaPartOfTotalArea * dto.TotalArea, 2); + }) + .RuleFor(x => x.TotalFloors, f => f.Random.Int(MinTotalFloors, MaxTotalFloors)) + .RuleFor(x => x.Floor, (f, dto) => + { + if (dto.PropertyType is "ИЖС") + { + return null; + } + + return f.Random.Int(1, dto.TotalFloors); + }) + .RuleFor(x => x.CadastralNumber, f => + $"{f.Random.Int(1, 99):D2}:" + + $"{f.Random.Int(1, 99):D2}:" + + $"{f.Random.Int(1, 9999999):D7}:" + + $"{f.Random.Int(1, 9999):D4}") + .RuleFor(x => x.CadastralValue, (f, dto) => + { + var pricePerM2 = f.Random.Double(MinPricePerM2, MaxPricePerM2); + var price = dto.TotalArea * pricePerM2; + return (decimal)Math.Round(price, 2); + }); + + var generatedObject = faker.Generate(); + + logger.LogInformation( + "Residential building generated: Id={Id}, Address='{Address}', PropertyType='{PropertyType}', " + + "BuildYear={BuildYear}, TotalArea={TotalArea}, LivingArea={LivingArea}, Floor={Floor}, " + + "TotalFloors={TotalFloors}, CadastralNumber='{CadastralNumber}', CadastralValue={CadastralValue}", + generatedObject.Id, generatedObject.Address, generatedObject.PropertyType, generatedObject.BuildYear, + generatedObject.TotalArea, generatedObject.LivingArea, generatedObject.Floor, generatedObject.TotalFloors, + generatedObject.CadastralNumber, generatedObject.CadastralValue + ); + + return generatedObject; + } +} \ No newline at end of file diff --git a/ResidentialBuilding.Generator/Program.cs b/ResidentialBuilding.Generator/Program.cs new file mode 100644 index 0000000..760ae15 --- /dev/null +++ b/ResidentialBuilding.Generator/Program.cs @@ -0,0 +1,46 @@ +using Generator.Generator; +using Generator.Service; +using ResidentialBuilding.ServiceDefaults; +using Serilog; +using Serilog.Formatting.Compact; + +var builder = WebApplication.CreateBuilder(args); + +builder.Host.UseSerilog((hostingContext, services, loggerConfiguration) => +{ + loggerConfiguration + .MinimumLevel.Information() + .ReadFrom.Configuration(hostingContext.Configuration) + .ReadFrom.Services(services) + .Enrich.FromLogContext() + .WriteTo.Console( + formatter: new CompactJsonFormatter() + ); +}); + +builder.AddServiceDefaults(); +builder.AddRedisDistributedCache("residential-building-cache"); + +builder.Services.AddCors(options => +{ + options.AddPolicy("AllowLocalDev", policy => + { + policy + .AllowAnyOrigin() + .AllowAnyHeader() + .AllowAnyMethod(); + }); +}); + +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); + +builder.Services.AddControllers(); + +var app = builder.Build(); + +app.UseCors("AllowLocalDev"); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/ResidentialBuilding.Generator/Properties/launchSettings.json b/ResidentialBuilding.Generator/Properties/launchSettings.json new file mode 100644 index 0000000..ffb66c1 --- /dev/null +++ b/ResidentialBuilding.Generator/Properties/launchSettings.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:39434", + "sslPort": 44329 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5204", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7291;http://localhost:5204", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/ResidentialBuilding.Generator/Service/IResidentialBuildingService.cs b/ResidentialBuilding.Generator/Service/IResidentialBuildingService.cs new file mode 100644 index 0000000..0247670 --- /dev/null +++ b/ResidentialBuilding.Generator/Service/IResidentialBuildingService.cs @@ -0,0 +1,21 @@ +using Generator.DTO; + +namespace Generator.Service; + +/// +/// Сервис получения объектов жилого строительства по идентификатору. +/// Если удалось найти объект в кэше - возвращает его, иначе генерирует, кэширует и возвращает сгенерированный. +/// +public interface IResidentialBuildingService +{ + /// + /// Пытается найти в кэше объект с заданным идентификатором: + /// если удалось, то десериализует объект из JSON-а и возвращает; + /// если не удалось или произошла ошибка в ходе получения/десериализации, то генерирует объект, сохраняет в кэш и + /// возвращает сгенерированный. + /// + /// Идентификатор объекта жилого строительства. + /// Токен отмены. + /// DTO объекта жилого строительства. + public Task GetByIdAsync(int id, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/ResidentialBuilding.Generator/Service/ResidentialBuildingService.cs b/ResidentialBuilding.Generator/Service/ResidentialBuildingService.cs new file mode 100644 index 0000000..7e3b5fe --- /dev/null +++ b/ResidentialBuilding.Generator/Service/ResidentialBuildingService.cs @@ -0,0 +1,86 @@ +using System.Text.Json; +using Generator.DTO; +using Generator.Generator; +using Microsoft.Extensions.Caching.Distributed; + +namespace Generator.Service; + +public class ResidentialBuildingService( + ILogger logger, + ResidentialBuildingGenerator generator, + IDistributedCache cache, + IConfiguration configuration + ) : IResidentialBuildingService +{ + private const string CacheKeyPrefix = "residential-building:"; + + private const int CacheExpirationTimeMinutesDefault = 15; + + private readonly TimeSpan _cacheExpirationTimeMinutes = TimeSpan.FromMinutes(configuration.GetValue("CacheSettings:ExpirationTimeMinutes", CacheExpirationTimeMinutesDefault)); + + public async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var cacheKey = $"{CacheKeyPrefix}{id}"; + + string? jsonCached = null; + try + { + jsonCached = await cache.GetStringAsync(cacheKey, cancellationToken); + } + catch (Exception ex) + { + logger.LogWarning(ex, "Failed to read from distributed cache for key={cacheKey}. Falling back to generation.", cacheKey); + } + + if (!string.IsNullOrEmpty(jsonCached)) + { + logger.LogInformation("Cache for residential building with Id={} received.", id); + + ResidentialBuildingDto? objCached = null; + try + { + objCached = JsonSerializer.Deserialize(jsonCached); + } + catch (Exception ex) + { + logger.LogWarning(ex, "Invalid JSON in residential building cache for key {cacheKey}.", cacheKey); + } + + if (objCached is null) + { + logger.LogWarning("Cache for residential building with Id={id} returned null.", id); + } + else + { + logger.LogInformation("Cache for residential building with Id={id} is valid, returned", id); + return objCached; + } + } + + var obj = generator.Generate(id); + + try + { + await cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(obj), CreateCacheOptions(), cancellationToken); + } + catch (Exception ex) + { + logger.LogWarning(ex, "Failed to write residential building with Id={id} to cache. Still returning generated value.", id); + } + + logger.LogInformation("Generated and cached residential building with Id={id}", id); + + return obj; + } + + /// + /// Создаёт настройки кэша - задаёт время жизни кэша. + /// + private DistributedCacheEntryOptions CreateCacheOptions() + { + return new DistributedCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = _cacheExpirationTimeMinutes + }; + } +} \ No newline at end of file diff --git a/ResidentialBuilding.Generator/appsettings.Development.json b/ResidentialBuilding.Generator/appsettings.Development.json new file mode 100644 index 0000000..af36b86 --- /dev/null +++ b/ResidentialBuilding.Generator/appsettings.Development.json @@ -0,0 +1,11 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "CacheSettings": { + "ExpirationTimeMinutes": 5 + } +} diff --git a/ResidentialBuilding.Generator/appsettings.json b/ResidentialBuilding.Generator/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/ResidentialBuilding.Generator/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} From 9b66e86249b49db7e50de3c9b4d173da3962ea77 Mon Sep 17 00:00:00 2001 From: Donistr Date: Sun, 22 Feb 2026 19:37:29 +0400 Subject: [PATCH 03/15] =?UTF-8?q?fix:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B,=20=D0=BA=D0=BE?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D1=8B=D0=B5=20=D0=B7=D0=B0=D0=B1=D1=8B=D0=BB?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=BA=D0=BE=D0=BC=D0=BC=D0=B8=D1=82=D0=B8=D1=82?= =?UTF-8?q?=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CloudDevelopment.sln | 18 +++++++++++++++++ .../ResidentialBuilding.Generator.csproj | 20 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 ResidentialBuilding.Generator/ResidentialBuilding.Generator.csproj diff --git a/CloudDevelopment.sln b/CloudDevelopment.sln index cb48241..da11d6e 100644 --- a/CloudDevelopment.sln +++ b/CloudDevelopment.sln @@ -5,6 +5,12 @@ VisualStudioVersion = 17.14.36811.4 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client.Wasm", "Client.Wasm\Client.Wasm.csproj", "{AE7EEA74-2FE0-136F-D797-854FD87E022A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResidentialBuilding.Generator", "ResidentialBuilding.Generator\ResidentialBuilding.Generator.csproj", "{4C3748F7-BE7B-4C97-A656-D0D467E4BC5D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResidentialBuilding.AppHost", "ResidentialBuilding.AppHost\ResidentialBuilding.AppHost.csproj", "{248C1F9B-F012-4C7C-A458-4E2D0F918A70}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResidentialBuilding.ServiceDefaults", "ResidentialBuilding.ServiceDefaults\ResidentialBuilding.ServiceDefaults.csproj", "{3AEE6EF1-603F-411D-89C2-5CE78EBCAEBA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +21,18 @@ Global {AE7EEA74-2FE0-136F-D797-854FD87E022A}.Debug|Any CPU.Build.0 = Debug|Any CPU {AE7EEA74-2FE0-136F-D797-854FD87E022A}.Release|Any CPU.ActiveCfg = Release|Any CPU {AE7EEA74-2FE0-136F-D797-854FD87E022A}.Release|Any CPU.Build.0 = Release|Any CPU + {4C3748F7-BE7B-4C97-A656-D0D467E4BC5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C3748F7-BE7B-4C97-A656-D0D467E4BC5D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C3748F7-BE7B-4C97-A656-D0D467E4BC5D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C3748F7-BE7B-4C97-A656-D0D467E4BC5D}.Release|Any CPU.Build.0 = Release|Any CPU + {248C1F9B-F012-4C7C-A458-4E2D0F918A70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {248C1F9B-F012-4C7C-A458-4E2D0F918A70}.Debug|Any CPU.Build.0 = Debug|Any CPU + {248C1F9B-F012-4C7C-A458-4E2D0F918A70}.Release|Any CPU.ActiveCfg = Release|Any CPU + {248C1F9B-F012-4C7C-A458-4E2D0F918A70}.Release|Any CPU.Build.0 = Release|Any CPU + {3AEE6EF1-603F-411D-89C2-5CE78EBCAEBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3AEE6EF1-603F-411D-89C2-5CE78EBCAEBA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3AEE6EF1-603F-411D-89C2-5CE78EBCAEBA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3AEE6EF1-603F-411D-89C2-5CE78EBCAEBA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ResidentialBuilding.Generator/ResidentialBuilding.Generator.csproj b/ResidentialBuilding.Generator/ResidentialBuilding.Generator.csproj new file mode 100644 index 0000000..07dcd97 --- /dev/null +++ b/ResidentialBuilding.Generator/ResidentialBuilding.Generator.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + enable + enable + Generator + + + + + + + + + + + + + From 4eaaee0d48ba047f9dac95111cc748bf649a7763 Mon Sep 17 00:00:00 2001 From: Donistr Date: Sun, 22 Feb 2026 19:38:09 +0400 Subject: [PATCH 04/15] =?UTF-8?q?feat:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20AppHost?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ResidentialBuilding.AppHost/AppHost.cs | 14 +++++++++ .../Properties/launchSettings.json | 29 +++++++++++++++++++ .../ResidentialBuilding.AppHost.csproj | 23 +++++++++++++++ .../appsettings.Development.json | 8 +++++ ResidentialBuilding.AppHost/appsettings.json | 9 ++++++ 5 files changed, 83 insertions(+) create mode 100644 ResidentialBuilding.AppHost/AppHost.cs create mode 100644 ResidentialBuilding.AppHost/Properties/launchSettings.json create mode 100644 ResidentialBuilding.AppHost/ResidentialBuilding.AppHost.csproj create mode 100644 ResidentialBuilding.AppHost/appsettings.Development.json create mode 100644 ResidentialBuilding.AppHost/appsettings.json diff --git a/ResidentialBuilding.AppHost/AppHost.cs b/ResidentialBuilding.AppHost/AppHost.cs new file mode 100644 index 0000000..2ed01ed --- /dev/null +++ b/ResidentialBuilding.AppHost/AppHost.cs @@ -0,0 +1,14 @@ +var builder = DistributedApplication.CreateBuilder(args); + +var cache = builder.AddRedis("residential-building-cache") + .WithRedisInsight(containerName: "residential-building-insight"); + +var generator = builder.AddProject("generator") + .WithReference(cache, "residential-building-cache") + .WaitFor(cache); + +var client = builder.AddProject("client") + .WithReference(generator) + .WaitFor(generator); + +builder.Build().Run(); \ No newline at end of file diff --git a/ResidentialBuilding.AppHost/Properties/launchSettings.json b/ResidentialBuilding.AppHost/Properties/launchSettings.json new file mode 100644 index 0000000..330da1c --- /dev/null +++ b/ResidentialBuilding.AppHost/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17129;http://localhost:15221", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21101", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22255" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15221", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19083", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20274" + } + } + } +} \ No newline at end of file diff --git a/ResidentialBuilding.AppHost/ResidentialBuilding.AppHost.csproj b/ResidentialBuilding.AppHost/ResidentialBuilding.AppHost.csproj new file mode 100644 index 0000000..83be43b --- /dev/null +++ b/ResidentialBuilding.AppHost/ResidentialBuilding.AppHost.csproj @@ -0,0 +1,23 @@ + + + + + + Exe + net8.0 + enable + enable + ed7e1e47-dc98-4419-8424-85412466aa9b + + + + + + + + + + + + + diff --git a/ResidentialBuilding.AppHost/appsettings.Development.json b/ResidentialBuilding.AppHost/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/ResidentialBuilding.AppHost/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/ResidentialBuilding.AppHost/appsettings.json b/ResidentialBuilding.AppHost/appsettings.json new file mode 100644 index 0000000..31c092a --- /dev/null +++ b/ResidentialBuilding.AppHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} From 52dc141366e0f9dc5b5280db0373e353adab7856 Mon Sep 17 00:00:00 2001 From: Donistr Date: Sun, 22 Feb 2026 19:39:05 +0400 Subject: [PATCH 05/15] =?UTF-8?q?fix:=20=D0=BF=D0=BE=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=B8=D0=BB=20=D0=B2=20=D0=BA=D0=BB=D0=B8=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B5=20=D0=BA=D0=B0=D1=80=D1=82=D0=BE=D1=87=D0=BA=D1=83?= =?UTF-8?q?=20=D1=81=D1=82=D1=83=D0=B4=D0=B5=D0=BD=D1=82=D0=B0=20=D0=B8=20?= =?UTF-8?q?=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20=D1=87=D1=82=D0=BE=D0=B1?= =?UTF-8?q?=D1=8B=20url=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=20=D1=81=D0=BE?= =?UTF-8?q?=D0=BE=D1=82=D0=B2=D0=B5=D1=82=D1=81=D1=82=D0=B2=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BB=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5=D1=80=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Client.Wasm/Components/DataCard.razor | 2 +- Client.Wasm/Components/StudentCard.razor | 8 ++++---- Client.Wasm/wwwroot/appsettings.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Client.Wasm/Components/DataCard.razor b/Client.Wasm/Components/DataCard.razor index c646a83..307d98c 100644 --- a/Client.Wasm/Components/DataCard.razor +++ b/Client.Wasm/Components/DataCard.razor @@ -68,7 +68,7 @@ private async Task RequestNewData() { var baseAddress = Configuration["BaseAddress"] ?? throw new KeyNotFoundException("Конфигурация клиента не содержит параметра BaseAddress"); - Value = await Client.GetFromJsonAsync($"{baseAddress}?id={Id}", new JsonSerializerOptions { }); + Value = await Client.GetFromJsonAsync($"{baseAddress}/{Id}", new JsonSerializerOptions { }); StateHasChanged(); } } diff --git a/Client.Wasm/Components/StudentCard.razor b/Client.Wasm/Components/StudentCard.razor index 661f118..eae33b0 100644 --- a/Client.Wasm/Components/StudentCard.razor +++ b/Client.Wasm/Components/StudentCard.razor @@ -4,10 +4,10 @@ - Номер №X "Название лабораторной" - Вариант №Х "Название варианта" - Выполнена Фамилией Именем 65ХХ - Ссылка на форк + Номер №1 «Кэширование» + Вариант №38 "Объект жилого строительства" + Выполнена Елагиным Денисом 6513 + Ссылка на форк diff --git a/Client.Wasm/wwwroot/appsettings.json b/Client.Wasm/wwwroot/appsettings.json index d1fe7ab..47f7d32 100644 --- a/Client.Wasm/wwwroot/appsettings.json +++ b/Client.Wasm/wwwroot/appsettings.json @@ -6,5 +6,5 @@ } }, "AllowedHosts": "*", - "BaseAddress": "" + "BaseAddress": "http://localhost:5204/api/residential-building" } From 70c225b3b43fdec93342d9de209797ee6b94ac36 Mon Sep 17 00:00:00 2001 From: Donistr Date: Sun, 22 Feb 2026 19:46:44 +0400 Subject: [PATCH 06/15] =?UTF-8?q?style:=20=D0=B7=D0=B0=D0=BF=D1=83=D1=81?= =?UTF-8?q?=D1=82=D0=B8=D0=BB=20code=20cleanup=20=D0=B2=D0=BE=20=D0=B2?= =?UTF-8?q?=D1=81=D1=91=D0=BC=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 23 ++++---- Client.Wasm/App.razor | 4 +- Client.Wasm/Client.Wasm.csproj | 30 +++++------ Client.Wasm/Components/DataCard.razor | 31 +++++++---- Client.Wasm/Components/StudentCard.razor | 11 ++-- Client.Wasm/Layout/MainLayout.razor.css | 24 ++++----- Client.Wasm/Program.cs | 2 +- Client.Wasm/wwwroot/css/app.css | 50 +++++++++--------- Client.Wasm/wwwroot/index.html | 45 ++++++++-------- ResidentialBuilding.AppHost/AppHost.cs | 10 ++-- .../ResidentialBuilding.AppHost.csproj | 14 ++--- .../ResidentialBuildingController.cs | 12 +++-- .../DTO/ResidentialBuildingDto.cs | 52 +++++++++---------- .../Generator/ResidentialBuildingGenerator.cs | 40 +++++++------- ResidentialBuilding.Generator/Program.cs | 8 +-- .../ResidentialBuilding.Generator.csproj | 8 +-- .../Service/IResidentialBuildingService.cs | 12 ++--- .../Service/ResidentialBuildingService.cs | 33 +++++++----- .../Extensions.cs | 11 ++-- ...ResidentialBuilding.ServiceDefaults.csproj | 8 +-- 20 files changed, 228 insertions(+), 200 deletions(-) diff --git a/.editorconfig b/.editorconfig index 0f3bba5..a8a58a0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -43,6 +43,7 @@ csharp_style_var_for_built_in_types = true:error csharp_style_var_when_type_is_apparent = true:error csharp_style_var_elsewhere = false:silent csharp_space_around_binary_operators = before_and_after + [*.{cs,vb}] #### Naming styles #### @@ -64,31 +65,31 @@ dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case dotnet_naming_symbols.interface.applicable_kinds = interface dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.interface.required_modifiers = +dotnet_naming_symbols.interface.required_modifiers = dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.types.required_modifiers = +dotnet_naming_symbols.types.required_modifiers = dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.non_field_members.required_modifiers = +dotnet_naming_symbols.non_field_members.required_modifiers = # Naming styles dotnet_naming_style.begins_with_i.required_prefix = I -dotnet_naming_style.begins_with_i.required_suffix = -dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = dotnet_naming_style.begins_with_i.capitalization = pascal_case -dotnet_naming_style.pascal_case.required_prefix = -dotnet_naming_style.pascal_case.required_suffix = -dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case -dotnet_naming_style.pascal_case.required_prefix = -dotnet_naming_style.pascal_case.required_suffix = -dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case dotnet_style_operator_placement_when_wrapping = beginning_of_line tab_width = 4 diff --git a/Client.Wasm/App.razor b/Client.Wasm/App.razor index 6fd3ed1..a8cf817 100644 --- a/Client.Wasm/App.razor +++ b/Client.Wasm/App.razor @@ -1,7 +1,7 @@  - - + + Not found diff --git a/Client.Wasm/Client.Wasm.csproj b/Client.Wasm/Client.Wasm.csproj index 0ba9f90..292e763 100644 --- a/Client.Wasm/Client.Wasm.csproj +++ b/Client.Wasm/Client.Wasm.csproj @@ -1,21 +1,21 @@  - - net8.0 - enable - enable - + + net8.0 + enable + enable + - - - + + + - - - - - - - + + + + + + + diff --git a/Client.Wasm/Components/DataCard.razor b/Client.Wasm/Components/DataCard.razor index 307d98c..ad0ad2f 100644 --- a/Client.Wasm/Components/DataCard.razor +++ b/Client.Wasm/Components/DataCard.razor @@ -1,22 +1,25 @@ -@inject IConfiguration Configuration +@inject IConfiguration Configuration @inject HttpClient Client - Характеристики текущего объекта + + + Характеристики текущего объекта + - +
- + # Характеристика Значение - + - @if(Value is null) + @if (Value is null) { 1 @@ -30,7 +33,7 @@ foreach (var property in array) { - @(Array.IndexOf(array, property)+1) + @(Array.IndexOf(array, property) + 1) @property.Key @property.Value?.ToString() @@ -40,10 +43,13 @@
- + - Запросить новый объект + + + Запросить новый объект + @@ -54,7 +60,9 @@ - + @@ -68,7 +76,8 @@ private async Task RequestNewData() { var baseAddress = Configuration["BaseAddress"] ?? throw new KeyNotFoundException("Конфигурация клиента не содержит параметра BaseAddress"); - Value = await Client.GetFromJsonAsync($"{baseAddress}/{Id}", new JsonSerializerOptions { }); + Value = await Client.GetFromJsonAsync($"{baseAddress}/{Id}", new JsonSerializerOptions()); StateHasChanged(); } + } diff --git a/Client.Wasm/Components/StudentCard.razor b/Client.Wasm/Components/StudentCard.razor index eae33b0..0613f8c 100644 --- a/Client.Wasm/Components/StudentCard.razor +++ b/Client.Wasm/Components/StudentCard.razor @@ -1,13 +1,18 @@  - Лабораторная работа + + + Лабораторная работа + Номер №1 «Кэширование» Вариант №38 "Объект жилого строительства" - Выполнена Елагиным Денисом 6513 - Ссылка на форк + Выполнена Елагиным Денисом 6513 + + + Ссылка на форк diff --git a/Client.Wasm/Layout/MainLayout.razor.css b/Client.Wasm/Layout/MainLayout.razor.css index ecf25e5..019d27b 100644 --- a/Client.Wasm/Layout/MainLayout.razor.css +++ b/Client.Wasm/Layout/MainLayout.razor.css @@ -21,20 +21,20 @@ main { align-items: center; } - .top-row ::deep a, .top-row ::deep .btn-link { - white-space: nowrap; - margin-left: 1.5rem; - text-decoration: none; - } +.top-row ::deep a, .top-row ::deep .btn-link { + white-space: nowrap; + margin-left: 1.5rem; + text-decoration: none; +} - .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { - text-decoration: underline; - } +.top-row ::deep a:hover, .top-row ::deep .btn-link:hover { + text-decoration: underline; +} - .top-row ::deep a:first-child { - overflow: hidden; - text-overflow: ellipsis; - } +.top-row ::deep a:first-child { + overflow: hidden; + text-overflow: ellipsis; +} @media (max-width: 640.98px) { .top-row { diff --git a/Client.Wasm/Program.cs b/Client.Wasm/Program.cs index a182a92..30bab12 100644 --- a/Client.Wasm/Program.cs +++ b/Client.Wasm/Program.cs @@ -14,4 +14,4 @@ .AddBootstrapProviders() .AddFontAwesomeIcons(); -await builder.Build().RunAsync(); +await builder.Build().RunAsync(); \ No newline at end of file diff --git a/Client.Wasm/wwwroot/css/app.css b/Client.Wasm/wwwroot/css/app.css index 54a8aa3..cc1cfb1 100644 --- a/Client.Wasm/wwwroot/css/app.css +++ b/Client.Wasm/wwwroot/css/app.css @@ -17,7 +17,7 @@ a, .btn-link { } .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { - box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; + box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; } .content { @@ -48,12 +48,12 @@ a, .btn-link { z-index: 1000; } - #blazor-error-ui .dismiss { - cursor: pointer; - position: absolute; - right: 0.75rem; - top: 0.5rem; - } +#blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; +} .blazor-error-boundary { background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; @@ -61,9 +61,9 @@ a, .btn-link { color: white; } - .blazor-error-boundary::after { - content: "An error has occurred." - } +.blazor-error-boundary::after { + content: "An error has occurred." +} .loading-progress { position: relative; @@ -73,19 +73,19 @@ a, .btn-link { margin: 20vh auto 1rem auto; } - .loading-progress circle { - fill: none; - stroke: #e0e0e0; - stroke-width: 0.6rem; - transform-origin: 50% 50%; - transform: rotate(-90deg); - } +.loading-progress circle { + fill: none; + stroke: #e0e0e0; + stroke-width: 0.6rem; + transform-origin: 50% 50%; + transform: rotate(-90deg); +} - .loading-progress circle:last-child { - stroke: #1b6ec2; - stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; - transition: stroke-dasharray 0.05s ease-in-out; - } +.loading-progress circle:last-child { + stroke: #1b6ec2; + stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; + transition: stroke-dasharray 0.05s ease-in-out; +} .loading-progress-text { position: absolute; @@ -94,9 +94,9 @@ a, .btn-link { inset: calc(20vh + 3.25rem) 0 auto 0.2rem; } - .loading-progress-text:after { - content: var(--blazor-load-percentage-text, "Loading"); - } +.loading-progress-text:after { + content: var(--blazor-load-percentage-text, "Loading"); +} code { color: #c02d76; diff --git a/Client.Wasm/wwwroot/index.html b/Client.Wasm/wwwroot/index.html index b74ee32..eb7d7c1 100644 --- a/Client.Wasm/wwwroot/index.html +++ b/Client.Wasm/wwwroot/index.html @@ -2,34 +2,35 @@ - - + + Client.Wasm - - - + + + - - - - + + + + -
- - - - -
-
+
+ + + + +
+
-
- An unhandled error has occurred. - Reload - 🗙 -
- +
+ An unhandled error has occurred. + Reload + 🗙 +
+ diff --git a/ResidentialBuilding.AppHost/AppHost.cs b/ResidentialBuilding.AppHost/AppHost.cs index 2ed01ed..d7182a3 100644 --- a/ResidentialBuilding.AppHost/AppHost.cs +++ b/ResidentialBuilding.AppHost/AppHost.cs @@ -1,13 +1,15 @@ -var builder = DistributedApplication.CreateBuilder(args); +using Projects; -var cache = builder.AddRedis("residential-building-cache") +IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder(args); + +IResourceBuilder cache = builder.AddRedis("residential-building-cache") .WithRedisInsight(containerName: "residential-building-insight"); -var generator = builder.AddProject("generator") +IResourceBuilder generator = builder.AddProject("generator") .WithReference(cache, "residential-building-cache") .WaitFor(cache); -var client = builder.AddProject("client") +IResourceBuilder client = builder.AddProject("client") .WithReference(generator) .WaitFor(generator); diff --git a/ResidentialBuilding.AppHost/ResidentialBuilding.AppHost.csproj b/ResidentialBuilding.AppHost/ResidentialBuilding.AppHost.csproj index 83be43b..cf0a5e1 100644 --- a/ResidentialBuilding.AppHost/ResidentialBuilding.AppHost.csproj +++ b/ResidentialBuilding.AppHost/ResidentialBuilding.AppHost.csproj @@ -1,7 +1,7 @@ - - + + Exe net8.0 @@ -11,13 +11,13 @@ - - + + - - + + - + diff --git a/ResidentialBuilding.Generator/Controller/ResidentialBuildingController.cs b/ResidentialBuilding.Generator/Controller/ResidentialBuildingController.cs index 5d921d2..1f6ab24 100644 --- a/ResidentialBuilding.Generator/Controller/ResidentialBuildingController.cs +++ b/ResidentialBuilding.Generator/Controller/ResidentialBuildingController.cs @@ -5,14 +5,16 @@ namespace Generator.Controller; /// -/// Контроллер для объектов жилого строительства. +/// Контроллер для объектов жилого строительства. /// [Route("api/residential-building")] [ApiController] -public class ResidentialBuildingController(ILogger logger, IResidentialBuildingService residentialBuildingService) : ControllerBase +public class ResidentialBuildingController( + ILogger logger, + IResidentialBuildingService residentialBuildingService) : ControllerBase { /// - /// Получение объекта жилого строительства по id. + /// Получение объекта жилого строительства по id. /// [HttpGet("{id:int}")] public async Task> GetResidentialBuilding(int id) @@ -21,9 +23,9 @@ public async Task> GetResidentialBuilding(i { return BadRequest("id must be >= 0"); } - + logger.LogInformation("Getting residential building with Id={id}.", id); - var result = await residentialBuildingService.GetByIdAsync(id); + ResidentialBuildingDto result = await residentialBuildingService.GetByIdAsync(id); logger.LogInformation("Residential building with Id={id} successfully received.", id); return Ok(result); diff --git a/ResidentialBuilding.Generator/DTO/ResidentialBuildingDto.cs b/ResidentialBuilding.Generator/DTO/ResidentialBuildingDto.cs index 2c9eac0..f62c40a 100644 --- a/ResidentialBuilding.Generator/DTO/ResidentialBuildingDto.cs +++ b/ResidentialBuilding.Generator/DTO/ResidentialBuildingDto.cs @@ -1,57 +1,57 @@ namespace Generator.DTO; /// -/// DTO объекта жилого строительства +/// DTO объекта жилого строительства. /// public class ResidentialBuildingDto { /// - /// Идентификатор в системе + /// Идентификатор в системе. /// public int Id { get; set; } - + /// - /// Адрес + /// Адрес. /// public string Address { get; set; } = string.Empty; - + /// - /// Тип недвижимости + /// Тип недвижимости. /// public string PropertyType { get; set; } = string.Empty; - + /// - /// Год постройки + /// Год постройки. /// - public int BuildYear { get; set; } - + public int BuildYear { get; set; } + /// - /// Общая площадь + /// Общая площадь. /// - public double TotalArea { get; set; } - + public double TotalArea { get; set; } + /// - /// Жилая площадь + /// Жилая площадь. /// - public double LivingArea { get; set; } - + public double LivingArea { get; set; } + /// - /// Этаж + /// Этаж. /// public int? Floor { get; set; } - + /// - /// Этажность + /// Этажность. /// - public int TotalFloors { get; set; } - + public int TotalFloors { get; set; } + /// - /// Кадастровый номер + /// Кадастровый номер. /// - public string CadastralNumber { get; set; } = string.Empty; - + public string CadastralNumber { get; set; } = string.Empty; + /// - /// Кадастровая стоимость + /// Кадастровая стоимость. /// - public decimal CadastralValue { get; set; } + public decimal CadastralValue { get; set; } } \ No newline at end of file diff --git a/ResidentialBuilding.Generator/Generator/ResidentialBuildingGenerator.cs b/ResidentialBuilding.Generator/Generator/ResidentialBuildingGenerator.cs index 1d5a534..7f68d3b 100644 --- a/ResidentialBuilding.Generator/Generator/ResidentialBuildingGenerator.cs +++ b/ResidentialBuilding.Generator/Generator/ResidentialBuildingGenerator.cs @@ -4,18 +4,10 @@ namespace Generator.Generator; /// -/// Генератор объектов жилого строительства на основе Bogus +/// Генератор объектов жилого строительства на основе Bogus. /// public class ResidentialBuildingGenerator(ILogger logger) { - private static readonly string[] _propertyTypes = - [ - "Квартира", - "ИЖС", - "Апартаменты", - "Офис" - ]; - private const int MinBuildYear = 1900; private const double MinTotalArea = 10.0; @@ -34,16 +26,24 @@ public class ResidentialBuildingGenerator(ILogger private const double MaxPricePerM2 = 200; + private static readonly string[] _propertyTypes = + [ + "Квартира", + "ИЖС", + "Апартаменты", + "Офис" + ]; + /// - /// Генерирует объект жилого строительства для заданного идентификатора + /// Генерирует объект жилого строительства для заданного идентификатора. /// - /// Идентификатор объекта жилого строительства - /// Сгенерированный объект жилого строительства + /// Идентификатор объекта жилого строительства. + /// Сгенерированный объект жилого строительства. public ResidentialBuildingDto Generate(int id) { logger.LogInformation("Generating Residential Building for Id={id}", id); - - var faker = new Faker("ru") + + Faker? faker = new Faker("ru") .RuleFor(x => x.Id, _ => id) .RuleFor(x => x.Address, f => f.Address.FullAddress()) .RuleFor(x => x.PropertyType, f => f.PickRandom(_propertyTypes)) @@ -77,16 +77,16 @@ public ResidentialBuildingDto Generate(int id) return (decimal)Math.Round(price, 2); }); - var generatedObject = faker.Generate(); - + ResidentialBuildingDto? generatedObject = faker.Generate(); + logger.LogInformation( "Residential building generated: Id={Id}, Address='{Address}', PropertyType='{PropertyType}', " + "BuildYear={BuildYear}, TotalArea={TotalArea}, LivingArea={LivingArea}, Floor={Floor}, " + - "TotalFloors={TotalFloors}, CadastralNumber='{CadastralNumber}', CadastralValue={CadastralValue}", - generatedObject.Id, generatedObject.Address, generatedObject.PropertyType, generatedObject.BuildYear, - generatedObject.TotalArea, generatedObject.LivingArea, generatedObject.Floor, generatedObject.TotalFloors, + "TotalFloors={TotalFloors}, CadastralNumber='{CadastralNumber}', CadastralValue={CadastralValue}", + generatedObject.Id, generatedObject.Address, generatedObject.PropertyType, generatedObject.BuildYear, + generatedObject.TotalArea, generatedObject.LivingArea, generatedObject.Floor, generatedObject.TotalFloors, generatedObject.CadastralNumber, generatedObject.CadastralValue - ); + ); return generatedObject; } diff --git a/ResidentialBuilding.Generator/Program.cs b/ResidentialBuilding.Generator/Program.cs index 760ae15..4412dc5 100644 --- a/ResidentialBuilding.Generator/Program.cs +++ b/ResidentialBuilding.Generator/Program.cs @@ -4,7 +4,7 @@ using Serilog; using Serilog.Formatting.Compact; -var builder = WebApplication.CreateBuilder(args); +WebApplicationBuilder builder = WebApplication.CreateBuilder(args); builder.Host.UseSerilog((hostingContext, services, loggerConfiguration) => { @@ -14,7 +14,7 @@ .ReadFrom.Services(services) .Enrich.FromLogContext() .WriteTo.Console( - formatter: new CompactJsonFormatter() + new CompactJsonFormatter() ); }); @@ -33,11 +33,11 @@ }); builder.Services.AddSingleton(); -builder.Services.AddSingleton(); +builder.Services.AddSingleton(); builder.Services.AddControllers(); -var app = builder.Build(); +WebApplication app = builder.Build(); app.UseCors("AllowLocalDev"); diff --git a/ResidentialBuilding.Generator/ResidentialBuilding.Generator.csproj b/ResidentialBuilding.Generator/ResidentialBuilding.Generator.csproj index 07dcd97..73d8dac 100644 --- a/ResidentialBuilding.Generator/ResidentialBuilding.Generator.csproj +++ b/ResidentialBuilding.Generator/ResidentialBuilding.Generator.csproj @@ -8,13 +8,13 @@ - - - + + + - + diff --git a/ResidentialBuilding.Generator/Service/IResidentialBuildingService.cs b/ResidentialBuilding.Generator/Service/IResidentialBuildingService.cs index 0247670..ae30a58 100644 --- a/ResidentialBuilding.Generator/Service/IResidentialBuildingService.cs +++ b/ResidentialBuilding.Generator/Service/IResidentialBuildingService.cs @@ -3,16 +3,16 @@ namespace Generator.Service; /// -/// Сервис получения объектов жилого строительства по идентификатору. -/// Если удалось найти объект в кэше - возвращает его, иначе генерирует, кэширует и возвращает сгенерированный. +/// Сервис получения объектов жилого строительства по идентификатору. +/// Если удалось найти объект в кэше - возвращает его, иначе генерирует, кэширует и возвращает сгенерированный. /// public interface IResidentialBuildingService { /// - /// Пытается найти в кэше объект с заданным идентификатором: - /// если удалось, то десериализует объект из JSON-а и возвращает; - /// если не удалось или произошла ошибка в ходе получения/десериализации, то генерирует объект, сохраняет в кэш и - /// возвращает сгенерированный. + /// Пытается найти в кэше объект с заданным идентификатором: + /// если удалось, то десериализует объект из JSON-а и возвращает; + /// если не удалось или произошла ошибка в ходе получения/десериализации, то генерирует объект, сохраняет в кэш и + /// возвращает сгенерированный. /// /// Идентификатор объекта жилого строительства. /// Токен отмены. diff --git a/ResidentialBuilding.Generator/Service/ResidentialBuildingService.cs b/ResidentialBuilding.Generator/Service/ResidentialBuildingService.cs index 7e3b5fe..6a669e2 100644 --- a/ResidentialBuilding.Generator/Service/ResidentialBuildingService.cs +++ b/ResidentialBuilding.Generator/Service/ResidentialBuildingService.cs @@ -10,14 +10,16 @@ public class ResidentialBuildingService( ResidentialBuildingGenerator generator, IDistributedCache cache, IConfiguration configuration - ) : IResidentialBuildingService +) : IResidentialBuildingService { private const string CacheKeyPrefix = "residential-building:"; - + private const int CacheExpirationTimeMinutesDefault = 15; - - private readonly TimeSpan _cacheExpirationTimeMinutes = TimeSpan.FromMinutes(configuration.GetValue("CacheSettings:ExpirationTimeMinutes", CacheExpirationTimeMinutesDefault)); - + + private readonly TimeSpan _cacheExpirationTimeMinutes = + TimeSpan.FromMinutes(configuration.GetValue("CacheSettings:ExpirationTimeMinutes", + CacheExpirationTimeMinutesDefault)); + public async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) { var cacheKey = $"{CacheKeyPrefix}{id}"; @@ -29,7 +31,8 @@ public async Task GetByIdAsync(int id, CancellationToken } catch (Exception ex) { - logger.LogWarning(ex, "Failed to read from distributed cache for key={cacheKey}. Falling back to generation.", cacheKey); + logger.LogWarning(ex, + "Failed to read from distributed cache for key={cacheKey}. Falling back to generation.", cacheKey); } if (!string.IsNullOrEmpty(jsonCached)) @@ -45,7 +48,7 @@ public async Task GetByIdAsync(int id, CancellationToken { logger.LogWarning(ex, "Invalid JSON in residential building cache for key {cacheKey}.", cacheKey); } - + if (objCached is null) { logger.LogWarning("Cache for residential building with Id={id} returned null.", id); @@ -56,25 +59,27 @@ public async Task GetByIdAsync(int id, CancellationToken return objCached; } } - - var obj = generator.Generate(id); + + ResidentialBuildingDto obj = generator.Generate(id); try { - await cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(obj), CreateCacheOptions(), cancellationToken); + await cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(obj), CreateCacheOptions(), + cancellationToken); } catch (Exception ex) { - logger.LogWarning(ex, "Failed to write residential building with Id={id} to cache. Still returning generated value.", id); + logger.LogWarning(ex, + "Failed to write residential building with Id={id} to cache. Still returning generated value.", id); } - + logger.LogInformation("Generated and cached residential building with Id={id}", id); - + return obj; } /// - /// Создаёт настройки кэша - задаёт время жизни кэша. + /// Создаёт настройки кэша - задаёт время жизни кэша. /// private DistributedCacheEntryOptions CreateCacheOptions() { diff --git a/ResidentialBuilding.ServiceDefaults/Extensions.cs b/ResidentialBuilding.ServiceDefaults/Extensions.cs index 5e59484..2991eb6 100644 --- a/ResidentialBuilding.ServiceDefaults/Extensions.cs +++ b/ResidentialBuilding.ServiceDefaults/Extensions.cs @@ -44,7 +44,8 @@ public static TBuilder AddServiceDefaults(this TBuilder builder) where return builder; } - public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder + public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) + where TBuilder : IHostApplicationBuilder { builder.Logging.AddOpenTelemetry(logging => { @@ -78,7 +79,8 @@ public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) w return builder; } - private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder + private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) + where TBuilder : IHostApplicationBuilder { var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); @@ -97,7 +99,8 @@ private static TBuilder AddOpenTelemetryExporters(this TBuilder builde return builder; } - public static TBuilder AddDefaultHealthChecks(this TBuilder builder) where TBuilder : IHostApplicationBuilder + public static TBuilder AddDefaultHealthChecks(this TBuilder builder) + where TBuilder : IHostApplicationBuilder { builder.Services.AddHealthChecks() // Add a default liveness check to ensure app is responsive @@ -124,4 +127,4 @@ public static WebApplication MapDefaultEndpoints(this WebApplication app) return app; } -} +} \ No newline at end of file diff --git a/ResidentialBuilding.ServiceDefaults/ResidentialBuilding.ServiceDefaults.csproj b/ResidentialBuilding.ServiceDefaults/ResidentialBuilding.ServiceDefaults.csproj index 051b99a..723047f 100644 --- a/ResidentialBuilding.ServiceDefaults/ResidentialBuilding.ServiceDefaults.csproj +++ b/ResidentialBuilding.ServiceDefaults/ResidentialBuilding.ServiceDefaults.csproj @@ -17,10 +17,10 @@ - - - - + + + + From ae35609d2d8d51af54c0a301c23f1c49ab7cce14 Mon Sep 17 00:00:00 2001 From: Donistr Date: Sun, 22 Feb 2026 20:27:16 +0400 Subject: [PATCH 07/15] =?UTF-8?q?refactor:=20=D0=B2=D1=8B=D0=BD=D0=B5?= =?UTF-8?q?=D1=81=20faker=20=D0=B2=20private=20static=20readonly=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Generator/ResidentialBuildingGenerator.cs | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/ResidentialBuilding.Generator/Generator/ResidentialBuildingGenerator.cs b/ResidentialBuilding.Generator/Generator/ResidentialBuildingGenerator.cs index 7f68d3b..5369047 100644 --- a/ResidentialBuilding.Generator/Generator/ResidentialBuildingGenerator.cs +++ b/ResidentialBuilding.Generator/Generator/ResidentialBuildingGenerator.cs @@ -34,6 +34,39 @@ public class ResidentialBuildingGenerator(ILogger "Офис" ]; + private static readonly Faker? _faker = new Faker("ru") + .RuleFor(x => x.Address, f => f.Address.FullAddress()) + .RuleFor(x => x.PropertyType, f => f.PickRandom(_propertyTypes)) + .RuleFor(x => x.BuildYear, f => f.Random.Int(MinBuildYear, DateTime.Today.Year)) + .RuleFor(x => x.TotalArea, f => Math.Round(f.Random.Double(MinTotalArea, MaxTotalArea), 2)) + .RuleFor(x => x.LivingArea, (f, dto) => + { + var livingAreaPartOfTotalArea = + f.Random.Double(MinLivingAreaPartOfTotalArea, MaxLivingAreaPartOfTotalArea); + return Math.Round(livingAreaPartOfTotalArea * dto.TotalArea, 2); + }) + .RuleFor(x => x.TotalFloors, f => f.Random.Int(MinTotalFloors, MaxTotalFloors)) + .RuleFor(x => x.Floor, (f, dto) => + { + if (dto.PropertyType is "ИЖС") + { + return null; + } + + return f.Random.Int(1, dto.TotalFloors); + }) + .RuleFor(x => x.CadastralNumber, f => + $"{f.Random.Int(1, 99):D2}:" + + $"{f.Random.Int(1, 99):D2}:" + + $"{f.Random.Int(1, 9999999):D7}:" + + $"{f.Random.Int(1, 9999):D4}") + .RuleFor(x => x.CadastralValue, (f, dto) => + { + var pricePerM2 = f.Random.Double(MinPricePerM2, MaxPricePerM2); + var price = dto.TotalArea * pricePerM2; + return (decimal)Math.Round(price, 2); + }); + /// /// Генерирует объект жилого строительства для заданного идентификатора. /// @@ -43,41 +76,8 @@ public ResidentialBuildingDto Generate(int id) { logger.LogInformation("Generating Residential Building for Id={id}", id); - Faker? faker = new Faker("ru") - .RuleFor(x => x.Id, _ => id) - .RuleFor(x => x.Address, f => f.Address.FullAddress()) - .RuleFor(x => x.PropertyType, f => f.PickRandom(_propertyTypes)) - .RuleFor(x => x.BuildYear, f => f.Random.Int(MinBuildYear, DateTime.Today.Year)) - .RuleFor(x => x.TotalArea, f => Math.Round(f.Random.Double(MinTotalArea, MaxTotalArea), 2)) - .RuleFor(x => x.LivingArea, (f, dto) => - { - var livingAreaPartOfTotalArea = - f.Random.Double(MinLivingAreaPartOfTotalArea, MaxLivingAreaPartOfTotalArea); - return Math.Round(livingAreaPartOfTotalArea * dto.TotalArea, 2); - }) - .RuleFor(x => x.TotalFloors, f => f.Random.Int(MinTotalFloors, MaxTotalFloors)) - .RuleFor(x => x.Floor, (f, dto) => - { - if (dto.PropertyType is "ИЖС") - { - return null; - } - - return f.Random.Int(1, dto.TotalFloors); - }) - .RuleFor(x => x.CadastralNumber, f => - $"{f.Random.Int(1, 99):D2}:" + - $"{f.Random.Int(1, 99):D2}:" + - $"{f.Random.Int(1, 9999999):D7}:" + - $"{f.Random.Int(1, 9999):D4}") - .RuleFor(x => x.CadastralValue, (f, dto) => - { - var pricePerM2 = f.Random.Double(MinPricePerM2, MaxPricePerM2); - var price = dto.TotalArea * pricePerM2; - return (decimal)Math.Round(price, 2); - }); - - ResidentialBuildingDto? generatedObject = faker.Generate(); + ResidentialBuildingDto? generatedObject = _faker!.Generate(); + generatedObject.Id = id; logger.LogInformation( "Residential building generated: Id={Id}, Address='{Address}', PropertyType='{PropertyType}', " + From 8724252d82ee5da176c3a8715efaddb0e1b69a39 Mon Sep 17 00:00:00 2001 From: Donistr Date: Wed, 25 Feb 2026 09:08:33 +0400 Subject: [PATCH 08/15] =?UTF-8?q?style:=20=D1=81=D0=B4=D0=B5=D0=BB=D0=B0?= =?UTF-8?q?=D0=BB=20revert=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B8=D1=82=D0=B0=20?= =?UTF-8?q?=D1=81=20=D0=BA=D0=BB=D0=B8=D0=BD=D0=B0=D0=BF=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 23 ++++---- Client.Wasm/App.razor | 4 +- Client.Wasm/Client.Wasm.csproj | 30 +++++------ Client.Wasm/Components/DataCard.razor | 31 ++++------- Client.Wasm/Components/StudentCard.razor | 11 ++-- Client.Wasm/Layout/MainLayout.razor.css | 24 ++++----- Client.Wasm/Program.cs | 2 +- Client.Wasm/wwwroot/css/app.css | 50 +++++++++--------- Client.Wasm/wwwroot/index.html | 45 ++++++++-------- ResidentialBuilding.AppHost/AppHost.cs | 10 ++-- .../ResidentialBuilding.AppHost.csproj | 14 ++--- .../ResidentialBuildingController.cs | 12 ++--- .../DTO/ResidentialBuildingDto.cs | 52 +++++++++---------- .../Generator/ResidentialBuildingGenerator.cs | 16 +++--- ResidentialBuilding.Generator/Program.cs | 8 +-- .../ResidentialBuilding.Generator.csproj | 8 +-- .../Service/IResidentialBuildingService.cs | 12 ++--- .../Service/ResidentialBuildingService.cs | 33 +++++------- .../Extensions.cs | 11 ++-- ...ResidentialBuilding.ServiceDefaults.csproj | 8 +-- 20 files changed, 188 insertions(+), 216 deletions(-) diff --git a/.editorconfig b/.editorconfig index a8a58a0..0f3bba5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -43,7 +43,6 @@ csharp_style_var_for_built_in_types = true:error csharp_style_var_when_type_is_apparent = true:error csharp_style_var_elsewhere = false:silent csharp_space_around_binary_operators = before_and_after - [*.{cs,vb}] #### Naming styles #### @@ -65,31 +64,31 @@ dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case dotnet_naming_symbols.interface.applicable_kinds = interface dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.interface.required_modifiers = +dotnet_naming_symbols.interface.required_modifiers = dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.types.required_modifiers = +dotnet_naming_symbols.types.required_modifiers = dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.non_field_members.required_modifiers = +dotnet_naming_symbols.non_field_members.required_modifiers = # Naming styles dotnet_naming_style.begins_with_i.required_prefix = I -dotnet_naming_style.begins_with_i.required_suffix = -dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = dotnet_naming_style.begins_with_i.capitalization = pascal_case -dotnet_naming_style.pascal_case.required_prefix = -dotnet_naming_style.pascal_case.required_suffix = -dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case -dotnet_naming_style.pascal_case.required_prefix = -dotnet_naming_style.pascal_case.required_suffix = -dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case dotnet_style_operator_placement_when_wrapping = beginning_of_line tab_width = 4 diff --git a/Client.Wasm/App.razor b/Client.Wasm/App.razor index a8cf817..6fd3ed1 100644 --- a/Client.Wasm/App.razor +++ b/Client.Wasm/App.razor @@ -1,7 +1,7 @@  - - + + Not found diff --git a/Client.Wasm/Client.Wasm.csproj b/Client.Wasm/Client.Wasm.csproj index 292e763..0ba9f90 100644 --- a/Client.Wasm/Client.Wasm.csproj +++ b/Client.Wasm/Client.Wasm.csproj @@ -1,21 +1,21 @@  - - net8.0 - enable - enable - + + net8.0 + enable + enable + - - - + + + - - - - - - - + + + + + + + diff --git a/Client.Wasm/Components/DataCard.razor b/Client.Wasm/Components/DataCard.razor index ad0ad2f..307d98c 100644 --- a/Client.Wasm/Components/DataCard.razor +++ b/Client.Wasm/Components/DataCard.razor @@ -1,25 +1,22 @@ -@inject IConfiguration Configuration +@inject IConfiguration Configuration @inject HttpClient Client - - - Характеристики текущего объекта - + Характеристики текущего объекта - +
- + # Характеристика Значение - + - @if (Value is null) + @if(Value is null) { 1 @@ -33,7 +30,7 @@ foreach (var property in array) { - @(Array.IndexOf(array, property) + 1) + @(Array.IndexOf(array, property)+1) @property.Key @property.Value?.ToString() @@ -43,13 +40,10 @@
- + - - - Запросить новый объект - + Запросить новый объект @@ -60,9 +54,7 @@ - + @@ -76,8 +68,7 @@ private async Task RequestNewData() { var baseAddress = Configuration["BaseAddress"] ?? throw new KeyNotFoundException("Конфигурация клиента не содержит параметра BaseAddress"); - Value = await Client.GetFromJsonAsync($"{baseAddress}/{Id}", new JsonSerializerOptions()); + Value = await Client.GetFromJsonAsync($"{baseAddress}/{Id}", new JsonSerializerOptions { }); StateHasChanged(); } - } diff --git a/Client.Wasm/Components/StudentCard.razor b/Client.Wasm/Components/StudentCard.razor index 0613f8c..eae33b0 100644 --- a/Client.Wasm/Components/StudentCard.razor +++ b/Client.Wasm/Components/StudentCard.razor @@ -1,18 +1,13 @@  - - - Лабораторная работа - + Лабораторная работа Номер №1 «Кэширование» Вариант №38 "Объект жилого строительства" - Выполнена Елагиным Денисом 6513 - - - Ссылка на форк + Выполнена Елагиным Денисом 6513 + Ссылка на форк diff --git a/Client.Wasm/Layout/MainLayout.razor.css b/Client.Wasm/Layout/MainLayout.razor.css index 019d27b..ecf25e5 100644 --- a/Client.Wasm/Layout/MainLayout.razor.css +++ b/Client.Wasm/Layout/MainLayout.razor.css @@ -21,20 +21,20 @@ main { align-items: center; } -.top-row ::deep a, .top-row ::deep .btn-link { - white-space: nowrap; - margin-left: 1.5rem; - text-decoration: none; -} + .top-row ::deep a, .top-row ::deep .btn-link { + white-space: nowrap; + margin-left: 1.5rem; + text-decoration: none; + } -.top-row ::deep a:hover, .top-row ::deep .btn-link:hover { - text-decoration: underline; -} + .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { + text-decoration: underline; + } -.top-row ::deep a:first-child { - overflow: hidden; - text-overflow: ellipsis; -} + .top-row ::deep a:first-child { + overflow: hidden; + text-overflow: ellipsis; + } @media (max-width: 640.98px) { .top-row { diff --git a/Client.Wasm/Program.cs b/Client.Wasm/Program.cs index 30bab12..a182a92 100644 --- a/Client.Wasm/Program.cs +++ b/Client.Wasm/Program.cs @@ -14,4 +14,4 @@ .AddBootstrapProviders() .AddFontAwesomeIcons(); -await builder.Build().RunAsync(); \ No newline at end of file +await builder.Build().RunAsync(); diff --git a/Client.Wasm/wwwroot/css/app.css b/Client.Wasm/wwwroot/css/app.css index cc1cfb1..54a8aa3 100644 --- a/Client.Wasm/wwwroot/css/app.css +++ b/Client.Wasm/wwwroot/css/app.css @@ -17,7 +17,7 @@ a, .btn-link { } .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { - box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; + box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; } .content { @@ -48,12 +48,12 @@ a, .btn-link { z-index: 1000; } -#blazor-error-ui .dismiss { - cursor: pointer; - position: absolute; - right: 0.75rem; - top: 0.5rem; -} + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; + } .blazor-error-boundary { background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; @@ -61,9 +61,9 @@ a, .btn-link { color: white; } -.blazor-error-boundary::after { - content: "An error has occurred." -} + .blazor-error-boundary::after { + content: "An error has occurred." + } .loading-progress { position: relative; @@ -73,19 +73,19 @@ a, .btn-link { margin: 20vh auto 1rem auto; } -.loading-progress circle { - fill: none; - stroke: #e0e0e0; - stroke-width: 0.6rem; - transform-origin: 50% 50%; - transform: rotate(-90deg); -} + .loading-progress circle { + fill: none; + stroke: #e0e0e0; + stroke-width: 0.6rem; + transform-origin: 50% 50%; + transform: rotate(-90deg); + } -.loading-progress circle:last-child { - stroke: #1b6ec2; - stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; - transition: stroke-dasharray 0.05s ease-in-out; -} + .loading-progress circle:last-child { + stroke: #1b6ec2; + stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; + transition: stroke-dasharray 0.05s ease-in-out; + } .loading-progress-text { position: absolute; @@ -94,9 +94,9 @@ a, .btn-link { inset: calc(20vh + 3.25rem) 0 auto 0.2rem; } -.loading-progress-text:after { - content: var(--blazor-load-percentage-text, "Loading"); -} + .loading-progress-text:after { + content: var(--blazor-load-percentage-text, "Loading"); + } code { color: #c02d76; diff --git a/Client.Wasm/wwwroot/index.html b/Client.Wasm/wwwroot/index.html index eb7d7c1..b74ee32 100644 --- a/Client.Wasm/wwwroot/index.html +++ b/Client.Wasm/wwwroot/index.html @@ -2,35 +2,34 @@ - - + + Client.Wasm - - - + + + - - - - + + + + -
- - - - -
-
+
+ + + + +
+
-
- An unhandled error has occurred. - Reload - 🗙 -
- +
+ An unhandled error has occurred. + Reload + 🗙 +
+ diff --git a/ResidentialBuilding.AppHost/AppHost.cs b/ResidentialBuilding.AppHost/AppHost.cs index d7182a3..2ed01ed 100644 --- a/ResidentialBuilding.AppHost/AppHost.cs +++ b/ResidentialBuilding.AppHost/AppHost.cs @@ -1,15 +1,13 @@ -using Projects; +var builder = DistributedApplication.CreateBuilder(args); -IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder(args); - -IResourceBuilder cache = builder.AddRedis("residential-building-cache") +var cache = builder.AddRedis("residential-building-cache") .WithRedisInsight(containerName: "residential-building-insight"); -IResourceBuilder generator = builder.AddProject("generator") +var generator = builder.AddProject("generator") .WithReference(cache, "residential-building-cache") .WaitFor(cache); -IResourceBuilder client = builder.AddProject("client") +var client = builder.AddProject("client") .WithReference(generator) .WaitFor(generator); diff --git a/ResidentialBuilding.AppHost/ResidentialBuilding.AppHost.csproj b/ResidentialBuilding.AppHost/ResidentialBuilding.AppHost.csproj index cf0a5e1..83be43b 100644 --- a/ResidentialBuilding.AppHost/ResidentialBuilding.AppHost.csproj +++ b/ResidentialBuilding.AppHost/ResidentialBuilding.AppHost.csproj @@ -1,7 +1,7 @@ - - + + Exe net8.0 @@ -11,13 +11,13 @@ - - + + - - + + - + diff --git a/ResidentialBuilding.Generator/Controller/ResidentialBuildingController.cs b/ResidentialBuilding.Generator/Controller/ResidentialBuildingController.cs index 1f6ab24..5d921d2 100644 --- a/ResidentialBuilding.Generator/Controller/ResidentialBuildingController.cs +++ b/ResidentialBuilding.Generator/Controller/ResidentialBuildingController.cs @@ -5,16 +5,14 @@ namespace Generator.Controller; /// -/// Контроллер для объектов жилого строительства. +/// Контроллер для объектов жилого строительства. /// [Route("api/residential-building")] [ApiController] -public class ResidentialBuildingController( - ILogger logger, - IResidentialBuildingService residentialBuildingService) : ControllerBase +public class ResidentialBuildingController(ILogger logger, IResidentialBuildingService residentialBuildingService) : ControllerBase { /// - /// Получение объекта жилого строительства по id. + /// Получение объекта жилого строительства по id. /// [HttpGet("{id:int}")] public async Task> GetResidentialBuilding(int id) @@ -23,9 +21,9 @@ public async Task> GetResidentialBuilding(i { return BadRequest("id must be >= 0"); } - + logger.LogInformation("Getting residential building with Id={id}.", id); - ResidentialBuildingDto result = await residentialBuildingService.GetByIdAsync(id); + var result = await residentialBuildingService.GetByIdAsync(id); logger.LogInformation("Residential building with Id={id} successfully received.", id); return Ok(result); diff --git a/ResidentialBuilding.Generator/DTO/ResidentialBuildingDto.cs b/ResidentialBuilding.Generator/DTO/ResidentialBuildingDto.cs index f62c40a..2c9eac0 100644 --- a/ResidentialBuilding.Generator/DTO/ResidentialBuildingDto.cs +++ b/ResidentialBuilding.Generator/DTO/ResidentialBuildingDto.cs @@ -1,57 +1,57 @@ namespace Generator.DTO; /// -/// DTO объекта жилого строительства. +/// DTO объекта жилого строительства /// public class ResidentialBuildingDto { /// - /// Идентификатор в системе. + /// Идентификатор в системе /// public int Id { get; set; } - + /// - /// Адрес. + /// Адрес /// public string Address { get; set; } = string.Empty; - + /// - /// Тип недвижимости. + /// Тип недвижимости /// public string PropertyType { get; set; } = string.Empty; - + /// - /// Год постройки. + /// Год постройки /// - public int BuildYear { get; set; } - + public int BuildYear { get; set; } + /// - /// Общая площадь. + /// Общая площадь /// - public double TotalArea { get; set; } - + public double TotalArea { get; set; } + /// - /// Жилая площадь. + /// Жилая площадь /// - public double LivingArea { get; set; } - + public double LivingArea { get; set; } + /// - /// Этаж. + /// Этаж /// public int? Floor { get; set; } - + /// - /// Этажность. + /// Этажность /// - public int TotalFloors { get; set; } - + public int TotalFloors { get; set; } + /// - /// Кадастровый номер. + /// Кадастровый номер /// - public string CadastralNumber { get; set; } = string.Empty; - + public string CadastralNumber { get; set; } = string.Empty; + /// - /// Кадастровая стоимость. + /// Кадастровая стоимость /// - public decimal CadastralValue { get; set; } + public decimal CadastralValue { get; set; } } \ No newline at end of file diff --git a/ResidentialBuilding.Generator/Generator/ResidentialBuildingGenerator.cs b/ResidentialBuilding.Generator/Generator/ResidentialBuildingGenerator.cs index 5369047..7b5e1d5 100644 --- a/ResidentialBuilding.Generator/Generator/ResidentialBuildingGenerator.cs +++ b/ResidentialBuilding.Generator/Generator/ResidentialBuildingGenerator.cs @@ -4,7 +4,7 @@ namespace Generator.Generator; /// -/// Генератор объектов жилого строительства на основе Bogus. +/// Генератор объектов жилого строительства на основе Bogus /// public class ResidentialBuildingGenerator(ILogger logger) { @@ -33,8 +33,8 @@ public class ResidentialBuildingGenerator(ILogger "Апартаменты", "Офис" ]; - - private static readonly Faker? _faker = new Faker("ru") + + private static readonly Faker _faker = new Faker("ru") .RuleFor(x => x.Address, f => f.Address.FullAddress()) .RuleFor(x => x.PropertyType, f => f.PickRandom(_propertyTypes)) .RuleFor(x => x.BuildYear, f => f.Random.Int(MinBuildYear, DateTime.Today.Year)) @@ -68,17 +68,17 @@ public class ResidentialBuildingGenerator(ILogger }); /// - /// Генерирует объект жилого строительства для заданного идентификатора. + /// Генерирует объект жилого строительства для заданного идентификатора /// - /// Идентификатор объекта жилого строительства. - /// Сгенерированный объект жилого строительства. + /// Идентификатор объекта жилого строительства + /// Сгенерированный объект жилого строительства public ResidentialBuildingDto Generate(int id) { logger.LogInformation("Generating Residential Building for Id={id}", id); - ResidentialBuildingDto? generatedObject = _faker!.Generate(); + var generatedObject = _faker.Generate(); generatedObject.Id = id; - + logger.LogInformation( "Residential building generated: Id={Id}, Address='{Address}', PropertyType='{PropertyType}', " + "BuildYear={BuildYear}, TotalArea={TotalArea}, LivingArea={LivingArea}, Floor={Floor}, " + diff --git a/ResidentialBuilding.Generator/Program.cs b/ResidentialBuilding.Generator/Program.cs index 4412dc5..760ae15 100644 --- a/ResidentialBuilding.Generator/Program.cs +++ b/ResidentialBuilding.Generator/Program.cs @@ -4,7 +4,7 @@ using Serilog; using Serilog.Formatting.Compact; -WebApplicationBuilder builder = WebApplication.CreateBuilder(args); +var builder = WebApplication.CreateBuilder(args); builder.Host.UseSerilog((hostingContext, services, loggerConfiguration) => { @@ -14,7 +14,7 @@ .ReadFrom.Services(services) .Enrich.FromLogContext() .WriteTo.Console( - new CompactJsonFormatter() + formatter: new CompactJsonFormatter() ); }); @@ -33,11 +33,11 @@ }); builder.Services.AddSingleton(); -builder.Services.AddSingleton(); +builder.Services.AddSingleton(); builder.Services.AddControllers(); -WebApplication app = builder.Build(); +var app = builder.Build(); app.UseCors("AllowLocalDev"); diff --git a/ResidentialBuilding.Generator/ResidentialBuilding.Generator.csproj b/ResidentialBuilding.Generator/ResidentialBuilding.Generator.csproj index 73d8dac..07dcd97 100644 --- a/ResidentialBuilding.Generator/ResidentialBuilding.Generator.csproj +++ b/ResidentialBuilding.Generator/ResidentialBuilding.Generator.csproj @@ -8,13 +8,13 @@ - - - + + + - + diff --git a/ResidentialBuilding.Generator/Service/IResidentialBuildingService.cs b/ResidentialBuilding.Generator/Service/IResidentialBuildingService.cs index ae30a58..0247670 100644 --- a/ResidentialBuilding.Generator/Service/IResidentialBuildingService.cs +++ b/ResidentialBuilding.Generator/Service/IResidentialBuildingService.cs @@ -3,16 +3,16 @@ namespace Generator.Service; /// -/// Сервис получения объектов жилого строительства по идентификатору. -/// Если удалось найти объект в кэше - возвращает его, иначе генерирует, кэширует и возвращает сгенерированный. +/// Сервис получения объектов жилого строительства по идентификатору. +/// Если удалось найти объект в кэше - возвращает его, иначе генерирует, кэширует и возвращает сгенерированный. /// public interface IResidentialBuildingService { /// - /// Пытается найти в кэше объект с заданным идентификатором: - /// если удалось, то десериализует объект из JSON-а и возвращает; - /// если не удалось или произошла ошибка в ходе получения/десериализации, то генерирует объект, сохраняет в кэш и - /// возвращает сгенерированный. + /// Пытается найти в кэше объект с заданным идентификатором: + /// если удалось, то десериализует объект из JSON-а и возвращает; + /// если не удалось или произошла ошибка в ходе получения/десериализации, то генерирует объект, сохраняет в кэш и + /// возвращает сгенерированный. /// /// Идентификатор объекта жилого строительства. /// Токен отмены. diff --git a/ResidentialBuilding.Generator/Service/ResidentialBuildingService.cs b/ResidentialBuilding.Generator/Service/ResidentialBuildingService.cs index 6a669e2..7e3b5fe 100644 --- a/ResidentialBuilding.Generator/Service/ResidentialBuildingService.cs +++ b/ResidentialBuilding.Generator/Service/ResidentialBuildingService.cs @@ -10,16 +10,14 @@ public class ResidentialBuildingService( ResidentialBuildingGenerator generator, IDistributedCache cache, IConfiguration configuration -) : IResidentialBuildingService + ) : IResidentialBuildingService { private const string CacheKeyPrefix = "residential-building:"; - + private const int CacheExpirationTimeMinutesDefault = 15; - - private readonly TimeSpan _cacheExpirationTimeMinutes = - TimeSpan.FromMinutes(configuration.GetValue("CacheSettings:ExpirationTimeMinutes", - CacheExpirationTimeMinutesDefault)); - + + private readonly TimeSpan _cacheExpirationTimeMinutes = TimeSpan.FromMinutes(configuration.GetValue("CacheSettings:ExpirationTimeMinutes", CacheExpirationTimeMinutesDefault)); + public async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) { var cacheKey = $"{CacheKeyPrefix}{id}"; @@ -31,8 +29,7 @@ public async Task GetByIdAsync(int id, CancellationToken } catch (Exception ex) { - logger.LogWarning(ex, - "Failed to read from distributed cache for key={cacheKey}. Falling back to generation.", cacheKey); + logger.LogWarning(ex, "Failed to read from distributed cache for key={cacheKey}. Falling back to generation.", cacheKey); } if (!string.IsNullOrEmpty(jsonCached)) @@ -48,7 +45,7 @@ public async Task GetByIdAsync(int id, CancellationToken { logger.LogWarning(ex, "Invalid JSON in residential building cache for key {cacheKey}.", cacheKey); } - + if (objCached is null) { logger.LogWarning("Cache for residential building with Id={id} returned null.", id); @@ -59,27 +56,25 @@ public async Task GetByIdAsync(int id, CancellationToken return objCached; } } - - ResidentialBuildingDto obj = generator.Generate(id); + + var obj = generator.Generate(id); try { - await cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(obj), CreateCacheOptions(), - cancellationToken); + await cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(obj), CreateCacheOptions(), cancellationToken); } catch (Exception ex) { - logger.LogWarning(ex, - "Failed to write residential building with Id={id} to cache. Still returning generated value.", id); + logger.LogWarning(ex, "Failed to write residential building with Id={id} to cache. Still returning generated value.", id); } - + logger.LogInformation("Generated and cached residential building with Id={id}", id); - + return obj; } /// - /// Создаёт настройки кэша - задаёт время жизни кэша. + /// Создаёт настройки кэша - задаёт время жизни кэша. /// private DistributedCacheEntryOptions CreateCacheOptions() { diff --git a/ResidentialBuilding.ServiceDefaults/Extensions.cs b/ResidentialBuilding.ServiceDefaults/Extensions.cs index 2991eb6..5e59484 100644 --- a/ResidentialBuilding.ServiceDefaults/Extensions.cs +++ b/ResidentialBuilding.ServiceDefaults/Extensions.cs @@ -44,8 +44,7 @@ public static TBuilder AddServiceDefaults(this TBuilder builder) where return builder; } - public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) - where TBuilder : IHostApplicationBuilder + public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder { builder.Logging.AddOpenTelemetry(logging => { @@ -79,8 +78,7 @@ public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) return builder; } - private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) - where TBuilder : IHostApplicationBuilder + private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder { var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); @@ -99,8 +97,7 @@ private static TBuilder AddOpenTelemetryExporters(this TBuilder builde return builder; } - public static TBuilder AddDefaultHealthChecks(this TBuilder builder) - where TBuilder : IHostApplicationBuilder + public static TBuilder AddDefaultHealthChecks(this TBuilder builder) where TBuilder : IHostApplicationBuilder { builder.Services.AddHealthChecks() // Add a default liveness check to ensure app is responsive @@ -127,4 +124,4 @@ public static WebApplication MapDefaultEndpoints(this WebApplication app) return app; } -} \ No newline at end of file +} diff --git a/ResidentialBuilding.ServiceDefaults/ResidentialBuilding.ServiceDefaults.csproj b/ResidentialBuilding.ServiceDefaults/ResidentialBuilding.ServiceDefaults.csproj index 723047f..051b99a 100644 --- a/ResidentialBuilding.ServiceDefaults/ResidentialBuilding.ServiceDefaults.csproj +++ b/ResidentialBuilding.ServiceDefaults/ResidentialBuilding.ServiceDefaults.csproj @@ -17,10 +17,10 @@ - - - - + + + + From 6cab0cf9c8db81867a4592131a8452bff81a418e Mon Sep 17 00:00:00 2001 From: Donistr Date: Wed, 25 Feb 2026 10:00:16 +0400 Subject: [PATCH 09/15] =?UTF-8?q?fix:=20=D1=83=D0=B1=D1=80=D0=B0=D0=BB=20s?= =?UTF-8?q?erilog,=20=D1=87=D1=82=D0=BE=D0=B1=D1=8B=20=D0=BF=D0=BE=D1=84?= =?UTF-8?q?=D0=B8=D0=BA=D1=81=D0=B8=D1=82=D1=8C=20=D1=81=D1=82=D1=80=D1=83?= =?UTF-8?q?=D0=BA=D1=82=D1=83=D1=80=D0=BD=D0=BE=D0=B5=20=D0=BB=D0=BE=D0=B3?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ResidentialBuilding.Generator/Program.cs | 14 -------------- .../ResidentialBuilding.ServiceDefaults.csproj | 4 ---- 2 files changed, 18 deletions(-) diff --git a/ResidentialBuilding.Generator/Program.cs b/ResidentialBuilding.Generator/Program.cs index 760ae15..8a3f973 100644 --- a/ResidentialBuilding.Generator/Program.cs +++ b/ResidentialBuilding.Generator/Program.cs @@ -1,23 +1,9 @@ using Generator.Generator; using Generator.Service; using ResidentialBuilding.ServiceDefaults; -using Serilog; -using Serilog.Formatting.Compact; var builder = WebApplication.CreateBuilder(args); -builder.Host.UseSerilog((hostingContext, services, loggerConfiguration) => -{ - loggerConfiguration - .MinimumLevel.Information() - .ReadFrom.Configuration(hostingContext.Configuration) - .ReadFrom.Services(services) - .Enrich.FromLogContext() - .WriteTo.Console( - formatter: new CompactJsonFormatter() - ); -}); - builder.AddServiceDefaults(); builder.AddRedisDistributedCache("residential-building-cache"); diff --git a/ResidentialBuilding.ServiceDefaults/ResidentialBuilding.ServiceDefaults.csproj b/ResidentialBuilding.ServiceDefaults/ResidentialBuilding.ServiceDefaults.csproj index 051b99a..9fb1f65 100644 --- a/ResidentialBuilding.ServiceDefaults/ResidentialBuilding.ServiceDefaults.csproj +++ b/ResidentialBuilding.ServiceDefaults/ResidentialBuilding.ServiceDefaults.csproj @@ -17,10 +17,6 @@ - - - - From c6f84a5a74b6df5ee472398204bff9d2d594b882 Mon Sep 17 00:00:00 2001 From: Donistr Date: Wed, 25 Feb 2026 10:04:05 +0400 Subject: [PATCH 10/15] =?UTF-8?q?refactor:=20=D1=83=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=BB=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=20CreateCahcheOptions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Service/ResidentialBuildingService.cs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/ResidentialBuilding.Generator/Service/ResidentialBuildingService.cs b/ResidentialBuilding.Generator/Service/ResidentialBuildingService.cs index 7e3b5fe..073f777 100644 --- a/ResidentialBuilding.Generator/Service/ResidentialBuildingService.cs +++ b/ResidentialBuilding.Generator/Service/ResidentialBuildingService.cs @@ -61,7 +61,11 @@ public async Task GetByIdAsync(int id, CancellationToken try { - await cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(obj), CreateCacheOptions(), cancellationToken); + var cacheOptions = new DistributedCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = _cacheExpirationTimeMinutes + }; + await cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(obj), cacheOptions, cancellationToken); } catch (Exception ex) { @@ -72,15 +76,4 @@ public async Task GetByIdAsync(int id, CancellationToken return obj; } - - /// - /// Создаёт настройки кэша - задаёт время жизни кэша. - /// - private DistributedCacheEntryOptions CreateCacheOptions() - { - return new DistributedCacheEntryOptions - { - AbsoluteExpirationRelativeToNow = _cacheExpirationTimeMinutes - }; - } } \ No newline at end of file From 4c1f77197b3ae1ec058b126b233672c9744e36f8 Mon Sep 17 00:00:00 2001 From: Donistr Date: Wed, 25 Feb 2026 10:28:28 +0400 Subject: [PATCH 11/15] =?UTF-8?q?refactor:=20=D1=83=D0=BB=D1=83=D1=87?= =?UTF-8?q?=D1=88=D0=B8=D0=BB=20=D0=BB=D0=BE=D0=B3=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B5=20=D1=81=D0=B3=D0=B5=D0=BD=D0=B5=D1=80?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=BD=D0=BE=D0=B3=D0=BE=20?= =?UTF-8?q?=D0=BE=D0=B1=D1=8A=D0=B5=D0=BA=D1=82=D0=B0=20ResidentialBuildin?= =?UTF-8?q?gDto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Generator/ResidentialBuildingGenerator.cs | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/ResidentialBuilding.Generator/Generator/ResidentialBuildingGenerator.cs b/ResidentialBuilding.Generator/Generator/ResidentialBuildingGenerator.cs index 7b5e1d5..0468a35 100644 --- a/ResidentialBuilding.Generator/Generator/ResidentialBuildingGenerator.cs +++ b/ResidentialBuilding.Generator/Generator/ResidentialBuildingGenerator.cs @@ -80,13 +80,20 @@ public ResidentialBuildingDto Generate(int id) generatedObject.Id = id; logger.LogInformation( - "Residential building generated: Id={Id}, Address='{Address}', PropertyType='{PropertyType}', " + - "BuildYear={BuildYear}, TotalArea={TotalArea}, LivingArea={LivingArea}, Floor={Floor}, " + - "TotalFloors={TotalFloors}, CadastralNumber='{CadastralNumber}', CadastralValue={CadastralValue}", - generatedObject.Id, generatedObject.Address, generatedObject.PropertyType, generatedObject.BuildYear, - generatedObject.TotalArea, generatedObject.LivingArea, generatedObject.Floor, generatedObject.TotalFloors, - generatedObject.CadastralNumber, generatedObject.CadastralValue - ); + "Residential building generated: {@Building}", + new + { + generatedObject.Id, + generatedObject.Address, + generatedObject.PropertyType, + generatedObject.BuildYear, + generatedObject.TotalArea, + generatedObject.LivingArea, + generatedObject.Floor, + generatedObject.TotalFloors, + generatedObject.CadastralNumber, + generatedObject.CadastralValue + }); return generatedObject; } From 51fac4e356137e5740ed8b7260b4d42ce17c6259 Mon Sep 17 00:00:00 2001 From: Donistr Date: Wed, 25 Feb 2026 10:35:16 +0400 Subject: [PATCH 12/15] =?UTF-8?q?fix:=20=D0=BF=D0=BE=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=B8=D0=BB=20cors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ResidentialBuilding.Generator/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ResidentialBuilding.Generator/Program.cs b/ResidentialBuilding.Generator/Program.cs index 8a3f973..fea32e2 100644 --- a/ResidentialBuilding.Generator/Program.cs +++ b/ResidentialBuilding.Generator/Program.cs @@ -13,8 +13,8 @@ { policy .AllowAnyOrigin() - .AllowAnyHeader() - .AllowAnyMethod(); + .WithHeaders("Content-Type") + .WithMethods("GET"); }); }); From 38f2929442e39caf3657132d43a880d11a438845 Mon Sep 17 00:00:00 2001 From: Donistr Date: Wed, 25 Feb 2026 10:43:21 +0400 Subject: [PATCH 13/15] =?UTF-8?q?refactor:=20=D1=83=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=BB=20=D0=BE=D0=B1=D1=8A=D1=8F=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=B1=D0=B5=D1=81=D0=BF=D0=BE=D0=BB=D0=B5=D0=B7=D0=BD?= =?UTF-8?q?=D0=BE=D0=B9=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D0=BD?= =?UTF-8?q?=D0=BE=D0=B9=20generator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ResidentialBuilding.AppHost/AppHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ResidentialBuilding.AppHost/AppHost.cs b/ResidentialBuilding.AppHost/AppHost.cs index 2ed01ed..abdadfe 100644 --- a/ResidentialBuilding.AppHost/AppHost.cs +++ b/ResidentialBuilding.AppHost/AppHost.cs @@ -7,7 +7,7 @@ .WithReference(cache, "residential-building-cache") .WaitFor(cache); -var client = builder.AddProject("client") +builder.AddProject("client") .WithReference(generator) .WaitFor(generator); From fbc4abf0014e9aa97d277411fefb433276f414a5 Mon Sep 17 00:00:00 2001 From: Donistr Date: Thu, 26 Feb 2026 12:55:02 +0400 Subject: [PATCH 14/15] =?UTF-8?q?reafactor:=20=D0=B8=D1=81=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=B8=D0=BB=20=D0=BF=D0=BE=D1=80=D1=8F=D0=B4=D0=BE?= =?UTF-8?q?=D0=BA=20=D0=B8=D0=BC=D0=BF=D0=BE=D1=80=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Service/ResidentialBuildingService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ResidentialBuilding.Generator/Service/ResidentialBuildingService.cs b/ResidentialBuilding.Generator/Service/ResidentialBuildingService.cs index 073f777..974b172 100644 --- a/ResidentialBuilding.Generator/Service/ResidentialBuildingService.cs +++ b/ResidentialBuilding.Generator/Service/ResidentialBuildingService.cs @@ -1,7 +1,7 @@ -using System.Text.Json; -using Generator.DTO; +using Generator.DTO; using Generator.Generator; using Microsoft.Extensions.Caching.Distributed; +using System.Text.Json; namespace Generator.Service; From 77121714dd9e2d277cac222faeac63bf7728d940 Mon Sep 17 00:00:00 2001 From: Donistr Date: Thu, 26 Feb 2026 12:55:39 +0400 Subject: [PATCH 15/15] =?UTF-8?q?refactor:=20=D0=B4=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=B8=D0=BB=20=D0=B2=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80?= =?UTF-8?q?=D0=BE=D0=BB=D0=BB=D0=B5=D1=80=20=D0=B0=D1=82=D1=80=D0=B8=D0=B1?= =?UTF-8?q?=D1=83=D1=82=D1=8B=20=D1=81=20=D0=B2=D0=BE=D0=B7=D0=B2=D1=80?= =?UTF-8?q?=D0=B0=D1=89=D0=B0=D0=B5=D0=BC=D1=8B=D0=BC=D0=B8=20=D1=82=D0=B8?= =?UTF-8?q?=D0=BF=D0=B0=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controller/ResidentialBuildingController.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ResidentialBuilding.Generator/Controller/ResidentialBuildingController.cs b/ResidentialBuilding.Generator/Controller/ResidentialBuildingController.cs index 5d921d2..7df7ada 100644 --- a/ResidentialBuilding.Generator/Controller/ResidentialBuildingController.cs +++ b/ResidentialBuilding.Generator/Controller/ResidentialBuildingController.cs @@ -14,7 +14,13 @@ public class ResidentialBuildingController(ILogger /// Получение объекта жилого строительства по id. /// + /// Идентификатор объекта жилого строительства. + /// DTO объекта жилого строительства. + /// Успешное получение объекта. + /// Некорректный id. [HttpGet("{id:int}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task> GetResidentialBuilding(int id) { if (id <= 0)