Skip to content
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
45 changes: 45 additions & 0 deletions Business/Services/TodoService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Threading.Tasks;
using TodoApi.Models;
using TodoApiDTO.Models;

namespace TodoApiDTO.Services
{
public class TodoService : ITodoService
{
private readonly ITodoRepository _todoRepository;

public TodoService(ITodoRepository todoRepository)
{
_todoRepository = todoRepository;
}

public async Task<TodoItem> Create(TodoItemDTO createInput)
{
if (createInput == null) throw new ArgumentNullException(nameof(createInput));
return await _todoRepository.Create(createInput);
}

public async Task<TodoItemActionResult> Delete(long itemToDeleteId)
{
return await _todoRepository.Delete(itemToDeleteId);
}

public async Task<TodoItem> GetById(long itemId)
{
return await _todoRepository.GetById(itemId);
}

public async Task<TodoItemActionResult> Update(TodoItemDTO updateInput)
{
if (updateInput == null) throw new ArgumentNullException(nameof(updateInput));
return await _todoRepository.Update(updateInput);
}

public DbSet<TodoItem> GetList()
{
return _todoRepository.GetList();
}
}
}
13 changes: 13 additions & 0 deletions Business/Utilities/ServiceHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Microsoft.Extensions.DependencyInjection;
using TodoApi.Models;

namespace TodoApiDTO.Services
{
public static class ServiceHelpers
{
public static void Configure(IServiceCollection services)
{
services.AddScoped<ITodoService, TodoService>();
}
}
}
157 changes: 101 additions & 56 deletions Controllers/TodoItemsController.cs
Original file line number Diff line number Diff line change
@@ -1,116 +1,161 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using TodoApi.Models;
using TodoApiDTO.Models;

namespace TodoApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TodoItemsController : ControllerBase
{
private readonly TodoContext _context;
private readonly ITodoService _todoService;
private readonly ILogger<TodoItemsController> _logger;

public TodoItemsController(TodoContext context)
public TodoItemsController(ITodoService todoService, ILogger<TodoItemsController> logger)
{
_context = context;
_todoService = todoService;
_logger = logger;
}

[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
{
return await _context.TodoItems
.Select(x => ItemToDTO(x))
.ToListAsync();
try
{
return await _todoService.GetList()
.Select(x => TodoItemDTO.ItemToDTO(x))
.ToListAsync();
}
catch (Exception ex)
{
_logger.LogError($"GetTodoItems: {ex.Message}\n{ex.StackTrace}");
return BadRequest(ex.Message);
}
}

[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);

if (todoItem == null)
if (id < 0)
{
return NotFound();
_logger.LogError($"GetTodoItem: invalid id value ({id})");
return BadRequest("Invalid id");
}

return ItemToDTO(todoItem);
try
{
var todoItem = await _todoService.GetById(id);
if (todoItem == null)
{
_logger.LogError($"GetTodoItem: item not found (id:{id})");
return NotFound("TODO item not found");
}

return TodoItemDTO.ItemToDTO(todoItem);
}
catch (Exception ex)
{
_logger.LogError($"GetTodoItem (id:{id}): {ex.Message}\n{ex.StackTrace}");
return BadRequest(ex.Message);
}
}

[HttpPut("{id}")]
[HttpPut("{id}/update")]
public async Task<IActionResult> UpdateTodoItem(long id, TodoItemDTO todoItemDTO)
{
if (id != todoItemDTO.Id)
if (id < 0)
{
return BadRequest();
_logger.LogError($"UpdateTodoItem: invalid id value ({id})");
return BadRequest("Invalid id");
}

var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
if (todoItemDTO == null)
{
return NotFound();
_logger.LogError($"UpdateTodoItem: request is null (id:{id})");
return BadRequest("Request is null");
}

todoItem.Name = todoItemDTO.Name;
todoItem.IsComplete = todoItemDTO.IsComplete;
if (id != todoItemDTO.Id)
{
_logger.LogError($"UpdateTodoItem: ids mismatch (parameter id:{id}, request id:{todoItemDTO?.Id})");
return BadRequest("Ids mismatch");
}

try
{
await _context.SaveChangesAsync();
var result = await _todoService.Update(todoItemDTO);
return HandleActionResult(result, $"UpdateTodoItem (id:{id})");
}
catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
catch (Exception ex)
{
return NotFound();
_logger.LogError($"UpdateTodoItem (id:{id}): {ex.Message}\n{ex.StackTrace}");
return BadRequest(ex.Message);
}

return NoContent();
}

[Route("create")]
[HttpPost]
public async Task<ActionResult<TodoItemDTO>> CreateTodoItem(TodoItemDTO todoItemDTO)
{
var todoItem = new TodoItem
try
{
IsComplete = todoItemDTO.IsComplete,
Name = todoItemDTO.Name
};

_context.TodoItems.Add(todoItem);
await _context.SaveChangesAsync();

return CreatedAtAction(
nameof(GetTodoItem),
new { id = todoItem.Id },
ItemToDTO(todoItem));
var todoItem = await _todoService.Create(todoItemDTO);
if (todoItem == null)
{
_logger.LogError($"CreateTodoItem: cannot create item");
return BadRequest("Couldn't create TODO item");
}

return CreatedAtAction(
nameof(GetTodoItem),
new { id = todoItem.Id },
TodoItemDTO.ItemToDTO(todoItem));
}
catch (Exception ex)
{
_logger.LogError($"CreateTodoItem: {ex.Message}\n{ex.StackTrace}");
return BadRequest(ex.Message);
}
}

