diff --git a/ContactsApp.csproj b/ContactsApp.csproj
new file mode 100644
index 0000000..fa1439a
--- /dev/null
+++ b/ContactsApp.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/ContactsApp.http b/ContactsApp.http
new file mode 100644
index 0000000..0a549e4
--- /dev/null
+++ b/ContactsApp.http
@@ -0,0 +1,6 @@
+@ContactsApp_HostAddress = http://localhost:5246
+
+GET {{ContactsApp_HostAddress}}/weatherforecast/
+Accept: application/json
+
+###
diff --git a/ContactsApp.slnx b/ContactsApp.slnx
new file mode 100644
index 0000000..cd7d5d3
--- /dev/null
+++ b/ContactsApp.slnx
@@ -0,0 +1,3 @@
+
+
+
diff --git a/Controllers/ContactController.cs b/Controllers/ContactController.cs
new file mode 100644
index 0000000..eb28f93
--- /dev/null
+++ b/Controllers/ContactController.cs
@@ -0,0 +1,90 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using System.Text.Json;
+
+namespace ContactsApp
+{
+ [ApiController]
+ [Route("api/contacts")]
+ public class ContactController(IContactRepository repository) : ControllerBase
+ {
+ [HttpGet]
+ public ActionResult> GetAll()
+ {
+ var contacts = repository.GetAll().ToList();
+ return Ok(contacts);
+ }
+
+ [HttpPost]
+ public ActionResult Create([FromBody] Contact contact)
+ {
+ repository.AddContact(contact);
+ return Ok(contact);
+ }
+
+ [HttpGet("{id:Guid}")]
+ public ActionResult GetById(Guid id)
+ {
+ var contact = repository.GetContactById(id);
+ if (contact == null)
+ return NotFound();
+
+ return Ok(contact);
+ }
+ [HttpPost("{id:Guid}")]
+ public ActionResult Update(Guid id, [FromBody] Contact contact)
+ {
+ repository.UpdateContact(id, contact);
+ return Ok();
+ }
+
+ [HttpDelete("{id:Guid}")]
+ public ActionResult Delete(Guid id)
+ {
+ var contact = repository.GetContactById(id);
+ if (contact == null)
+ return NotFound();
+
+ repository.DeleteContact(id);
+ return Ok();
+ }
+
+ [HttpPost("upload")]
+ public async Task Upload(IFormFile file)
+ {
+ if (file == null || file.Length == 0)
+ return BadRequest("Файл пустой.");
+
+ using var stream = new MemoryStream();
+ await file.CopyToAsync(stream);
+ string json = System.Text.Encoding.UTF8.GetString(stream.ToArray());
+
+ try
+ {
+ repository.LoadJSON(json); // репозиторий сам парсит JSON и обновляет список
+ }
+ catch (Exception ex)
+ {
+ return BadRequest("Ошибка при обработке JSON: " + ex.Message);
+ }
+
+ return Ok("Контакты успешно загружены.");
+ }
+
+ [HttpGet("download")]
+ public IActionResult Download()
+ {
+ string json = repository.SaveJSON(); // репозиторий формирует JSON строку
+ byte[] bytes = System.Text.Encoding.UTF8.GetBytes(json);
+
+ return File(
+ bytes,
+ "application/json",
+ "contacts.json"
+ );
+ }
+
+ }
+}
+
+
diff --git a/IContactRepository.cs b/IContactRepository.cs
new file mode 100644
index 0000000..230ae69
--- /dev/null
+++ b/IContactRepository.cs
@@ -0,0 +1,16 @@
+п»їusing System;
+using System.Threading.Tasks;
+
+namespace ContactsApp
+{
+ public interface IContactRepository
+ {
+ public void AddContact(Contact contact);
+ public void UpdateContact(Guid id, Contact contact);
+ public void DeleteContact(Guid id);
+ public Contact GetContactById(Guid id);
+ public IEnumerable GetAll();
+ public void LoadJSON(string file);
+ public string SaveJSON();
+ }
+}
diff --git a/Models/Contact.cs b/Models/Contact.cs
new file mode 100644
index 0000000..c718597
--- /dev/null
+++ b/Models/Contact.cs
@@ -0,0 +1,33 @@
+using System.ComponentModel.DataAnnotations;
+using System.Net.Mail;
+
+namespace ContactsApp
+{
+ public class Contact
+ {
+ public Guid Id { get; }
+ public string Name { get; set; }
+ public string? Email { get; set; }
+ public string Phone { get; set; }
+
+ public Contact(string name, string? email, string phone)
+ {
+ Id = Guid.NewGuid();
+ Name = name;
+
+ try
+ {
+ var addr = new MailAddress(email);
+ Email = email;
+ }
+ catch (Exception ex)
+ {
+ throw new Exception("Invalid email");
+ }
+
+ Phone = phone;
+ }
+ }
+}
+
+
diff --git a/Program.cs b/Program.cs
new file mode 100644
index 0000000..8bf2641
--- /dev/null
+++ b/Program.cs
@@ -0,0 +1,32 @@
+using Microsoft.AspNetCore.Mvc;
+using Swashbuckle.AspNetCore.Swagger;
+
+
+namespace ContactsApp;
+
+public class Program
+{
+ public static void Main(string[] args)
+ {
+ var builder = WebApplication.CreateBuilder(args);
+
+ // Add services to the container.
+ builder.Services.AddControllers();
+ builder.Services.AddAuthorization();
+ builder.Services.AddSwaggerGen();
+
+ builder.Services.AddSingleton();
+
+ var app = builder.Build();
+
+ // Configure the HTTP request pipeline.
+ // Запрос -> Компу -> По порту -> Приложение -> Kestrel -> (HttpContext) Middlewares -> Controller (mapping) -> AfterMethod -> Method -> BeforeMethod
+ app.UseAuthorization();
+ app.UseSwagger();
+ app.UseSwaggerUI();
+
+
+ app.MapControllers();
+ app.Run();
+ }
+}
\ No newline at end of file
diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json
new file mode 100644
index 0000000..883ff9b
--- /dev/null
+++ b/Properties/launchSettings.json
@@ -0,0 +1,15 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": false,
+ "launchUrl": "swagger",
+ "applicationUrl": "http://localhost:5043",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/Repositories/ContactRepository.cs b/Repositories/ContactRepository.cs
new file mode 100644
index 0000000..c8e8823
--- /dev/null
+++ b/Repositories/ContactRepository.cs
@@ -0,0 +1,64 @@
+п»їusing Microsoft.Win32;
+using System;
+using System.Runtime;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace ContactsApp
+{
+ public class ContactRepository : IContactRepository
+ {
+ List Contacts = new ();
+
+
+ public void AddContact(Contact contact)
+ {
+ CheckUnique(contact);
+ Contacts.Add(contact);
+ }
+
+ public void UpdateContact(Guid id, Contact contact)
+ {
+ CheckUnique(contact);
+ Contact previous = GetContactById(id);
+ if (previous == null)
+ {
+ Contacts.Add(contact);
+ return;
+ }
+
+ previous.Name = contact.Name;
+ previous.Email = contact.Email;
+ previous.Phone = contact.Phone;
+ }
+
+ public void DeleteContact(Guid id)
+ {
+ var contact = GetContactById(id);
+ if (contact == null)
+ return;
+ Contacts.Remove(contact);
+ }
+
+ public Contact GetContactById(Guid id)
+ {
+ return Contacts.Find(x => x.Id == id);
+ }
+ public void LoadJSON(string json)
+ {
+ var list = JsonSerializer.Deserialize>(json);
+ Contacts.AddRange(list);
+ }
+ public string SaveJSON()
+ {
+ return JsonSerializer.Serialize(Contacts, new JsonSerializerOptions { WriteIndented = true });
+ }
+
+ public IEnumerable GetAll() => Contacts;
+ private void CheckUnique(Contact contact)
+ {
+ if (Contacts.FirstOrDefault(x => x.Equals(contact)) != null)
+ throw new Exception("Такой контакт уже есть");
+ }
+ }
+}
diff --git a/appsettings.Development.json b/appsettings.Development.json
new file mode 100644
index 0000000..0c208ae
--- /dev/null
+++ b/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/appsettings.json b/appsettings.json
new file mode 100644
index 0000000..10f68b8
--- /dev/null
+++ b/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}