Skip to content

Commit

Permalink
REST API Development Best Practices. Automapper and Repositories
Browse files Browse the repository at this point in the history
  • Loading branch information
trevoirwilliams committed Mar 8, 2022
1 parent 93e0f5a commit 01ad6d4
Show file tree
Hide file tree
Showing 14 changed files with 228 additions and 22 deletions.
21 changes: 21 additions & 0 deletions HotelListing.API/Configurations/MapperConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using AutoMapper;
using HotelListing.API.Data;
using HotelListing.API.Models.Country;
using HotelListing.API.Models.Hotel;

namespace HotelListing.API.Configurations
{
public class MapperConfig : Profile
{
public MapperConfig()
{
CreateMap<Country, CreateCountryDto>().ReverseMap();
CreateMap<Country, GetCountryDto>().ReverseMap();
CreateMap<Country, CountryDto>().ReverseMap();
CreateMap<Country, UpdateCountryDto>().ReverseMap();

CreateMap<Hotel, HotelDto>().ReverseMap();

}
}
}
9 changes: 9 additions & 0 deletions HotelListing.API/Contracts/ICountriesRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using HotelListing.API.Data;

namespace HotelListing.API.Contracts
{
public interface ICountriesRepository : IGenericRepository<Country>
{
Task<Country> GetDetails(int id);
}
}
12 changes: 12 additions & 0 deletions HotelListing.API/Contracts/IGenericRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace HotelListing.API.Contracts
{
public interface IGenericRepository<T> where T : class
{
Task<T> GetAsync(int? id);
Task<List<T>> GetAllAsync();
Task<T> AddAsync(T entity);
Task DeleteAsync(int id);
Task UpdateAsync(T entity);
Task<bool> Exists(int id);
}
}
59 changes: 37 additions & 22 deletions HotelListing.API/Controllers/CountriesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,61 +6,76 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using HotelListing.API.Data;
using HotelListing.API.Models.Country;
using AutoMapper;
using HotelListing.API.Contracts;

namespace HotelListing.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class CountriesController : ControllerBase
{
private readonly HotelListingDbContext _context;
private readonly IMapper _mapper;
private readonly ICountriesRepository _countriesRepository;

public CountriesController(HotelListingDbContext context)
public CountriesController(IMapper mapper, ICountriesRepository countriesRepository)
{
_context = context;
this._mapper = mapper;
this._countriesRepository = countriesRepository;
}

// GET: api/Countries
[HttpGet]
public async Task<ActionResult<IEnumerable<Country>>> GetCountries()
public async Task<ActionResult<IEnumerable<GetCountryDto>>> GetCountries()
{
var countries = await _context.Countries.ToListAsync();
return Ok(countries);
var countries = await _countriesRepository.GetAllAsync();
var records = _mapper.Map<List<GetCountryDto>>(countries);
return Ok(records);
}

// GET: api/Countries/5
[HttpGet("{id}")]
public async Task<ActionResult<Country>> GetCountry(int id)
public async Task<ActionResult<CountryDto>> GetCountry(int id)
{
var country = await _context.Countries.FindAsync(id);
var country = await _countriesRepository.GetDetails(id);

if (country == null)
{
return NotFound();
}

return Ok(country);
var countryDto = _mapper.Map<CountryDto>(country);

return Ok(countryDto);
}

// PUT: api/Countries/5
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPut("{id}")]
public async Task<IActionResult> PutCountry(int id, Country country)
public async Task<IActionResult> PutCountry(int id, UpdateCountryDto updateCountryDto)
{
if (id != country.Id)
if (id != updateCountryDto.Id)
{
return BadRequest("Invalid Record Id");
}

_context.Entry(country).State = EntityState.Modified;
var country = await _countriesRepository.GetAsync(id);

if (country == null)
{
return NotFound();
}

_mapper.Map(updateCountryDto, country);

try
{
await _context.SaveChangesAsync();
await _countriesRepository.UpdateAsync(country);
}
catch (DbUpdateConcurrencyException)
{
if (!CountryExists(id))
if (!await CountryExists(id))
{
return NotFound();
}
Expand All @@ -76,10 +91,11 @@ public async Task<IActionResult> PutCountry(int id, Country country)
// POST: api/Countries
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPost]
public async Task<ActionResult<Country>> PostCountry(Country country)
public async Task<ActionResult<Country>> PostCountry(CreateCountryDto createCountryDto)
{
_context.Countries.Add(country);
await _context.SaveChangesAsync();
var country = _mapper.Map<Country>(createCountryDto);

await _countriesRepository.AddAsync(country);

return CreatedAtAction("GetCountry", new { id = country.Id }, country);
}
Expand All @@ -88,21 +104,20 @@ public async Task<ActionResult<Country>> PostCountry(Country country)
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteCountry(int id)
{
var country = await _context.Countries.FindAsync(id);
var country = await _countriesRepository.GetAsync(id);
if (country == null)
{
return NotFound();
}

_context.Countries.Remove(country);
await _context.SaveChangesAsync();
await _countriesRepository.DeleteAsync(id);

return NoContent();
}

private bool CountryExists(int id)
private async Task<bool> CountryExists(int id)
{
return _context.Countries.Any(e => e.Id == id);
return await _countriesRepository.Exists(id);
}
}
}
1 change: 1 addition & 0 deletions HotelListing.API/HotelListing.API.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
Expand Down
12 changes: 12 additions & 0 deletions HotelListing.API/Models/Country/BaseCountryDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations;

