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

Турмухамбетов Амир Иванов Станислав #46

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
4 changes: 2 additions & 2 deletions Tests/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ public static void Main()
{
var testsToRun = new string[]
{
typeof(Task1_GetUserByIdTests).FullName,
//typeof(Task1_GetUserByIdTests).FullName,
//typeof(Task2_CreateUserTests).FullName,
//typeof(Task3_UpdateUserTests).FullName,
typeof(Task3_UpdateUserTests).FullName,
//typeof(Task4_PartiallyUpdateUserTests).FullName,
//typeof(Task5_DeleteUserTests).FullName,
//typeof(Task6_HeadUserByIdTests).FullName,
Expand Down
2 changes: 1 addition & 1 deletion Tests/UsersApiTestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ protected void DeleteUser(string userId)
request.Method = HttpMethod.Delete;
request.RequestUri = BuildUsersByIdUri(userId);
request.Headers.Add("Accept", "*/*");
var response = HttpClient.Send(request);
var response = HttpClient.SendAsync(request).GetAwaiter().GetResult();;

response.StatusCode.Should().Be(HttpStatusCode.NoContent);
response.ShouldNotHaveHeader("Content-Type");
Expand Down
169 changes: 161 additions & 8 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 System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using AutoMapper;
using Microsoft.AspNetCore.JsonPatch;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using WebApi.MinimalApi.Domain;
using WebApi.MinimalApi.Models;

namespace WebApi.MinimalApi.Controllers;



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

private IUserRepository userRepository;
private IMapper mapper;
private LinkGenerator linkGenerator;
public UsersController(IUserRepository userRepository, IMapper mapper, LinkGenerator linkGenerator)
{
this.userRepository = userRepository;
this.mapper = mapper;
this.linkGenerator = linkGenerator;
}

[HttpGet("{userId}")]
public ActionResult<UserDto> GetUserById([FromRoute] Guid userId)
[Produces("application/json", "application/xml")]
[HttpGet("{userId}", Name = nameof(GetUserById))]
[HttpHead("{userId}")]
public ActionResult<Models.UserDto> GetUserById([FromRoute] System.Guid userId)
{
throw new NotImplementedException();
var user = userRepository.FindById(userId);
if (user is null)
{
return NotFound();
}
if (HttpContext.Request.Method == HttpMethods.Head)
{
Response.Headers["Content-Type"] = "application/json; charset=utf-8";
return Ok();
}
var userDto = mapper.Map<Models.UserDto>(user);
return Ok(userDto);
}


[Produces("application/json", "application/xml")]
[HttpPost]
public IActionResult CreateUser([FromBody] object user)
public IActionResult CreateUser([FromBody] CreateUserDto createUser)
{
var createdUserEntity = mapper.Map<UserEntity>(createUser);
if (createUser == null)
{
return BadRequest();
}

if (createUser.Login == null || !createUser.Login.All(char.IsLetterOrDigit))
{
ModelState.AddModelError(nameof(createUser.Login), "Сообщение об ошибке");
return UnprocessableEntity(ModelState);
}
var insertedUser = userRepository.Insert(createdUserEntity);
return CreatedAtRoute(
nameof(GetUserById),
new { userId = insertedUser.Id },
insertedUser.Id);
}

[Produces("application/json", "application/xml")]
[HttpPut("{userId}")]
public IActionResult UpdateUser([FromBody] UpdateUserDto createUser , [FromRoute] Guid userId)
{

var createdUserEntity = mapper.Map(new UserEntity(userId), mapper.Map<UserEntity>(createUser));
if (createUser is null || userId == Guid.Empty)
{
return BadRequest();
}

if (!ModelState.IsValid)
{
return UnprocessableEntity(ModelState);
}

userRepository.UpdateOrInsert(createdUserEntity, out bool isInsert);

if (isInsert)
return CreatedAtAction(
nameof(GetUserById),
new { userId = createdUserEntity.Id },
createdUserEntity.Id);

return NoContent();
}

[Produces("application/json", "application/xml")]
[HttpPatch("{userId}")]
public IActionResult PartiallyUpdateUser([FromBody] JsonPatchDocument<UpdateUserDto> patchDoc, [FromRoute] Guid userId)
{

if (patchDoc is null)
{
return BadRequest();
}
var user = userRepository.FindById(userId);
if (user == null || userId == Guid.Empty)
{
return NotFound();
}

var updateDtoUser = mapper.Map<UpdateUserDto>(user);
patchDoc.ApplyTo(updateDtoUser, ModelState);

if (!TryValidateModel(updateDtoUser))
{
return UnprocessableEntity(ModelState);
}

var createdUserEntity = mapper.Map(new UserEntity(userId), mapper.Map<UserEntity>(updateDtoUser));

userRepository.Update(createdUserEntity);

return NoContent();
}


[HttpDelete("{userId}")]
public IActionResult DeleteUser([FromRoute] System.Guid userId)
{
var user = userRepository.FindById(userId);
if (user == null)
{
return NotFound();
}
userRepository.Delete(userId);
return NoContent();
}

[HttpGet]
[Produces("application/json", "application/xml")]
public ActionResult<IEnumerable<Models.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 pageList = userRepository.GetPage(pageNumber, pageSize);
var users = mapper.Map<IEnumerable<Models.UserDto>>(pageList);
var paginationHeader = new
{
previousPageLink = pageList.HasPrevious ?
linkGenerator.GetUriByAction(HttpContext, nameof(GetUsers), values: new { pageNumber = pageNumber - 1, pageSize }) : null,
nextPageLink = pageList.HasNext ?
linkGenerator.GetUriByAction(HttpContext, nameof(GetUsers), values: new { pageNumber = pageNumber + 1, pageSize }) : null,
totalCount = pageList.TotalCount,
pageSize = pageSize,
currentPage = pageNumber,
totalPages = (int)Math.Ceiling((double)pageList.TotalCount / pageSize)
};
Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(paginationHeader));
return Ok(users);
}

