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

Суровнев, Толченов, Шишкин, Коуров #31

Open
wants to merge 10 commits 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
153 changes: 147 additions & 6 deletions WebApi/Controllers/UsersController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using AutoMapper;
using Game.Domain;
using Microsoft.AspNetCore.JsonPatch;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Newtonsoft.Json;
using WebApi.Models;

namespace WebApi.Controllers
Expand All @@ -9,21 +17,154 @@ namespace WebApi.Controllers
[ApiController]
public class UsersController : Controller
{
// Чтобы ASP.NET положил что-то в userRepository требуется конфигурация
public UsersController(IUserRepository userRepository)
private readonly IUserRepository usersRepository;
private readonly IMapper mapper;
private readonly LinkGenerator linkGenerator;

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

[HttpGet("{userId}")]
[HttpGet("{userId:guid}", Name = nameof(GetUserById))]
[HttpHead("{userId}")]
[Produces("application/json", "application/xml")]
public ActionResult<UserDto> GetUserById([FromRoute] Guid userId)
{
throw new NotImplementedException();
var user = usersRepository.FindById(userId);
if (user is null)
return NotFound();

var userDto = mapper.Map<UserDto>(user);
return Ok(userDto);
}

[HttpPost]
public IActionResult CreateUser([FromBody] object user)
[Produces("application/json", "application/xml")]
public IActionResult CreateUser([FromBody] CreateUserDto user)
{
if (user is null)
return BadRequest();

if (string.IsNullOrEmpty(user.Login) || !user.Login.All(char.IsLetterOrDigit))
{
ModelState.AddModelError(nameof(CreateUserDto.Login),
"Login should contain only letters or digits");
}

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

var userEntity = mapper.Map<UserEntity>(user);
var createdUserEntity = usersRepository.Insert(userEntity);
var response = CreatedAtRoute(
nameof(GetUserById),
new {userId = createdUserEntity.Id},
createdUserEntity.Id);

return response;
}

[HttpPut("{userId}")]
[Produces("application/json", "application/xml")]
public IActionResult UpdateUser([FromRoute] Guid userId, [FromBody] UpdateUserDto user)
{
if (user is null || userId == Guid.Empty)
return BadRequest();

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

var userEntity = new UserEntity(userId);
mapper.Map(user, userEntity);

usersRepository.UpdateOrInsert(userEntity, out var isInserted);

return isInserted
? CreatedAtRoute(
nameof(GetUserById),
new {userId = userEntity.Id},
userEntity.Id)
: NoContent();
}

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

var userEntity = usersRepository.FindById(userId);
if (userEntity is null)
return NotFound();

var updateDto = mapper.Map<UpdateUserDto>(userEntity);
patchDoc.ApplyTo(updateDto, ModelState);

TryValidateModel(updateDto);
if (!ModelState.IsValid)
return UnprocessableEntity(ModelState);

mapper.Map(updateDto, userEntity);
usersRepository.Update(userEntity);

return NoContent();
}

[HttpDelete("{userId}")]
[Produces("application/json", "application/xml")]
public IActionResult DeleteUser([FromRoute] Guid userId)
{
var user = usersRepository.FindById(userId);
if (user is null)
return NotFound();

usersRepository.Delete(userId);
return NoContent();
}

[HttpGet(Name = nameof(GetUsers))]
[Produces("application/json", "application/xml")]
public ActionResult<IEnumerable<UserDto>> GetUsers(int pageNumber = 1, int pageSize = 10)
{
pageNumber = Math.Max(pageNumber, 1);
pageSize = Math.Min(Math.Max(pageSize, 1), 20);
var page = usersRepository.GetPage(pageNumber, pageSize);

var paginationHeader = new
{
previousPageLink = page.HasPrevious
? CreateGetUsersUri(page.CurrentPage - 1, page.PageSize)
: null,
nextPageLink = page.HasNext
? CreateGetUsersUri(page.CurrentPage + 1, page.PageSize)
: null,
totalCount = page.TotalCount,
pageSize = page.PageSize,
currentPage = page.CurrentPage,
totalPages = page.TotalPages
};
Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(paginationHeader));

return Ok(page);
}

[HttpOptions]
public IActionResult GetUsersOptions()
{
throw new NotImplementedException();
Response.Headers.Add("Allow", "POST,GET,OPTIONS");
return Ok();
}

private string CreateGetUsersUri(int pageNumber, int pageSize)
=> linkGenerator.GetUriByRouteValues(HttpContext, nameof(GetUsers),
new
{
pageNumber,
pageSize
});
}
}
17 changes: 17 additions & 0 deletions WebApi/Models/CreateUserDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace WebApi.Models
{
public class CreateUserDto
{
[Required]
public string Login { get; set; }

[DefaultValue("Doe")]
public string LastName { get; set; }

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

namespace WebApi.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 LastName { get; set; }

[Required]
public string FirstName { get; set; }
}
}
34 changes: 32 additions & 2 deletions WebApi/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
using System;
using System.Reflection;
using AutoMapper;
using Game.Domain;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using WebApi.Models;

namespace WebApi
{
Expand All @@ -17,17 +26,38 @@ public Startup(IConfiguration configuration)
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers()
services.AddSingleton<IUserRepository, InMemoryUserRepository>();
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.DefaultValueHandling = DefaultValueHandling.Populate;
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
options.SerializerSettings.Converters.Add(new StringEnumConverter());
});

services.AddAutoMapper(cfg =>
{
cfg.CreateMap<UserEntity, UserDto>()
.ForMember(d => d.FullName, opt => opt.MapFrom(src => $"{src.LastName} {src.FirstName}"));
cfg.CreateMap<CreateUserDto, UserEntity>();
cfg.CreateMap<UpdateUserDto, UserEntity>();
cfg.CreateMap<UserEntity, UpdateUserDto>();
}, Array.Empty<Assembly>());
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseDeveloperExceptionPage();
//app.UseDeveloperExceptionPage();

app.UseHttpsRedirection();

Expand Down