Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Мирошник ФТ-402, Морозов ФТ-402, Бабинцев ФТ-304 #56

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions Tests/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,17 @@ public static void Main()
var testsToRun = new string[]
{
typeof(Task1_GetUserByIdTests).FullName,
//typeof(Task2_CreateUserTests).FullName,
//typeof(Task3_UpdateUserTests).FullName,
//typeof(Task4_PartiallyUpdateUserTests).FullName,
//typeof(Task5_DeleteUserTests).FullName,
//typeof(Task6_HeadUserByIdTests).FullName,
//typeof(Task7_GetUsersTests).FullName,
//typeof(Task8_GetUsersOptionsTests).FullName,
typeof(Task2_CreateUserTests).FullName,
typeof(Task3_UpdateUserTests).FullName,
typeof(Task4_PartiallyUpdateUserTests).FullName,
typeof(Task5_DeleteUserTests).FullName,
typeof(Task6_HeadUserByIdTests).FullName,
typeof(Task7_GetUsersTests).FullName,
typeof(Task8_GetUsersOptionsTests).FullName,
};
new AutoRun().Execute(new[]
{
// раскомментируй, чтоб останавливать выполнение после первой ошибки
// "--stoponerror",
"--stoponerror",
"--noresult",
"--test=" + string.Join(",", testsToRun)
});
Expand Down
165 changes: 159 additions & 6 deletions WebApi.MinimalApi/Controllers/UsersController.cs
Original file line number Diff line number Diff line change
@@ -1,27 +1,180 @@
using Microsoft.AspNetCore.Mvc;
using WebApi.MinimalApi.Domain;
using WebApi.MinimalApi.Models;
using AutoMapper;
using Microsoft.AspNetCore.JsonPatch;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Newtonsoft.Json;
using Microsoft.Net.Http.Headers;
using Swashbuckle.AspNetCore.Annotations;

namespace WebApi.MinimalApi.Controllers;