[HttpDelete("{id}")]
[HttpDelete("{id}/delete")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);

if (todoItem == null)
if (id < 0)
{
return NotFound();
_logger.LogError($"DeleteTodoItem: invalid id ({id})");
return BadRequest("Invalid id");
}

_context.TodoItems.Remove(todoItem);
await _context.SaveChangesAsync();

return NoContent();
try
{
var result = await _todoService.Delete(id);
return HandleActionResult(result, $"DeleteTodoItem (id:{id})");
}
catch (Exception ex)
{
_logger.LogError($"DeleteTodoItem (id:{id}): {ex.Message}\n{ex.StackTrace}");
return BadRequest(ex.Message);
}
}

private bool TodoItemExists(long id) =>
_context.TodoItems.Any(e => e.Id == id);

private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>
new TodoItemDTO

private IActionResult HandleActionResult(TodoItemActionResult result, string actionName)
{
switch (result)
{
Id = todoItem.Id,
Name = todoItem.Name,
IsComplete = todoItem.IsComplete
};
case TodoItemActionResult.Success:
return NoContent();
case TodoItemActionResult.NotFound:
_logger.LogError($"{actionName}: item not found");
return NotFound("TODO item not found");
default:
return NoContent();
}
}
}
}
75 changes: 75 additions & 0 deletions Data/Repositories/TodoRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;
using TodoApi.Data;
using TodoApi.Models;
using TodoApiDTO.Models;

namespace TodoApiDTO.Data
{
public class TodoRepository : ITodoRepository
{
private readonly TodoContext _context;

public TodoRepository(TodoContext context)
{
_context = context;
}

public async Task<TodoItem> Create(TodoItemDTO input)
{
var createdTodoItem = await _context.TodoItems.AddAsync(new TodoItem { IsComplete = input.IsComplete, Name = input.Name });
await _context.SaveChangesAsync();

return createdTodoItem?.Entity;
}

public async Task<TodoItemActionResult> Delete(long itemToDeleteId)
{
var itemToDelete = await _context.TodoItems.FindAsync(itemToDeleteId);

if (itemToDelete == null)
{
return TodoItemActionResult.NotFound;
}

_context.TodoItems.Remove(itemToDelete);
await _context.SaveChangesAsync();

return TodoItemActionResult.Success;
}

public async Task<TodoItem> GetById(long itemId)
{
return await _context.TodoItems.FindAsync(itemId);
}

public DbSet<TodoItem> GetList()
{
return _context.TodoItems;
}

public async Task<TodoItemActionResult> Update(TodoItemDTO input)
{
var todoItemToUpdate = await _context.TodoItems.FindAsync(input.Id);
if (todoItemToUpdate == null)
{
return TodoItemActionResult.NotFound;
}

todoItemToUpdate.Name = input.Name;
todoItemToUpdate.IsComplete = input.IsComplete;

try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex) when (!_context.TodoItems.Any(e => e.Id == input.Id))
{
throw ex;
}

return TodoItemActionResult.Success;
}
}
}
3 changes: 2 additions & 1 deletion Models/TodoContext.cs → Data/TodoContext.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

namespace TodoApi.Models
namespace TodoApi.Data
{
public class TodoContext : DbContext
{
Expand Down
21 changes: 21 additions & 0 deletions Data/Utilities/DataHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using TodoApi.Data;
using TodoApiDTO.Models;

namespace TodoApiDTO.Data
{
public static class DataHelpers
{
public static void SetDbConnection(IServiceCollection services, IConfiguration config)
{
services.AddDbContext<TodoContext>(opt => opt.UseSqlServer(config.GetConnectionString("MsSqlAuth")));
}

public static void Configure(IServiceCollection services)
{
services.AddScoped<ITodoRepository, TodoRepository>();
}
}
}
Loading