diff --git a/docs/en/Tutorials/Todo/Index.md b/docs/en/Tutorials/Todo/Index.md new file mode 100644 index 0000000000..d026649451 --- /dev/null +++ b/docs/en/Tutorials/Todo/Index.md @@ -0,0 +1,362 @@ +# Quick Tutorial + +````json +//[doc-params] +{ + "UI": ["MVC", "Blazor", "BlazorServer", "NG"], + "DB": ["EF", "Mongo"], +} +```` + +This is a single-part, quick tutorial to build a simple TODO application with the ABP Framework. Here, a sample screenshot from the final application: + +![todo-list](todo-list.png) + +### Source Code + +You can find source code of the example application [here](https://github.com/abpframework/abp-samples/tree/master/TodoApp). + +## Creating a New Solution + +Please create a new solution, named `TodoApp`, by following the [Getting Started](../../Getting-Started.md) document. + +**TODO: Instead, we can create the solution here!** + +## Domain Layer + +This application has a single [entity](../../Entities.md) and we are starting by creating it. Create a new `TodoItem` class inside the *TodoApp.Domain* project: + +````csharp +using System; +using Volo.Abp.Domain.Entities; + +namespace TodoApp +{ + public class TodoItem : BasicAggregateRoot + { + public string Text { get; set; } + } +} +```` + +`BasicAggregateRoot` is one the simplest base class to create root entities, and `Guid` is the primary key (`Id`) of the entity here. + +## Database Integration + +Next step is to setup the [Entity Framework Core](../../Entity-Framework-Core.md) configuration. Open the `TodoAppDbContext` class in the `EntityFrameworkCore` folder of the *TodoApp.EntityFrameworkCore* project and add a new `DbSet` property to this class: + +````csharp +public DbSet TodoItems { get; set; } +```` + +Then open the `TodoAppDbContextModelCreatingExtensions` class in the same folder and add a mapping configuration for the `TodoItem` class as shown below: + +````csharp +public static void ConfigureTodoApp(this ModelBuilder builder) +{ + Check.NotNull(builder, nameof(builder)); + + builder.Entity(b => + { + b.ToTable("TodoItems"); + }); +} +```` + +We've mapped `TodoItem` entity to a `TodoItems` table in the database. + +**TODO: DB MIGRATIONS** + +Now, we can use ABP repositories to save and retrieve todo items, as we'll do in the next section. + +## Application Layer + +An [Application Service](../../Application-Services.md) is used to perform use cases of the application. We need to perform the following use cases; + +* Get the list of todo items +* Create a new todo item +* Delete an existing todo item + +We can start by defining an interface for the application service. Create a new `ITodoAppService` interface in the *TodoApp.Application.Contracts* project, as shown below: + +````csharp +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Application.Services; + +namespace TodoApp +{ + public interface ITodoAppService : IApplicationService + { + Task> GetListAsync(); + Task CreateAsync(string text); + Task DeleteAsync(Guid id); + } +} +```` + +`GetListAsync` and `CreateAsync` methods return `TodoItemDto`. Applications Services typically gets and returns DTOs ([Data Transfer Objects](../../Data-Transfer-Objects.md)) instead of entities. So, we should define the DTO class here. Create a new `TodoItemDto` class inside the *TodoApp.Application.Contracts* project: + +````csharp +using System; + +namespace TodoApp +{ + public class TodoItemDto + { + public Guid Id { get; set; } + public string Text { get; set; } + } +} +```` + +This is a very simple DTO class that matches to our `TodoItem` entity. We are ready to implement the `ITodoAppService`. Create a `TodoAppService` class inside the *TodoApp.Application* project, as shown below: + +````csharp +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Repositories; + +namespace TodoApp +{ + public class TodoAppService : ApplicationService, ITodoAppService + { + private readonly IRepository _todoItemRepository; + + public TodoAppService(IRepository todoItemRepository) + { + _todoItemRepository = todoItemRepository; + } + + // TODO: Implement the methods + } +} +```` + +This class inherits from the `ApplicationService` class of the ABP Framework and implements the `ITodoAppService` that was defined before. ABP provides default generic [repositories](../../Repositories.md) for the entities. We can use them to perform the fundamental database operations. This class [injects](../../Dependency-Injection.md) `IRepository`, which is the default repository for the `TodoItem` entity. We will use it to implement the use cases described before. + +Let's start by implementing the `GetListAsync` method: + +````csharp +public async Task> GetListAsync() +{ + var items = await _todoItemRepository.GetListAsync(); + return items + .Select(item => new TodoItemDto + { + Id = item.Id, + Text = item.Text + }).ToList(); +} +```` + +We are simply getting the complete `TodoItem` list from database, mapping them to `TodoItemDto` objects and returning as the result. + +Next method is `CreateAsync` and we can implement it as shown below: + +````csharp +public async Task CreateAsync(string text) +{ + var todoItem = await _todoItemRepository.InsertAsync( + new TodoItem {Text = text} + ); + + return new TodoItemDto + { + Id = todoItem.Id, + Text = todoItem.Text + }; +} +```` + +Repository's `InsertAsync` method inserts the given `TodoItem` to database and returns the same `TodoItem` object. It also sets the `Id`, so we can use it on the returning object. We are simply returning a `TodoItemDto` by creating from the new `TodoItem` entity. + +Finally, we can simple implement the `DeleteAsync` as the following code block: + +````csharp +public async Task DeleteAsync(Guid id) +{ + await _todoItemRepository.DeleteAsync(id); +} +```` + +The application service is ready to be used from the UI layer. + +## User Interface Layer + +It is time to show the todo items on the UI! Before starting to write the code, it would be good to remember what we are trying to build. Here, a sample screenshot from the final UI: + +![todo-list](todo-list.png) + +> **We will keep the UI side minimal for this tutorial to make the tutorial simple and focused. See the [web application development tutorial](../Part-1.md) to build real-life pages with all aspects.** + +### Index.cshtml.cs + +Open the `Index.cshtml.cs` file in the `Pages` folder of the *TodoApp.Web* project and replace with the following content: + +````csharp +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace TodoApp.Web.Pages +{ + public class IndexModel : TodoAppPageModel + { + public List TodoItems { get; set; } + + private readonly ITodoAppService _todoAppService; + + public IndexModel(ITodoAppService todoAppService) + { + _todoAppService = todoAppService; + } + + public async Task OnGetAsync() + { + TodoItems = await _todoAppService.GetListAsync(); + } + } +} +```` + +This class uses the `ITodoAppService` to get the list of todo items and assign the the `TodoItems` property. We will use it to render the todo items on the razor page. + +### Index.cshtml + +Open the `Index.cshtml` file in the `Pages` folder of the *TodoApp.Web* project and replace with the following content: + +````xml +@page +@model TodoApp.Web.Pages.IndexModel +@section styles { + +} +@section scripts { + +} +
+ + + + TODO LIST + + + + + +
+ + +
+ + +
    + @foreach (var todoItem in Model.TodoItems) + { +
  • + @todoItem.Text +
  • + } +
