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
110 changes: 67 additions & 43 deletions Controllers/TodoItemsController.cs
Original file line number Diff line number Diff line change
@@ -1,116 +1,140 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using TodoApi.Models;
using TodoApi.Services;

namespace TodoApi.Controllers
{
[Produces("application/json")]
[Route("api/[controller]")]
[ApiController]
public class TodoItemsController : ControllerBase
public sealed 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 ?? throw new ArgumentNullException(nameof(todoService));

_logger = logger;
}

/// <summary>List of all todo items</summary>
/// <returns>Returns the list of all todo items</returns>
/// <response code="200">Returns the list of all todo items</response>
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems(CancellationToken cancellationToken)
{
return await _context.TodoItems
.Select(x => ItemToDTO(x))
.ToListAsync();
return await _todoService
.List()
.ToListAsync(cancellationToken);
}

[HttpGet("{id}")]
/// <summary>Get the todo item for the id</summary>
/// <param name="id">The todo's id to get the todo item for</param>
/// <returns>The todo item for the id</returns>
/// <response code="200">The todo item for the id</response>
/// <response code="404">The todo item for this id not found</response>
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
var todoItem = await _todoService.FindAsync(id);

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

return ItemToDTO(todoItem);
return TodoItemDTO.From(todoItem);
}

/// <summary>Update the todo item for the id</summary>
/// <param name="id">The todo's id to update the todo item for</param>
/// <param name="todoItemDTO">The todo's information to update for</param>
/// <param name="cancellationToken">Cancellation token to monitor cancellation</param>
/// <response code="204">The todo item for the id updated successfully</response>
/// <response code="404">The todo item for this id not found</response>
/// <response code="400">The Id field value in request body doesnt match with id parameter</response>
[HttpPut("{id}")]
public async Task<IActionResult> UpdateTodoItem(long id, TodoItemDTO todoItemDTO)
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> UpdateTodoItem(long id, TodoItemDTO todoItemDTO, CancellationToken cancellationToken)
{
if (id != todoItemDTO.Id)
{
return BadRequest();
}

var todoItem = await _context.TodoItems.FindAsync(id);
var todoItem = await _todoService.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}

todoItem.Name = todoItemDTO.Name;
todoItem.IsComplete = todoItemDTO.IsComplete;
todoItem.Update(todoItemDTO.Name, todoItemDTO.IsComplete);

try
{
await _context.SaveChangesAsync();
await _todoService.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
catch (DbUpdateConcurrencyException) when (!_todoService.TodoItemExists(id))
{
return NotFound();
}

return NoContent();
}

/// <summary>Create new todo item</summary>
/// <param name="todoItemDTO">The todo's information to create for</param>
/// <param name="cancellationToken">Cancellation token to monitor cancellation</param>
/// <returns>The created todo item</returns>
/// <response code="201">New todo item created successfully</response>
[HttpPost]
public async Task<ActionResult<TodoItemDTO>> CreateTodoItem(TodoItemDTO todoItemDTO)
[ProducesResponseType(StatusCodes.Status201Created)]
public async Task<ActionResult<TodoItemDTO>> CreateTodoItem(TodoItemDTO todoItemDTO, CancellationToken cancellationToken)
{
var todoItem = new TodoItem
{
IsComplete = todoItemDTO.IsComplete,
Name = todoItemDTO.Name
};
var todoItem = todoItemDTO.ToTodoItem();

_context.TodoItems.Add(todoItem);
await _context.SaveChangesAsync();
await _todoService.CreateAsync(todoItem, cancellationToken);

return CreatedAtAction(
nameof(GetTodoItem),
new { id = todoItem.Id },
ItemToDTO(todoItem));
TodoItemDTO.From(todoItem));
}

/// <summary>Delete todo item for this id</summary>
/// <param name="id">The todo's id to delete the todo item for</param>
/// <param name="cancellationToken">Cancellation token to monitor cancellation</param>
/// <response code="204">The todo item for the id deleted successfully</response>
/// <response code="404">The todo item for this id not found</response>
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> DeleteTodoItem(long id, CancellationToken cancellationToken)
{
var todoItem = await _context.TodoItems.FindAsync(id);
var todoItem = await _todoService.FindAsync(id);

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

_context.TodoItems.Remove(todoItem);
await _context.SaveChangesAsync();
await _todoService.RemoveAsync(todoItem, cancellationToken);

return NoContent();
}

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

private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>
new TodoItemDTO
{
Id = todoItem.Id,
Name = todoItem.Name,
IsComplete = todoItem.IsComplete
};
}
}
}
62 changes: 62 additions & 0 deletions Migrations/20230822121205_Initial.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 41 additions & 0 deletions Migrations/20230822121205_Initial.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using Microsoft.EntityFrameworkCore.Migrations;

namespace TodoApiDTO.Migrations
{
public partial class Initial : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "TodoItems",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(nullable: true),
IsComplete = table.Column<bool>(nullable: false),
Secret = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_TodoItems", x => x.Id);
});

migrationBuilder.InsertData(
table: "TodoItems",
columns: new[] { "Id", "IsComplete", "Name", "Secret" },
values: new object[] { 1L, true, "Todo #1", "Secret A" });

migrationBuilder.InsertData(
table: "TodoItems",
columns: new[] { "Id", "IsComplete", "Name", "Secret" },
values: new object[] { 2L, false, "Todo #2", "Secret B" });
}

protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "TodoItems");
}
}
}
60 changes: 60 additions & 0 deletions Migrations/TodoContextModelSnapshot.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using TodoApi.Models;

namespace TodoApiDTO.Migrations
{
[DbContext(typeof(TodoContext))]
partial class TodoContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.0")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);

modelBuilder.Entity("TodoApi.Models.TodoItem", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);

b.Property<bool>("IsComplete")
.HasColumnType("bit");

b.Property<string>("Name")
.HasColumnType("nvarchar(max)");

b.Property<string>("Secret")
.HasColumnType("nvarchar(max)");

b.HasKey("Id");

b.ToTable("TodoItems");

b.HasData(
new
{
Id = 1L,
IsComplete = true,
Name = "Todo #1",
Secret = "Secret A"
},
new
{
Id = 2L,
IsComplete = false,
Name = "Todo #2",
Secret = "Secret B"
});
});
#pragma warning restore 612, 618
}
}
}
14 changes: 10 additions & 4 deletions Models/TodoContext.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
using Microsoft.EntityFrameworkCore;
using TodoApi.Seeds;

namespace TodoApi.Models
{
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}
: base(options) { }

public DbSet<TodoItem> TodoItems { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
SeedTodo.Seed(modelBuilder);

base.OnModelCreating(modelBuilder);
}
}
}
}
12 changes: 9 additions & 3 deletions Models/TodoItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,15 @@
public class TodoItem
{
public long Id { get; set; }
public string Name { get; set; }
public string Name { get; set; } = "";
public bool IsComplete { get; set; }
public string Secret { get; set; }
public string Secret { get; set; } = "";

public void Update(string name, bool isComplete)
{
Name = name;
IsComplete = isComplete;
}
}
#endregion
}
}
Loading