From d7efec5df502926bfc534647a239cacd26618c21 Mon Sep 17 00:00:00 2001 From: Engincan VESKE Date: Tue, 17 May 2022 15:53:15 +0300 Subject: [PATCH] Update Quick Start documentation for `app-nolayers` --- docs/en/Tutorials/Todo/Single-Layer/Index.md | 205 ++++++++++++++++-- .../todo-efcore-migration-single-layer.png | Bin 0 -> 8866 bytes 2 files changed, 188 insertions(+), 17 deletions(-) create mode 100644 docs/en/Tutorials/Todo/Single-Layer/todo-efcore-migration-single-layer.png diff --git a/docs/en/Tutorials/Todo/Single-Layer/Index.md b/docs/en/Tutorials/Todo/Single-Layer/Index.md index fe70013e19..e902f3108c 100644 --- a/docs/en/Tutorials/Todo/Single-Layer/Index.md +++ b/docs/en/Tutorials/Todo/Single-Layer/Index.md @@ -12,7 +12,7 @@ This is a single-part quick-start tutorial to build a simple todo application wi ![todo-list](../todo-list.png) -You can find the source code of the completed application [here](TODO: sample's url???). +You can find the source code of the completed application [here](https://github.com/abpframework/abp-samples/tree/master/TodoApp). ## Pre-Requirements @@ -46,11 +46,11 @@ abp new TodoApp -t app-nolayers{{if UI=="BlazorServer"}} -u blazor-server{{else {{if UI=="NG"}} -This will create a new solution with a single-project, named *TodoApp* with `angular` and `aspnet-core` folders. Once the solution is ready, open the solution in your favorite IDE. +This will create a new solution with a single project, named *TodoApp* with `angular` and `aspnet-core` folders. Once the solution is ready, open the solution in your favorite IDE. {{else}} -This will create a new solution with a single-project, named *TodoApp*. Once the solution is ready, open it in your favorite IDE. +This will create a new solution with a single project, named *TodoApp*. Once the solution is ready, open it in your favorite IDE. {{end}} @@ -68,13 +68,13 @@ This command will create the database and seed the initial data for you. Then yo {{if UI=="MVC" || UI=="BlazorServer"}} -It is good to run the application before starting the development. Running the application is pretty straigth-forward, you can run the application with any IDE that supports .NET or by running the `dotnet run` CLI command in the directory of your project to see the initial UI: +It is good to run the application before starting the development. Running the application is pretty straight-forward, you can run the application with any IDE that supports .NET or by running the `dotnet run` CLI command in the directory of your project: {{else if UI=="NG"}} It is good to run the application before starting the development. The solution has two main applications: -* `TodoApp` (in the .NET solution) host the server-side HTTP API, so Angular application can consume it. (server-side application) +* `TodoApp` (in the .NET solution) hosts the server-side HTTP API, so the Angular application can consume it. (server-side application) * `angular` folder contains the Angular application. (client-side application) Firstly, run the `TodoApp` project in your favorite IDE (or run the `dotnet run` CLI command on your project directory) to see the server-side HTTP API on the [Swagger UI](https://swagger.io/tools/swagger-ui/): @@ -101,9 +101,9 @@ This command takes time, but eventually runs and opens the application in your d ![todo-ui-initial](../todo-ui-initial.png) -You can click on the *Login* button, use `admin` as the username and `1q2w3E*` as the password to login to the application. +You can click on the *Login* button and use `admin` as the username and `1q2w3E*` as the password to login to the application. -All ready. We can start coding! +All right. We can start coding! ## Defining Entities @@ -156,7 +156,7 @@ protected override void OnModelCreating(ModelBuilder builder) } ```` -We've mapped the `TodoItem` entity to the `TodoItems` table in the database. The next step is creating a migration and apply the changes to the database. +We've mapped the `TodoItem` entity to the `TodoItems` table in the database. The next step is to create a migration and apply the changes to the database. ### Code First Migrations @@ -168,11 +168,11 @@ Open a command-line terminal in the directory of your project and type the follo dotnet ef migrations add Added_TodoItem ```` -This will add a new migration class to the project: +This will add a new migration class to the project. You should see the migration in the **Migrations** folder. -![todo-efcore-migration](../todo-efcore-migration-single-layer.png) TODO: add this screenshot??? +![todo-efcore-migration](todo-efcore-migration-single-layer.png) -You can apply changes to the database using the following command, in the same command-line terminal: +Then, you can apply changes to the database using the following command, in the same command-line terminal: ````bash dotnet ef database update @@ -180,7 +180,7 @@ dotnet ef database update {{else if DB=="Mongo"}} -Next step is to setup the [MongoDB](../../../MongoDB.md) configuration. Open the `TodoAppMongoDbContext` class (it's under the **Data** folder) in your project and make the following changes: +The next step is to setup the [MongoDB](../../../MongoDB.md) configuration. Open the `TodoAppMongoDbContext` class (it's under the **Data** folder) in your project and make the following changes: 1. Add a new property to the class: @@ -239,7 +239,7 @@ public class TodoItemDto } ``` -This is a very simple DTO class that have the same properties with our `TodoItem` entity. We are ready to implement the `ITodoAppService`. +This is a very simple DTO class that has the same properties as the `TodoItem` entity. We are ready to implement the `ITodoAppService`. ## Application Service Implementation @@ -284,11 +284,11 @@ public async Task> GetListAsync() } ```` -We are simply getting the complete `TodoItem` list from the database, mapping them to `TodoItemDto` objects and returning as the result. +We are simply getting the `TodoItem` list from the database, mapping them to `TodoItemDto` objects and returning as the result. #### Creating a New Todo Item -Next method is `CreateAsync` and we can implement it as shown below: +The next method is `CreateAsync` and we can implement it as shown below: ````csharp public async Task CreateAsync(string text) @@ -328,8 +328,179 @@ It is time to show the todo items on the UI! Before starting to write the code, {{if UI=="MVC"}} +### Index.cshtml.cs + +Open the `Index.cshtml.cs` file in the `Pages` folder and replace the content with the following code block: + +```csharp +using TodoApp.Services; +using TodoApp.Services.Dtos; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace TodoApp.Pages; + +public class IndexModel : AbpPageModel +{ + 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 `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 and replace it with the following content: + +```xml +@page +@model TodoApp.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](../../UI/AspNetCore/Tag-Helpers/Index.md) make it much easier and type safe. + +This page imports a CSS and a JavaScript file, so we should also create them. + +### Index.js + +Create an `Index.js` file in the `Pages` folder and add the following content: + +````js +$(function () { + + // DELETING ITEMS ///////////////////////////////////////// + $('#TodoList').on('click', 'li i', function(){ + var $li = $(this).parent(); + var id = $li.attr('data-id'); + + todoApp.services.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.services.todo.create(todoText).then(function(result){ + $('
  • ') + .html(' ' + result.text) + .appendTo($('#TodoList')); + $('#NewItemText').val(''); + }); + }); +}); +```` + +In the first part, we are subscribing to the click events of the trash icons near 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 the 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 succeeds, we are then manipulating the DOM to insert a new `
  • ` element to the todo list. This way we don't need to refresh the whole page after creating a new todo item. + +The interesting part here is how we communicate with the server. See the [*Dynamic JavaScript Proxies & Auto API Controllers*](#dynamic-javascript-proxies--auto-api-controllers) section to understand how it works. But now, let's continue and complete the application. + +### Index.css + +As the final touch, create the `Index.css` file in the `Pages` folder and add 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 :) + +Now, you can run the application again and see the result. + +### Dynamic JavaScript Proxies & Auto API Controllers + +In the `Index.js` file, we've used the `todoApp.services.todo.delete(...)` and `todoApp.services.todo.create(...)` functions to communicate with the server. These functions are dynamically created by the ABP Framework, thanks to the [Dynamic JavaScript Client Proxy](../../../UI/AspNetCore/Dynamic-JavaScript-Proxies.md) system. They 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. + +> **services** keyword comes from the namespace (`namespace TodoApp.Services;`). + +However, you may notice that we haven't created any API Controllers, so how does the server handle these requests? This question brings us to the [Auto API Controller](../../../API/Auto-API-Controllers.md) feature of the ABP Framework. It automatically converts the application services to **API Controllers** by convention. + +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) + + {{else if UI=="BlazorServer"}} + {{else if UI=="NG"}} {{end}} @@ -340,8 +511,8 @@ In this tutorial, we've built a very simple application to warm up with the ABP ## Source Code -You can find source code of the completed application [here](https://github.com/abpframework/abp-samples/tree/master/TodoApp). +You can find the source code of the completed application [here](https://github.com/abpframework/abp-samples/tree/master/TodoApp). ## See Also -* [Web Application Development Tutorial](../Part-1.md) to see a real-life web application development in a layered architecture. \ No newline at end of file +* Check the [Web Application Development Tutorial](../Part-1.md) to see a real-life web application development in a layered architecture using the [Application Startup Template](../../../Startup-Templates/Application.md). \ No newline at end of file diff --git a/docs/en/Tutorials/Todo/Single-Layer/todo-efcore-migration-single-layer.png b/docs/en/Tutorials/Todo/Single-Layer/todo-efcore-migration-single-layer.png new file mode 100644 index 0000000000000000000000000000000000000000..686d8a5f87be890ca789998d6cbafae931e872da GIT binary patch literal 8866 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