namespace HotelListing.API.Models.Country
{
public abstract class BaseCountryDto
{
[Required]
public string Name { get; set; }
public string ShortName { get; set; }

}
}
10 changes: 10 additions & 0 deletions HotelListing.API/Models/Country/CountryDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using HotelListing.API.Models.Hotel;

namespace HotelListing.API.Models.Country
{
public class CountryDto : BaseCountryDto
{
public int Id { get; set; }
public List<HotelDto> Hotels { get; set; }
}
}
8 changes: 8 additions & 0 deletions HotelListing.API/Models/Country/CreateCountryDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.ComponentModel.DataAnnotations;

namespace HotelListing.API.Models.Country
{
public class CreateCountryDto : BaseCountryDto
{
}
}
9 changes: 9 additions & 0 deletions HotelListing.API/Models/Country/GetCountryDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.ComponentModel.DataAnnotations.Schema;

namespace HotelListing.API.Models.Country
{
public class GetCountryDto : BaseCountryDto
{
public int Id { get; set; }
}
}
7 changes: 7 additions & 0 deletions HotelListing.API/Models/Country/UpdateCountryDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace HotelListing.API.Models.Country
{
public class UpdateCountryDto : BaseCountryDto
{
public int Id { get; set; }
}
}
11 changes: 11 additions & 0 deletions HotelListing.API/Models/Hotel/HotelDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace HotelListing.API.Models.Hotel
{
public class HotelDto
{
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public double Rating { get; set; }
public int CountryId { get; set; }
}
}
8 changes: 8 additions & 0 deletions HotelListing.API/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using HotelListing.API.Configurations;
using HotelListing.API.Contracts;
using HotelListing.API.Data;
using HotelListing.API.Repository;
using Microsoft.EntityFrameworkCore;
using Serilog;

Expand All @@ -25,6 +28,11 @@

builder.Host.UseSerilog((ctx, lc) => lc.WriteTo.Console().ReadFrom.Configuration(ctx.Configuration));

builder.Services.AddAutoMapper(typeof(MapperConfig));

builder.Services.AddScoped(typeof(IGenericRepository<>), typeof(GenericRepository<>));
builder.Services.AddScoped<ICountriesRepository, CountriesRepository>();

var app = builder.Build();

// Configure the HTTP request pipeline.
Expand Down
22 changes: 22 additions & 0 deletions HotelListing.API/Repository/CountriesRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using HotelListing.API.Contracts;
using HotelListing.API.Data;
using Microsoft.EntityFrameworkCore;

namespace HotelListing.API.Repository
{
public class CountriesRepository : GenericRepository<Country>, ICountriesRepository
{
private readonly HotelListingDbContext _context;

public CountriesRepository(HotelListingDbContext context) : base(context)
{
this._context = context;
}

public async Task<Country> GetDetails(int id)
{
return await _context.Countries.Include(q => q.Hotels)
.FirstOrDefaultAsync(q => q.Id == id);
}
}
}
61 changes: 61 additions & 0 deletions HotelListing.API/Repository/GenericRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using AutoMapper;
using AutoMapper.QueryableExtensions;
using HotelListing.API.Contracts;
using HotelListing.API.Data;
using HotelListing.API.Models.Country;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

namespace HotelListing.API.Repository
{
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
private readonly HotelListingDbContext _context;

public GenericRepository(HotelListingDbContext context)
{
this._context = context;
}

public async Task<T> AddAsync(T entity)
{
await _context.AddAsync(entity);
await _context.SaveChangesAsync();
return entity;
}

public async Task DeleteAsync(int id)
{
var entity = await GetAsync(id);
_context.Set<T>().Remove(entity);
await _context.SaveChangesAsync();
}

public async Task<bool> Exists(int id)
{
var entity = await GetAsync(id);
return entity != null;
}

public async Task<List<T>> GetAllAsync()
{
return await _context.Set<T>().ToListAsync();
}

public async Task<T> GetAsync(int? id)
{
if (id is null)
{
return null;
}

return await _context.Set<T>().FindAsync(id);
}

public async Task UpdateAsync(T entity)
{
_context.Update(entity);
await _context.SaveChangesAsync();
}
}
}

0 comments on commit 01ad6d4

Please sign in to comment.