[Route("api/[controller]")]
[ApiController]
public class UsersController : Controller
{
// Чтобы ASP.NET положил что-то в userRepository требуется конфигурация
public UsersController(IUserRepository userRepository)
private readonly IUserRepository userRepository;
private readonly IMapper mapper;
private readonly LinkGenerator linkGenerator;
public UsersController(IUserRepository userRepository, IMapper mapper, LinkGenerator linkGenerator)
{
this.userRepository = userRepository;
this.linkGenerator = linkGenerator;
this.mapper = mapper;
}

[HttpGet("{userId}")]
[HttpGet("{userId}", Name = nameof(GetUserById))]
[HttpHead("{userId}")]
[Produces("application/json", "application/xml")]
[SwaggerResponse(200, "OK", typeof(UserDto))]
[SwaggerResponse(404, "Пользователь не найден")]
public ActionResult<UserDto> GetUserById([FromRoute] Guid userId)
{
throw new NotImplementedException();
var user = userRepository.FindById(userId);
if (user is null)
return NotFound();
var userDto = mapper.Map<UserDto>(user);
return Ok(userDto);
}

[HttpPost]
public IActionResult CreateUser([FromBody] object user)
[Consumes("application/json")]
[Produces("application/json", "application/xml")]
[SwaggerResponse(201, "Пользователь создан")]
[SwaggerResponse(400, "Некорректные входные данные")]
[SwaggerResponse(422, "Ошибка при проверке")]
public IActionResult CreateUser([FromBody] UserDtoToCreate? user)
{
throw new NotImplementedException();
if (user == null)
return BadRequest();
if (!ModelState.IsValid)
return UnprocessableEntity(ModelState);
var userEntity = userRepository.Insert(mapper.Map<UserEntity>(user));
return CreatedAtRoute(nameof(GetUserById), new { userId = userEntity.Id }, userEntity.Id);
}

public static bool IsInvalid(ModelStateDictionary modelState, string key)
{
return modelState.GetValidationState(key) == ModelValidationState.Invalid;
}

[HttpPut("{userId}")]
[Consumes("application/json")]
[Produces("application/json", "application/xml")]
[SwaggerResponse(201, "Пользователь создан")]
[SwaggerResponse(204, "Пользователь обновлен")]
[SwaggerResponse(400, "Некорректные входные данные")]
[SwaggerResponse(422, "Ошибка при проверке")]
public IActionResult UpdateUser([FromRoute] Guid? userId, [FromBody] UserDtoToUpdate? user)
{
if (user == null || IsInvalid(ModelState, nameof(userId)))
return BadRequest();
if (!ModelState.IsValid)
return UnprocessableEntity(ModelState);
userId ??= Guid.NewGuid();
var entity = mapper.Map(user, new UserEntity(userId.Value));
userRepository.UpdateOrInsert(entity, out var inserted);
if (inserted)
return CreatedAtRoute(nameof(GetUserById), new { userId = userId.Value }, entity.Id);
return NoContent();
}

[HttpPatch("{userId}")]
[Consumes("application/json-patch+json")]
[Produces("application/json", "application/xml")]
[SwaggerResponse(204, "Пользователь обновлен")]
[SwaggerResponse(400, "Некорректные входные данные")]
[SwaggerResponse(404, "Пользователь не найден")]
[SwaggerResponse(422, "Ошибка при проверке")]
public IActionResult PartiallyUpdateUser([FromRoute] Guid userId, [FromBody] JsonPatchDocument<UserDtoToUpdate>? patchDocument)
{
if (patchDocument == null)
return BadRequest();
var user = userRepository.FindById(userId);
if (user == null)
return NotFound();
if (!ModelState.IsValid)
return UnprocessableEntity(ModelState);
var toUpdate = mapper.Map<UserDtoToUpdate>(user);
patchDocument.ApplyTo(toUpdate, ModelState);
if (!TryValidateModel(toUpdate))
return UnprocessableEntity(ModelState);
var entity = mapper.Map(toUpdate, user);
userRepository.Update(entity);
return NoContent();
}

[HttpDelete("{userId}")]
[Produces("application/json", "application/xml")]
[SwaggerResponse(204, "Пользователь удален")]
[SwaggerResponse(404, "Пользователь не найден")]
public IActionResult DeleteUser([FromRoute] Guid userId)
{
var user = userRepository.FindById(userId);
if (user == null)
return NotFound();
userRepository.Delete(userId);
return NoContent();
}

[HttpHead("{userId:guid}")]
[HttpHead("{userId}")]
[Produces("application/json", "application/xml")]
[SwaggerResponse(200, "OK", typeof(UserDto))]
[SwaggerResponse(404, "Пользователь не найден")]
public IActionResult Head(Guid userId)
{
var user = userRepository.FindById(userId);
if (user is null)
return NotFound();
HttpContext.Response.ContentType = "application/json; charset=utf-8";
return Ok();
}

[HttpGet(Name = nameof(GetUsers))]
[Produces("application/json", "application/xml")]
[ProducesResponseType(typeof(IEnumerable<UserDto>), 200)]
public ActionResult<IEnumerable<UserDto>> GetUsers([FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 10)
{
if (pageNumber < 1)
pageNumber = 1;
if (pageSize < 1)
pageSize = 1;
if (pageSize > 20)
pageSize = 20;
var users = userRepository.GetPage(pageNumber, pageSize);
var previousPageLink = users.CurrentPage > 1
? linkGenerator.GetUriByRouteValues(HttpContext, nameof(GetUsers), new
{
pageNumber = users.CurrentPage - 1,
pageSize = users.PageSize
}) : null;
var nextPageLink = users.CurrentPage < 20
? linkGenerator.GetUriByRouteValues(HttpContext, nameof(GetUsers), new
{
pageNumber = users.CurrentPage + 1,
pageSize = users.PageSize
}) : null;
var header = new
{
previousPageLink,
nextPageLink,
totalCount = users.TotalCount,
pageSize = users.PageSize,
currentPage = users.CurrentPage,
totalPages = users.TotalPages
};
Response.Headers.Append("X-Pagination", JsonConvert.SerializeObject(header));
return Ok(users);
}

[HttpOptions]
[SwaggerResponse(200, "OK")]
public IActionResult Options()
{
var allowedMethods = new[] { HttpMethods.Post, HttpMethods.Get, HttpMethods.Options };
Response.Headers.AppendList(HeaderNames.Allow, allowedMethods);
return Ok();
}
}
17 changes: 17 additions & 0 deletions WebApi.MinimalApi/Models/UserDtoToCreate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace WebApi.MinimalApi.Models;

public class UserDtoToCreate
{
[Required]
[RegularExpression("^[0-9\\p{L}]*$", ErrorMessage = "Login should contain only letters or digits")]
public string Login { get; set; }

[DefaultValue("John")]
public string FirstName { get; set; }

[DefaultValue("Doe")]
public string LastName { get; set; }
}
17 changes: 17 additions & 0 deletions WebApi.MinimalApi/Models/UserDtoToUpdate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace WebApi.MinimalApi.Models;

public class UserDtoToUpdate
{
[Required]
[RegularExpression("^[0-9\\p{L}]*$", ErrorMessage = "Login should contain only letters or digits")]
public string Login { get; set; }

[Required]
public string FirstName { get; set; }

[Required]
public string LastName { get; set; }
}
30 changes: 28 additions & 2 deletions WebApi.MinimalApi/Program.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,37 @@
using Microsoft.AspNetCore.Mvc.Formatters;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using WebApi.MinimalApi.Domain;
using WebApi.MinimalApi.Models;

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseUrls("http://localhost:5000");
builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options => {
builder.Services.AddControllers(options =>
{
options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
options.ReturnHttpNotAcceptable = true;
options.RespectBrowserAcceptHeader = true;
})
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressModelStateInvalidFilter = true;
options.SuppressMapClientErrors = true;
})
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
options.SerializerSettings.DefaultValueHandling = DefaultValueHandling.Populate;
});

builder.Services.AddSingleton<IUserRepository, InMemoryUserRepository>();
builder.Services.AddAutoMapper(cfg =>
{
cfg.CreateMap<UserEntity, UserDto>()
.ForMember(dest => dest.FullName, opt => opt.MapFrom(src => $"{src.LastName} {src.FirstName}"));
cfg.CreateMap<UserDtoToCreate, UserEntity>();
cfg.CreateMap<UserDtoToUpdate, UserEntity>();
}, new System.Reflection.Assembly[0]);

var app = builder.Build();

app.MapControllers();
Expand Down