+
+
+
+```` + +We are using ABP's [card tag helper](../../UI/AspNetCore/Tag-Helpers/Cards.md) to create a simple card view. You could directly use the standard bootstrap HTML structure, however the ABP [tag helpers]() make it much easier and type safe. + +This page imports a CSS and a JavaScript file, so we should also create them. + +### Index.js + +Open the `Index.js` file in the `Pages` folder of the *TodoApp.Web* project and replace with the following content: + +````js +$(function () { + + // DELETING ITEMS ///////////////////////////////////////// + $('#TodoList').on('click', 'li i', function(){ + var $li = $(this).parent(); + var id = $li.attr('data-id'); + + todoApp.todo.delete(id).then(function(){ + $li.remove(); + abp.notify.info('Deleted the todo item.'); + }); + }); + + // CREATING NEW ITEMS ///////////////////////////////////// + $('#NewItemForm').submit(function(e){ + e.preventDefault(); + + var todoText = $('#NewItemText').val(); + todoApp.todo.create(todoText).then(function(result){ + $('
  • ') + .html(' ' + result.text) + .appendTo($('#TodoList')); + $('#NewItemText').val(''); + }); + }); +}); +```` + +In the first part, we are registering to click events of the trash icons near to the todo items, deleting the related item on the server and showing a notification on the UI. Also, we are removing the deleted item from DOM, so we don't need to refresh the page. + +In the second part, we are creating a new todo item on the server. If it succeed, we are then manipulating DOM to insert a new `
  • ` element to the todo list. In this way, no need to refresh the whole page after creating a new todo item. + +The interesting part here is how we communicate with the server. It seems we are just calling some JavaScript functions like `todoApp.todo.delete(id)` and `todoApp.todo.create(todoText)`. + +#### Dynamic JavaScript Proxies & Auto API Controllers + + `todoApp.todo.delete(...)` and `todoApp.todo.create(...)` functions are dynamically created by the ABP Framework, thanks to the [Dynamic JavaScript Client Proxy](../../UI/AspNetCore/Dynamic-JavaScript-Proxies.md) system. These functions perform HTTP API calls to the server and return a promise, so you can register a callback to the `then` function as we've done above. + +However, you may ask that we haven't created any API Controller, so how server handles these requests? This question brings us the [Auto API Controller](../../API/Auto-API-Controllers.md) feature of the ABP Framework. It automatically converts the application services to API Controllers by conventions. + +If you open the [Swagger UI](https://swagger.io/tools/swagger-ui/) by entering the `/swagger` URL in your application, you can see the TODO API: + +![todo-api](todo-api.png) + +### Index.css + +As the final touch, open the `Index.css` file in the `Pages` folder of the *TodoApp.Web* project and replace with the following content: + +````css +#TodoList{ + list-style: none; + margin: 0; + padding: 0; +} + +#TodoList li { + padding: 5px; + margin: 5px 0px; + border: 1px solid #cccccc; + background-color: #f5f5f5; +} + +#TodoList li i +{ + opacity: 0.5; +} + +#TodoList li i:hover +{ + opacity: 1; + color: #ff0000; + cursor: pointer; +} +```` + +This is a simple styling for the todo page. We believe that you can do much better :) + +## Conclusion + +In this tutorial, we've build a very simple application to warm up to the ABP Framework. If you are looking to build a serious application, please check the [web application development tutorial](../Part-1.md) which covers all the aspects of a real-life web application development. + +## Source Code + +You can find source code of the example application [here](https://github.com/abpframework/abp-samples/tree/master/TodoApp). \ No newline at end of file diff --git a/docs/en/Tutorials/Todo/todo-api.png b/docs/en/Tutorials/Todo/todo-api.png new file mode 100644 index 0000000000..f9a25f57b5 Binary files /dev/null and b/docs/en/Tutorials/Todo/todo-api.png differ diff --git a/docs/en/Tutorials/Todo/todo-list.png b/docs/en/Tutorials/Todo/todo-list.png new file mode 100644 index 0000000000..62f5a48065 Binary files /dev/null and b/docs/en/Tutorials/Todo/todo-list.png differ