[HttpOptions]
public IActionResult Options()
{
throw new NotImplementedException();
Response.Headers.Add("Allow", "GET, POST, OPTIONS");
return Ok();
}
}
14 changes: 14 additions & 0 deletions WebApi.MinimalApi/Models/CreateUserDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace WebApi.MinimalApi.Models;

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

namespace WebApi.MinimalApi.Models;

public class UpdateUserDto
{
[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; }
}
2 changes: 2 additions & 0 deletions WebApi.MinimalApi/Models/UserDto.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.ComponentModel.DataAnnotations;

namespace WebApi.MinimalApi.Models;

public class UserDto
Expand Down
33 changes: 32 additions & 1 deletion WebApi.MinimalApi/Program.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,40 @@
using Microsoft.AspNetCore.Mvc.Formatters;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using WebApi.MinimalApi.Domain;
using WebApi.MinimalApi.Models;
using WebApi.MinimalApi.Controllers;

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseUrls("http://localhost:5000");
builder.Services.AddControllers()
builder.Services.AddSingleton<IUserRepository, InMemoryUserRepository>();
builder.Services.AddAutoMapper(cfg =>
{
cfg.CreateMap<UserEntity, UserDto>()
.ForMember(dest => dest.FullName, opt => opt.MapFrom(src => $"{src.FirstName} {src.LastName}"));
cfg.CreateMap<CreateUserDto, UserEntity>();
cfg.CreateMap<UpdateUserDto, UserEntity>();
cfg.CreateMap<UserEntity,UpdateUserDto>();
}, new System.Reflection.Assembly[0]);


builder.Services.AddControllers(options =>
{
// Этот OutputFormatter позволяет возвращать данные в XML, если требуется.
options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
// Эта настройка позволяет отвечать кодом 406 Not Acceptable на запросы неизвестных форматов.
options.ReturnHttpNotAcceptable = true;
// Эта настройка приводит к игнорированию заголовка Accept, когда он содержит */*
// Здесь она нужна, чтобы в этом случае ответ возвращался в формате JSON
options.RespectBrowserAcceptHeader = true;
})
.ConfigureApiBehaviorOptions(options => {
options.SuppressModelStateInvalidFilter = true;
options.SuppressMapClientErrors = true;
}).AddNewtonsoftJson(options =>
{
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
options.SerializerSettings.DefaultValueHandling = DefaultValueHandling.Populate;
});

var app = builder.Build();
Expand Down