mirror of https://github.com/abpframework/abp.git
3 changed files with 362 additions and 0 deletions
@ -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: |
|||
|
|||
 |
|||
|
|||
### 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<Guid> |
|||
{ |
|||
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<TodoItem> 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<TodoItem>(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<List<TodoItemDto>> GetListAsync(); |
|||
Task<TodoItemDto> 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<TodoItem, Guid> _todoItemRepository; |
|||
|
|||
public TodoAppService(IRepository<TodoItem, Guid> 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<TodoItem, Guid>`, 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<List<TodoItemDto>> 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<TodoItemDto> 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: |
|||
|
|||
 |
|||
|
|||
> **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<TodoItemDto> 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 { |
|||
<abp-style src="/Pages/Index.css" /> |
|||
} |
|||
@section scripts { |
|||
<abp-script src="/Pages/Index.js" /> |
|||
} |
|||
<div class="container"> |
|||
<abp-card> |
|||
<abp-card-header> |
|||
<abp-card-title> |
|||
TODO LIST |
|||
</abp-card-title> |
|||
</abp-card-header> |
|||
<abp-card-body> |
|||
|
|||
<!-- FORM FOR NEW TODO ITEMS --> |
|||
<form id="NewItemForm" class="form-inline"> |
|||
<input id="NewItemText" type="text" class="form-control mr-2" placeholder="enter text..."> |
|||
<button type="submit" class="btn btn-primary">Submit</button> |
|||
</form> |
|||
|
|||
<!-- TODO ITEMS LIST --> |
|||
<ul id="TodoList"> |
|||
@foreach (var todoItem in Model.TodoItems) |
|||
{ |
|||
<li data-id="@todoItem.Id"> |
|||
<i class="fa fa-trash-o"></i> @todoItem.Text |
|||
</li> |
|||
} |
|||
</ul> |
|||
</abp-card-body> |
|||
</abp-card> |
|||
</div> |
|||
```` |
|||
|
|||
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){ |
|||
$('<li data-id="' + result.id + '">') |
|||
.html('<i class="fa fa-trash-o"></i> ' + 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 `<li>` 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: |
|||
|
|||
 |
|||
|
|||
### 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). |
|||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 12 KiB |
Loading…
Reference in new issue