mirror of https://github.com/abpframework/abp.git
152 changed files with 2912 additions and 15459 deletions
@ -0,0 +1,107 @@ |
|||
# Application (Single Layer) Startup Template |
|||
|
|||
## Introduction |
|||
|
|||
This template provides a simple solution structure with a single project. This document explains that solution structure in details. |
|||
|
|||
### The Difference Between the Application Startup Templates |
|||
|
|||
ABP's [Application Startup Template](Application.md) provides a well-organized and layered solution to create maintainable business applications based on the [Domain Driven Design](../Domain-Driven-Design.md) (DDD) practices. However, some developers find this template a little bit complex for simple and short-term applications. The single-layer application template has been created to provide a simpler development model for such applications. This template has the same functionality, features and modules on runtime with the [Application Startup Template](Application.md) but the development model is minimal and everything is in a single project (`.csproj`). |
|||
|
|||
## How to Start with It? |
|||
|
|||
You can use the [ABP CLI](../CLI.md) to create a new project using this startup template. Alternatively, you can directly create & download this startup template from the [Get Started](https://abp.io/get-started) page. In this section, we will use the ABP CLI. |
|||
|
|||
Firstly, install the ABP CLI if you haven't installed it before: |
|||
|
|||
```bash |
|||
dotnet tool install -g Volo.Abp.Cli |
|||
``` |
|||
|
|||
Then, use the `abp new` command in an empty folder to create a new solution: |
|||
|
|||
```bash |
|||
abp new Acme.BookStore -t app-nolayers |
|||
``` |
|||
|
|||
* `Acme.BookStore` is the solution name, like *YourCompany.YourProduct*. You can use single-level, two-level or three-level naming. |
|||
* In this example, the `-t` (or `--template`) option specifies the template name. |
|||
|
|||
### Specify the UI Framework |
|||
|
|||
This template provides multiple UI frameworks: |
|||
|
|||
* `mvc`: ASP.NET Core MVC UI with Razor Pages (default) |
|||
* `blazor-server`: Blazor Server UI |
|||
* `angular`: Angular UI |
|||
* `none`: Without UI (for HTTP API development) |
|||
|
|||
> This template doesn't have Blazor WebAssembly UI, because it requires 3 projects at least (server-side, UI and shared library between these two projects). We are recommending to use the layered [application startup template](Application.md) for Blazor WebAssembly projects. |
|||
|
|||
Use the `-u` (or `--ui`) option to specify the UI framework while creating the solution: |
|||
|
|||
```bash |
|||
abp new Acme.BookStore -t app-nolayers -u angular |
|||
``` |
|||
|
|||
This example specifies the UI type (the `-u` option) as `angular`. You can also specify `mvc`, `blazor-server` or `none` for the UI type. |
|||
|
|||
### Specify the Database Provider |
|||
|
|||
This template supports the following database providers: |
|||
|
|||
- `ef`: Entity Framework Core (default) |
|||
- `mongodb`: MongoDB |
|||
|
|||
Use the `-d` (or `--database-provider`) option to specify the database provider while creating the solution: |
|||
|
|||
```bash |
|||
abp new Acme.BookStore -t app-nolayers -d mongodb |
|||
``` |
|||
|
|||
## Solution Structure |
|||
|
|||
If you don't specify any additional options while creating an `app-nolayers` template, you will have a solution as shown below: |
|||
|
|||
 |
|||
|
|||
In the next sections, we will explain the structure based on this example. Your startup solution can be slightly different based on your preferences. |
|||
|
|||
### Folder Structure |
|||
|
|||
Since this template provides a single-project solution, we've separated concerns into folders instead of projects. You can see the pre-defined folders as shown below: |
|||
|
|||
 |
|||
|
|||
* Define your database mappings (for [EF Core](../Entity-Framework-Core.md) or [MongoDB](../MongoDB.md)) and [repositories](../Repositories.md) in the `Data` folder. |
|||
* Define your [entities](../Entities.md) in the `Entities` folder. |
|||
* Define your UI localization keys/values in the `Localization` folder. |
|||
* Define your UI menu items in the `Menus` folder. |
|||
* Define your [object-to-object mapping](../Object-To-Object-Mapping.md) classes in the `ObjectMapping` folder. |
|||
* Define your UI pages (Razor Pages) in the `Pages` folder (create `Controllers` and `Views` folder yourself if you prefer the MVC pattern). |
|||
* Define your [application services](../Application-Services.md) in the `Services` folder. |
|||
|
|||
### How to Run? |
|||
|
|||
Before running the application, you need to create the database and seed the initial data. To do that, you can run the following command in the directory of your project (in the same folder of the `.csproj` file): |
|||
|
|||
```bash |
|||
dotnet run --migrate-database |
|||
``` |
|||
|
|||
This command will create the database and seed the initial data for you. Then you can run the application with any IDE that supports .NET or by running the `dotnet run` command in the directory of your project. The default username is `admin` and the password is `1q2w3E*`. |
|||
|
|||
> While creating a database & applying migrations seem only necessary for relational databases, you should run this command even if you choose a NoSQL database provider (like MongoDB). In that case, it still seeds the initial data which is necessary for the application. |
|||
|
|||
### The Angular UI |
|||
|
|||
If you choose `Angular` as the UI framework, the solution will be separated into two folders: |
|||
|
|||
* An `angular` folder that contains the Angular UI application, the client-side code. |
|||
* An `aspnet-core` folder that contains the ASP.NET Core solution (a single project), the server-side code. |
|||
|
|||
The server-side is similar to the solution described in the *Solution Structure* section above. This project serves the API, so the Angular application can consume it. |
|||
|
|||
The client-side application consumes the HTTP APIs as mentioned. You can see the folder structure of the Angular project shown below: |
|||
|
|||
 |
|||
@ -0,0 +1,14 @@ |
|||
# Quick Start: Overall |
|||
|
|||
**Welcome to the ABP Framework**. This is a single-part, quick-start tutorial to build a simple application. Start with this tutorial if you want to quickly understand how ABP Framework works. |
|||
|
|||
## Select the Solution Architecture |
|||
|
|||
This tutorial has multiple versions. Please select the one best fits for you: |
|||
|
|||
* **[Single-Layer Solution](Single-Layer/Index.md)**: Creates a single-project solution. Recommended for building an application with a **simpler and easy to understand** architecture. |
|||
* **[Layered Solution Architecture](Index.md)**: A fully layered (multiple projects) solution based on [Domain Driven Design](../../Domain-Driven-Design.md) practices. Recommended for long-term projects that need a **maintainable and extensible** codebase. |
|||
|
|||
## See Also |
|||
|
|||
* Check the [Web Application Development Tutorial](../Part-1.md) to see a real-life web application development in a layered architecture. |
|||
@ -0,0 +1,755 @@ |
|||
# Quick Start |
|||
|
|||
````json |
|||
//[doc-params] |
|||
{ |
|||
"UI": ["MVC", "BlazorServer", "NG"], |
|||
"DB": ["EF", "Mongo"] |
|||
} |
|||
```` |
|||
|
|||
This is a single-part quick-start tutorial to build a simple todo application with the ABP Framework. Here's a screenshot from the final application: |
|||
|
|||
 |
|||
|
|||
You can find the source code of the completed application [here](https://github.com/abpframework/abp-samples/tree/master/TodoApp-SingleLayer). |
|||
|
|||
## Pre-Requirements |
|||
|
|||
* An IDE (e.g. [Visual Studio](https://visualstudio.microsoft.com/vs/)) that supports [.NET 6.0+](https://dotnet.microsoft.com/download/dotnet) development. |
|||
* [Node v14.x](https://nodejs.org/) |
|||
|
|||
{{if DB=="Mongo"}} |
|||
|
|||
* [MongoDB Server 4.0+](https://docs.mongodb.com/manual/administration/install-community/) |
|||
|
|||
{{end}} |
|||
|
|||
## Creating a New Solution |
|||
|
|||
In this tutorial, we will use the [ABP CLI](../../../CLI.md) to create the sample application with the ABP Framework. You can run the following command in a command-line terminal to install the **ABP CLI**, if you haven't installed it yet: |
|||
|
|||
````bash |
|||
dotnet tool install -g Volo.Abp.Cli |
|||
```` |
|||
|
|||
Then create an empty folder, open a command-line terminal and execute the following command in the terminal: |
|||
|
|||
````bash |
|||
abp new TodoApp -t app-nolayers{{if UI=="BlazorServer"}} -u blazor-server{{else if UI=="NG"}} -u angular{{end}}{{if DB=="Mongo"}} -d mongodb{{end}} |
|||
```` |
|||
|
|||
{{if UI=="NG"}} |
|||
|
|||
This will create a new solution, named *TodoApp*, with `angular` and `aspnet-core` folders. Once the solution is ready, open the solution (in the `aspnet-core` folder) with 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. |
|||
|
|||
{{end}} |
|||
|
|||
### Create the Database |
|||
|
|||
You can run the following command in the root directory of your project (in the same folder of the `.csproj` file) to create the database and seed the initial data: |
|||
|
|||
```bash |
|||
dotnet run --migrate-database |
|||
``` |
|||
|
|||
This command will create the database and seed the initial data for you. Then you can run the application. |
|||
|
|||
### Run the Application |
|||
|
|||
{{if UI=="MVC" || UI=="BlazorServer"}} |
|||
|
|||
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) 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 [Swagger UI](https://swagger.io/tools/swagger-ui/): |
|||
|
|||
 |
|||
|
|||
You can explore and test your HTTP API with this UI. If it works, then we can run the Angular client application. |
|||
|
|||
You can run the application using the following (or `yarn start`) command: |
|||
|
|||
````bash |
|||
npm start |
|||
```` |
|||
|
|||
This command takes time, but eventually runs and opens the application in your default browser: |
|||
|
|||
{{end}} |
|||
|
|||
 |
|||
|
|||
You can click on the *Login* button and use `admin` as the username and `1q2w3E*` as the password to login to the application. |
|||
|
|||
All right. We can start coding! |
|||
|
|||
## Defining the Entity |
|||
|
|||
This application will have a single [entity](../../../Entities.md) and we can start by creating it. So, create a new `TodoItem` class under the `Entities` folder of the project: |
|||
|
|||
````csharp |
|||
using Volo.Abp.Domain.Entities; |
|||
|
|||
namespace TodoApp.Entities; |
|||
|
|||
public class TodoItem : BasicAggregateRoot<Guid> |
|||
{ |
|||
public string Text { get; set; } |
|||
} |
|||
```` |
|||
|
|||
`BasicAggregateRoot` is the simplest base class to create root entities, and `Guid` is the primary key (`Id`) of the entity here. |
|||
|
|||
## Database Integration |
|||
|
|||
{{if DB=="EF"}} |
|||
|
|||
Next step is to setup the [Entity Framework Core](../../../Entity-Framework-Core.md) configuration. |
|||
|
|||
### Mapping Configuration |
|||
|
|||
Open the `TodoAppDbContext` class (in the `Data` folder) and add a new `DbSet` property to this class: |
|||
|
|||
````csharp |
|||
public DbSet<TodoItem> TodoItems { get; set; } |
|||
```` |
|||
|
|||
Then navigate to the `OnModelCreating` method in the same class and add the following mapping code for the `TodoItem ` entity: |
|||
|
|||
````csharp |
|||
protected override void OnModelCreating(ModelBuilder builder) |
|||
{ |
|||
base.OnModelCreating(builder); |
|||
|
|||
/* Include modules to your migration db context */ |
|||
|
|||
builder.ConfigurePermissionManagement(); |
|||
... |
|||
|
|||
/* Configure your own tables/entities inside here */ |
|||
builder.Entity<TodoItem>(b => |
|||
{ |
|||
b.ToTable("TodoItems"); |
|||
}); |
|||
} |
|||
```` |
|||
|
|||
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 |
|||
|
|||
The startup solution is configured to use Entity Framework Core [Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations). Since we've changed the database mapping configuration, we should create a new migration and apply changes to the database. |
|||
|
|||
Open a command-line terminal in the root directory of your project and type the following command: |
|||
|
|||
````bash |
|||
dotnet ef migrations add Added_TodoItem |
|||
```` |
|||
|
|||
This will add a new migration class to the project. You should see the new migration in the `Migrations` folder: |
|||
|
|||
 |
|||
|
|||
Then, you can apply changes to the database using the following command, in the same command-line terminal: |
|||
|
|||
````bash |
|||
dotnet ef database update |
|||
```` |
|||
|
|||
{{else if DB=="Mongo"}} |
|||
|
|||
The next step is to setup the [MongoDB](../../../MongoDB.md) configuration. Open the `TodoAppDbContext` class (under the **Data** folder) in your project and make the following changes: |
|||
|
|||
1. Add a new property to the class: |
|||
|
|||
````csharp |
|||
public IMongoCollection<TodoItem> TodoItems => Collection<TodoItem>(); |
|||
```` |
|||
|
|||
2. Add the following code inside the `CreateModel` method: |
|||
|
|||
````csharp |
|||
modelBuilder.Entity<TodoItem>(b => |
|||
{ |
|||
b.CollectionName = "TodoItems"; |
|||
}); |
|||
```` |
|||
|
|||
{{end}} |
|||
|
|||
After the database integrations, now we can start to create application service methods and implement our use-cases. |
|||
|
|||
## Creating the Application Service |
|||
|
|||
An [application service](../../../Application-Services.md) is used to perform the use cases of the application. We need to perform the following use cases in this application: |
|||
|
|||
* Get the list of the todo items |
|||
* Create a new todo item |
|||
* Delete an existing todo item |
|||
|
|||
Before starting to implement these use cases, first we need to create a DTO class that will be used in the application service. |
|||
|
|||
### Creating the Data Transfer Object (DTO) |
|||
|
|||
[Application services](../../../Application-Services.md) typically get and return DTOs ([Data Transfer Objects](../../../Data-Transfer-Objects.md)) instead of entities. So, create a new `TodoItemDto` class under the `Services/Dtos` folder: |
|||
|
|||
```csharp |
|||
namespace TodoApp.Services.Dtos; |
|||
|
|||
public class TodoItemDto |
|||
{ |
|||
public Guid Id { get; set; } |
|||
public string Text { get; set; } |
|||
} |
|||
``` |
|||
|
|||
This is a very simple DTO class that has the same properties as the `TodoItem` entity. Now, we are ready to implement our use-cases. |
|||
|
|||
### The Application Service Implementation |
|||
|
|||
Create a `TodoAppService` class under the `Services` folder of your project, as shown below: |
|||
|
|||
```csharp |
|||
using TodoApp.Entities; |
|||
using Volo.Abp.Application.Services; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace TodoApp.Services; |
|||
|
|||
public class TodoAppService : ApplicationService |
|||
{ |
|||
private readonly IRepository<TodoItem, Guid> _todoItemRepository; |
|||
|
|||
public TodoAppService(IRepository<TodoItem, Guid> todoItemRepository) |
|||
{ |
|||
_todoItemRepository = todoItemRepository; |
|||
} |
|||
|
|||
// TODO: Implement the methods here... |
|||
} |
|||
``` |
|||
|
|||
This class inherits from the `ApplicationService` class of the ABP Framework and implements our use-cases. 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 our use cases. |
|||
|
|||
#### Getting the Todo Items |
|||
|
|||
Let's start by implementing the `GetListAsync` method, which is used to get a list of todo items: |
|||
|
|||
````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 `TodoItem` list from the repository, mapping them to the `TodoItemDto` objects and returning as the result. |
|||
|
|||
#### Creating a New Todo Item |
|||
|
|||
The 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 |
|||
}; |
|||
} |
|||
```` |
|||
|
|||
The repository's `InsertAsync` method inserts the given `TodoItem` to the 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. |
|||
|
|||
#### Deleting a Todo Item |
|||
|
|||
Finally, we can 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. So, let's implement it. |
|||
|
|||
## User Interface |
|||
|
|||
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's a sample screenshot from the final UI: |
|||
|
|||
 |
|||
|
|||
{{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<TodoItemDto> TodoItems { get; set; } |
|||
|
|||
private readonly TodoAppService _todoAppService; |
|||
|
|||
public IndexModel(TodoAppService todoAppService) |
|||
{ |
|||
_todoAppService = todoAppService; |
|||
} |
|||
|
|||
public async Task OnGetAsync() |
|||
{ |
|||
TodoItems = await _todoAppService.GetListAsync(); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
This class uses `TodoAppService` 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 { |
|||
<abp-style src="/Pages/Index.cshtml.css" /> |
|||
} |
|||
@section scripts { |
|||
<abp-script src="/Pages/Index.cshtml.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="row row-cols-lg-auto g-3 align-items-center"> |
|||
<div class="col-12"> |
|||
<div class="input-group"> |
|||
<input id="NewItemText" type="text" class="form-control" placeholder="enter text..."> |
|||
</div> |
|||
</div> |
|||
<div class="col-12"> |
|||
<button type="submit" class="btn btn-primary">Submit</button> |
|||
</div> |
|||
</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](../../../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.cshtml.js |
|||
|
|||
Open the `Index.cshtml.js` file in the `Pages` folder 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.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){ |
|||
$('<li data-id="' + result.id + '">') |
|||
.html('<i class="fa fa-trash-o"></i> ' + result.text) |
|||
.appendTo($('#TodoList')); |
|||
$('#NewItemText').val(''); |
|||
}); |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
In the first part, we subscribed to the click events of the trash icons near the todo items, deleted the related item on the server and showed a notification on the UI. Also, we removed the deleted item from the DOM, so we wouldn't need to refresh the page. |
|||
|
|||
In the second part, we created a new todo item on the server. If it succeeded, we would then manipulate the DOM to insert a new `<li>` element to the todo list. This way, we wouldn'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* section to understand how it works. But now, let's continue and complete the application. |
|||
|
|||
### Index.cshtml.css |
|||
|
|||
As for the final touch, open the `Index.cshtml.css` file in the `Pages` folder 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 :) |
|||
|
|||
Now, you can run the application again and see the result. |
|||
|
|||
### Dynamic JavaScript Proxies & Auto API Controllers |
|||
|
|||
In the `Index.cshtml.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;`). It's a naming convention. |
|||
|
|||
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 [Swagger UI](https://swagger.io/tools/swagger-ui/) by entering the `/swagger` URL in your application, you can see the Todo API: |
|||
|
|||
 |
|||
|
|||
{{else if UI=="BlazorServer"}} |
|||
|
|||
### Index.razor.cs |
|||
|
|||
Open the `Index.razor.cs` file in the `Pages` folder and replace the content with the following code block: |
|||
|
|||
```csharp |
|||
using Microsoft.AspNetCore.Components; |
|||
using TodoApp.Services; |
|||
using TodoApp.Services.Dtos; |
|||
|
|||
namespace TodoApp.Pages; |
|||
|
|||
public partial class Index |
|||
{ |
|||
[Inject] |
|||
private TodoAppService TodoAppService { get; set; } |
|||
|
|||
private List<TodoItemDto> TodoItems { get; set; } = new List<TodoItemDto>(); |
|||
private string NewTodoText { get; set; } |
|||
|
|||
protected override async Task OnInitializedAsync() |
|||
{ |
|||
TodoItems = await TodoAppService.GetListAsync(); |
|||
} |
|||
|
|||
private async Task Create() |
|||
{ |
|||
var result = await TodoAppService.CreateAsync(NewTodoText); |
|||
TodoItems.Add(result); |
|||
NewTodoText = null; |
|||
} |
|||
|
|||
private async Task Delete(TodoItemDto todoItem) |
|||
{ |
|||
await TodoAppService.DeleteAsync(todoItem.Id); |
|||
await Notify.Info("Deleted the todo item."); |
|||
TodoItems.Remove(todoItem); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
This class uses the `TodoAppService` to get the list of todo items. It manipulates the `TodoItems` list after create and delete operations. This way, we don't need to refresh the whole todo list from the server. |
|||
|
|||
### Index.razor |
|||
|
|||
Open the `Index.razor` file in the `Pages` folder and replace the content with the following code block: |
|||
|
|||
```xml |
|||
@page "/" |
|||
@inherits TodoAppComponentBase |
|||
|
|||
<div class="container"> |
|||
<Card> |
|||
<CardHeader> |
|||
<CardTitle> |
|||
TODO LIST |
|||
</CardTitle> |
|||
</CardHeader> |
|||
<CardBody> |
|||
<!-- FORM FOR NEW TODO ITEMS --> |
|||
<form id="NewItemForm" @onsubmit:preventDefault @onsubmit="() => Create()" class="row row-cols-lg-auto g-3 align-items-center"> |
|||
<div class="col-12"> |
|||
<div class="input-group"> |
|||
<input name="NewTodoText" type="text" @bind-value="@NewTodoText" class="form-control" placeholder="enter text..." /> |
|||
</div> |
|||
</div> |
|||
<div class="col-12"> |
|||
<button type="submit" class="btn btn-primary">Submit</button> |
|||
</div> |
|||
</form> |
|||
<!-- TODO ITEMS LIST --> |
|||
<ul id="TodoList"> |
|||
@foreach (var todoItem in TodoItems) |
|||
{ |
|||
<li data-id="@todoItem.Id"> |
|||
<i class="far fa-trash-alt" |
|||
@onclick="() => Delete(todoItem)"></i> |
|||
@todoItem.Text |
|||
</li> |
|||
} |
|||
</ul> |
|||
</CardBody> |
|||
</Card> |
|||
</div> |
|||
``` |
|||
|
|||
### Index.razor.css |
|||
|
|||
As the final touch, open the `Index.razor.css` file in the `Pages` folder and replace it 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 :) |
|||
|
|||
Now, you can run the application again to see the result. |
|||
|
|||
{{else if UI=="NG"}} |
|||
|
|||
### Service Proxy Generation |
|||
|
|||
ABP provides a handy feature to automatically create client-side services to easily consume HTTP APIs provided by the server. |
|||
|
|||
You first need to run the `TodoApp` project since the proxy generator reads API definitions from the server application. |
|||
|
|||
Once you run the `TodoApp` project (**Swagger API Definition** will be shown), open a command-line terminal in the directory of `angular` folder and run the following command: |
|||
|
|||
```bash |
|||
abp generate-proxy -t ng |
|||
``` |
|||
|
|||
If everything goes well, it should generate an output as shown below: |
|||
|
|||
```bash |
|||
CREATE src/app/proxy/generate-proxy.json (182755 bytes) |
|||
CREATE src/app/proxy/README.md (1000 bytes) |
|||
CREATE src/app/proxy/services/todo.service.ts (833 bytes) |
|||
CREATE src/app/proxy/services/dtos/models.ts (71 bytes) |
|||
CREATE src/app/proxy/services/dtos/index.ts (26 bytes) |
|||
CREATE src/app/proxy/services/index.ts (81 bytes) |
|||
CREATE src/app/proxy/index.ts (61 bytes) |
|||
``` |
|||
|
|||
Then, we can use the `TodoService` to use the server-side HTTP APIs, as we'll do in the next section. |
|||
|
|||
### home.component.ts |
|||
|
|||
Open the `/angular/src/app/home/home.component.ts` file and replace its content with the following code block: |
|||
|
|||
```ts |
|||
import { ToasterService } from "@abp/ng.theme.shared"; |
|||
import { Component, OnInit } from '@angular/core'; |
|||
import { TodoItemDto } from "@proxy/services/dtos"; |
|||
import { TodoService } from "@proxy/services"; |
|||
|
|||
@Component({ |
|||
selector: 'app-home', |
|||
templateUrl: './home.component.html', |
|||
styleUrls: ['./home.component.scss'], |
|||
}) |
|||
|
|||
export class HomeComponent implements OnInit { |
|||
|
|||
todoItems: TodoItemDto[]; |
|||
newTodoText: string; |
|||
|
|||
constructor( |
|||
private todoService: TodoService, |
|||
private toasterService: ToasterService) |
|||
{ } |
|||
|
|||
ngOnInit(): void { |
|||
this.todoService.getList().subscribe(response => { |
|||
this.todoItems = response; |
|||
}); |
|||
} |
|||
|
|||
create(): void{ |
|||
this.todoService.create(this.newTodoText).subscribe((result) => { |
|||
this.todoItems = this.todoItems.concat(result); |
|||
this.newTodoText = null; |
|||
}); |
|||
} |
|||
|
|||
delete(id: string): void { |
|||
this.todoService.delete(id).subscribe(() => { |
|||
this.todoItems = this.todoItems.filter(item => item.id !== id); |
|||
this.toasterService.info('Deleted the todo item.'); |
|||
}); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
We've used `TodoService` to get the list of todo items and assigned the returning value to the `todoItems` array. We've also added `create` and `delete` methods. These methods will be used on the view side. |
|||
|
|||
### home.component.html |
|||
|
|||
Open the `/angular/src/app/home/home.component.html` file and replace its content with the following code block: |
|||
|
|||
````html |
|||
<div class="container"> |
|||
<div class="card"> |
|||
<div class="card-header"> |
|||
<div class="card-title">TODO LIST</div> |
|||
</div> |
|||
<div class="card-body"> |
|||
<!-- FORM FOR NEW TODO ITEMS --> |
|||
<form class="row row-cols-lg-auto g-3 align-items-center" (ngSubmit)="create()"> |
|||
<div class="col-12"> |
|||
<div class="input-group"> |
|||
<input name="NewTodoText" type="text" [(ngModel)]="newTodoText" class="form-control" placeholder="enter text..." /> |
|||
</div> |
|||
</div> |
|||
<div class="col-12"> |
|||
<button type="submit" class="btn btn-primary">Submit</button> |
|||
</div> |
|||
</form> |
|||
<!-- TODO ITEMS LIST --> |
|||
<ul id="TodoList"> |
|||
<li *ngFor="let todoItem of todoItems"> |
|||
<i class="fa fa-trash-o" (click)="delete(todoItem.id)"></i> {%{{{ todoItem.text }}}%} |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
```` |
|||
|
|||
### home.component.scss |
|||
|
|||
As the final touch, open the `/angular/src/app/home/home.component.scss` file and replace its content with the following code block: |
|||
|
|||
````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 to see the result. |
|||
|
|||
{{end}} |
|||
|
|||
## Conclusion |
|||
|
|||
In this tutorial, we've built a very simple application to warm up with the ABP Framework. |
|||
|
|||
## Source Code |
|||
|
|||
You can find the source code of the completed application [here](https://github.com/abpframework/abp-samples/tree/master/TodoApp-SingleLayer). |
|||
|
|||
## See Also |
|||
|
|||
* 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). |
|||
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 169 KiB |
@ -0,0 +1,50 @@ |
|||
## Breadcrumb Component |
|||
|
|||
ABP provides a component that listens to the angular router's `NavigationEnd` |
|||
event and creates inputs for `BreadcrumbItemsComponent`. This component is used in |
|||
ABP components with [`PageComponent`](./Page-Component.md). |
|||
|
|||
## Breadcrumb Items Component |
|||
|
|||
`BreadcrumbItemsComponent` is used to display breadcrumb items. It can be useful |
|||
when you want to display breadcrumb items in a different way than the default. |
|||
|
|||
### Usage |
|||
|
|||
Example of overriding the default template of `PageComponent`: |
|||
|
|||
```html |
|||
<abp-page title="Title"> |
|||
<abp-page-breadcrumb-container> |
|||
<abp-breadcrumb-items [items]="breadCrumbItems"></abp-breadcrumb-items> |
|||
</abp-page-breadcrumb-container> |
|||
</abp-page> |
|||
``` |
|||
|
|||
```js |
|||
import { Component } from "@angular/core"; |
|||
import { ABP } from "@abp/ng.core"; |
|||
|
|||
@Component({ |
|||
/* component metadata */ |
|||
}) |
|||
export class YourComponent { |
|||
breadCrumbItems: ABP.Route[] = [ |
|||
{ |
|||
name: "Item 1", |
|||
}, |
|||
{ |
|||
name: "Item 2", |
|||
path: "/path", |
|||
}, |
|||
]; |
|||
} |
|||
``` |
|||
|
|||
### Inputs |
|||
|
|||
- items: Partial<ABP.Route>[] : Array of ABP.Route objects. The source code of ABP.Route can be found in [github](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/common.ts#L69). |
|||
|
|||
## See Also |
|||
|
|||
- [Page Component](./Page-Component.md) |
|||
@ -0,0 +1,25 @@ |
|||
# Blazor UI: Global Features |
|||
`GlobalFeatureManager` allows you to check the global features in your Blazor applications. |
|||
|
|||
## Usage |
|||
|
|||
```html |
|||
@using Volo.Abp.GlobalFeatures |
|||
|
|||
@* ... *@ |
|||
|
|||
@* Global Feature can be checked with feature name *@ |
|||
@if(GlobalFeatureManager.Instance.IsEnabled("Ecommerce.Subscription")) |
|||
{ |
|||
<span>Ecommerce.Subscription is enabled.</span> |
|||
} |
|||
|
|||
@* OR it can be checked with type *@ |
|||
|
|||
@if(GlobalFeatureManager.Instance.IsEnabled<EcommerceSubscriptionGlobalFeature>()) |
|||
{ |
|||
<span>Ecommerce.Subscription is enabled.</span> |
|||
} |
|||
``` |
|||
|
|||
- You can follow _Check for a Global Feature_ section of the [Global Features document](../../Global-Features.md) to check global features in your C# code. |
|||
@ -0,0 +1,249 @@ |
|||
# PWA Configuration |
|||
|
|||
[PWAs (Progressive Web Apps)](https://web.dev/progressive-web-apps/) are developed using specific technologies to allow applications to take advantage of both web and native app features. |
|||
|
|||
Here is a list of some features that PWA provides: |
|||
|
|||
- **Installable**: A web application can be installed and used like a native/desktop application. |
|||
- **Network Independent**: PWAs support offline scenarios. It can work offline or with a poor network connection. |
|||
- **Responsive**: It's usable on any devices such as mobile phones, tablets, laptops, etc. |
|||
|
|||
## Creating a Project with PWA Support |
|||
|
|||
You can create a new web application with PWA support for **Blazor WebAssembly** by using the `--pwa` option as below: |
|||
|
|||
```bash |
|||
abp new Acme.BookStore -t blazor --pwa |
|||
``` |
|||
|
|||
After this command, your application will be created and some additional PWA related files (such as **manifest**, **icons**, **service workers**, etc.) will be added. Then, you can get the full advantages of web and native app features. |
|||
|
|||
## Adding PWA Support to an Existing Project |
|||
|
|||
If you started your application without PWA support, it's possible to change your mind and get the benefit of PWA later. You only need to make some configurations as listed below: |
|||
|
|||
### 1-) Add the `manifest.json` File |
|||
|
|||
> Web Application Manifest provides information about a web application in a JSON text file and it's required for the web application to be downloaded and be presented to the user similarly to a native application. |
|||
|
|||
First, you need to create a JSON file named **manifest.json** under the **wwwroot** folder and define some pieces of information about your application. You can see an example **manifest.json** file content below: |
|||
|
|||
```json |
|||
{ |
|||
"name": "MyProjectName", |
|||
"short_name": "MyCompanyName.MyProjectName", |
|||
"start_url": "./", |
|||
"display": "standalone", |
|||
"background_color": "#ffffff", |
|||
"theme_color": "#03173d", |
|||
"prefer_related_applications": false, |
|||
"icons": [ |
|||
{ |
|||
"src": "icon-512.png", |
|||
"type": "image/png", |
|||
"sizes": "512x512" |
|||
}, |
|||
{ |
|||
"src": "icon-192.png", |
|||
"type": "image/png", |
|||
"sizes": "192x192" |
|||
} |
|||
] |
|||
} |
|||
``` |
|||
|
|||
- Some application specific information should be defined in this file. |
|||
- For example, you can configure which icon needs to be seen in which screen size, background color, description, etc. |
|||
|
|||
### 2-) Add Icons for Specific Screen Sizes (Optional) |
|||
|
|||
You can add some icons for your application to be seen in specific screen sizes and define in which screen sizes icons should be displayed in the **manifest.json** file. You can see the **icons** section in the **manifest.json** file as an example above. |
|||
|
|||
> You can use, default icons from our [template](https://github.com/abpframework/abp/tree/dev/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot). |
|||
|
|||
### 3-) Configure Service Workers |
|||
|
|||
> Service workers are one of the fundamental parts of PWAs. They enable fast loading (regardless of the network), offline access, push notifications, and other web/native app capabilities. They run in the background and don't block the main thread so they don't slow your application. |
|||
|
|||
You need to create `service-worker.js` and `service-worker.published.js` files under the **wwwroot** folder of your project. These files will be used by your project to determine which PWA features you want to use. |
|||
|
|||
You can get the simple configurations for the [service-worker.js](https://github.com/abpframework/abp/blob/dev/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/service-worker.js) and [service-worker.published.js](https://github.com/abpframework/abp/blob/dev/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/service-worker.published.js) files from our [template](https://github.com/abpframework/abp/tree/dev/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot). |
|||
|
|||
After the related service worker files are added, then we need to define them in our `.csproj` file to notify our application. So open your `*.csproj` file and add the following content: |
|||
|
|||
```xml |
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData> |
|||
|
|||
<!-- Add the following line --> |
|||
<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest> |
|||
</PropertyGroup> |
|||
|
|||
<!-- Add the following item group --> |
|||
<ItemGroup> |
|||
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" /> |
|||
</ItemGroup> |
|||
``` |
|||
|
|||
* With the `ServiceWorkerAssetsManifest` MSBuild property, your Blazor application generates a service worker assets manifest with the specified name. This file will be generated in the path of `/bin/Debug/{TARGET FRAMEWORK}/wwwroot/service-worker-assets.js` on runtime. This manifest can list all resources such as images, stylesheets, JS files etc. by examining the `service-worker.published.js` file (regarding to your configurations in this file). |
|||
* The `ServiceWorker` property is used to define which files need to be accounted as **Service Worker** files and service workers are used to determine which PWA features should be used. |
|||
|
|||
|
|||
### 4-) Define Web Application Manifest and Register Service Workers |
|||
|
|||
Finally, now you can define the `manifest.json` file and **icons** in the **index.html** file and register the **service workers** for your application. |
|||
|
|||
Let's start with adding `<link>` elements (between `<head>` tags) for the manifest and app icon in the **index.html** file (under the **wwwroot** folder): |
|||
|
|||
```html |
|||
<head> |
|||
<!-- ... --> |
|||
<link href="manifest.json" rel="manifest" /> |
|||
<link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" /> |
|||
<link rel="apple-touch-icon" sizes="192x192" href="icon-192.png" /> |
|||
</head> |
|||
``` |
|||
|
|||
Then, add the following `<script>` tag inside the closing `</body>` tag in the same file: |
|||
|
|||
```html |
|||
<body> |
|||
<!-- ... --> |
|||
<script> |
|||
if ('serviceWorker' in navigator) { |
|||
navigator.serviceWorker.register("service-worker.js"); |
|||
} |
|||
</script> |
|||
</body> |
|||
``` |
|||
|
|||
You've added the related files and made the related configurations with this final touch to add PWA support to your existing application. Now, you can take full advantage of PWAs. |
|||
|
|||
## Differences Between the Debug and Published Service Workers |
|||
|
|||
Application Template produces two service worker files, if you create your application with PWA support: |
|||
|
|||
* The `service-worker.js` file is used during development and does nothing by default. |
|||
* The `service-worker.published.js` file, which is used after the app is published. Caches certain file extensions and supports offline scenarios by default (uses a *cache-first* strategy). A user must first visit the app while they're online. The browser automatically downloads and caches all of the resources required to operate offline and then when the network connection is disconnected, it can be used like before. |
|||
|
|||
You can configure those files as mentioned in the *Customize Service Workers* section down below. |
|||
|
|||
> If you want to share logic between those two service worker files, you can consider creating a third JS file and hold the common logic in this file or use the [self.importScripts](https://developer.mozilla.org/docs/Web/API/WorkerGlobalScope/importScripts)s to load the common logic into both service worker files. |
|||
|
|||
## Customization |
|||
|
|||
You can customize the `manifest.json`, `service-worker.js` and `service-worker.published.js` files generated by the ABP Framework if you created an application with PWA support. |
|||
|
|||
### Customize Web Application Manifest (`manifest.json`) |
|||
|
|||
> The web app manifest is a JSON file that tells the browser about your Progressive Web App and how it should behave when installed on the user's desktop or mobile device. A typical manifest file includes the app name, the icons the app should use, and the URL that should be opened when the app is launched. - From [web.dev](https://web.dev/add-manifest) |
|||
|
|||
You can customize the `manifest.json` file (under the **wwwroot** folder) to your needs. You can set the **name**, **short_name**, **icons**, **description**, **start_url**, etc. You can see an example `manifest.json` file content below: |
|||
|
|||
```json |
|||
{ |
|||
"name": "Acme.BookStore", |
|||
"short_name": "BookStore", |
|||
"description": "My application description", |
|||
"theme_color": "#000000", |
|||
"background_color": "#ffffff", |
|||
"icons": [ |
|||
{ |
|||
"src": "../icon-192.png", |
|||
"sizes": "192x192", |
|||
"type": "image/png" |
|||
}, |
|||
{ |
|||
"src": "../icon-512.png", |
|||
"sizes": "512x512", |
|||
"type": "image/png" |
|||
} |
|||
] |
|||
// other properties... |
|||
} |
|||
``` |
|||
|
|||
* You must provide at least the `short_name` or `name` property. If both of these properties are provided, the `short_name` property is used almost anywhere like the **launcher** and the **home** screen. |
|||
* For Chromium based browsers, you must provide at least a *192x192* px icon and a *512x512* px icon. If only those two icon sizes are provided, the browsers will automatically scale the icons to fit the device. If you don't want to let the browser auto-scale icons, you need to add icons for other sizes too. |
|||
|
|||
> You can see the other properties from [here](https://web.dev/add-manifest/#manifest-properties). |
|||
|
|||
### Customize Service Workers |
|||
|
|||
If you create your application with PWA support, two service worker files will be generated: `service-worker.js` and `service-worker.published.js`. |
|||
|
|||
ABP Framework's service-worker files are the same as the .NET Core's and it's valid for most of the time and you'll probably not need to configure it manually. However, if you want to configure the service workers you can do it easily. |
|||
|
|||
You can configure the `service-worker.js` file for debug mode and the `service-worker.published.js` file for release mode according to your own needs. |
|||
|
|||
#### `service-worker.js` |
|||
|
|||
```js |
|||
// Caution: In development, always fetch from the network and do not enable offline support. |
|||
self.addEventListener('fetch', () => { }); |
|||
``` |
|||
|
|||
* Configuring this file, you can use additional PWA features in debug mode. |
|||
* By default, it does nothing in debug mode, it fetches from the network (recommended) and does not support offline scenarios. You can change this behavior by configuring this file and also benefit from additional features of PWAs. |
|||
|
|||
#### `service-worker.published.js` |
|||
|
|||
```js |
|||
// Caution: Be sure you understand the caveats before publishing an application with |
|||
// offline support. See https://aka.ms/blazor-offline-considerations |
|||
|
|||
self.importScripts('./service-worker-assets.js'); |
|||
self.addEventListener('install', event => event.waitUntil(onInstall(event))); |
|||
self.addEventListener('activate', event => event.waitUntil(onActivate(event))); |
|||
self.addEventListener('fetch', event => event.respondWith(onFetch(event))); |
|||
|
|||
const cacheNamePrefix = 'offline-cache-'; |
|||
const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`; |
|||
const offlineAssetsInclude = [ /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/ ]; |
|||
const offlineAssetsExclude = [ /^service-worker\.js$/ ]; |
|||
|
|||
async function onInstall(event) { |
|||
console.info('Service worker: Install'); |
|||
|
|||
// Fetch and cache all matching items from the assets manifest |
|||
const assetsRequests = self.assetsManifest.assets |
|||
.filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url))) |
|||
.filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url))) |
|||
.map(asset => new Request(asset.url, { integrity: asset.hash, cache: 'no-cache' })); |
|||
await caches.open(cacheName).then(cache => cache.addAll(assetsRequests)); |
|||
} |
|||
|
|||
async function onActivate(event) { |
|||
console.info('Service worker: Activate'); |
|||
|
|||
// Delete unused caches |
|||
const cacheKeys = await caches.keys(); |
|||
await Promise.all(cacheKeys |
|||
.filter(key => key.startsWith(cacheNamePrefix) && key !== cacheName) |
|||
.map(key => caches.delete(key))); |
|||
} |
|||
|
|||
async function onFetch(event) { |
|||
let cachedResponse = null; |
|||
if (event.request.method === 'GET') { |
|||
// For all navigation requests, try to serve index.html from cache |
|||
// If you need some URLs to be server-rendered, edit the following check to exclude those URLs |
|||
const shouldServeIndexHtml = event.request.mode === 'navigate'; |
|||
|
|||
const request = shouldServeIndexHtml ? 'index.html' : event.request; |
|||
const cache = await caches.open(cacheName); |
|||
cachedResponse = await cache.match(request); |
|||
} |
|||
|
|||
return cachedResponse || fetch(event.request); |
|||
} |
|||
``` |
|||
|
|||
* You can configure this file if you want to cache additional file extensions such as `.webp` or etc. You can also use some additional features of PWA by configuring this file. |
|||
* By default, dll files (`*.dll`) and some static assets (`*.js`, `*.css`, etc.) are cached. |
|||
* Cached files will be stored in the `service-worker-assets.js` (**/bin/Debug/{TARGET FRAMEWORK}/wwwroot/service-worker-assets.js**). You can change this file name by renaming it in between the `ServiceWorkerAssetsManifest` tags on your `*.csproj` file. |
|||
|
|||
## See Also |
|||
|
|||
* [ASP.NET Core Blazor Progressive Web Application (PWA)](https://docs.microsoft.com/en-us/aspnet/core/blazor/progressive-web-app). |
|||
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
File diff suppressed because it is too large
@ -1,11 +1,11 @@ |
|||
{ |
|||
"version": "5.3.0-rc.2", |
|||
"version": "5.3.0-rc.3", |
|||
"name": "@abp/aspnetcore.components.server.basictheme", |
|||
"publishConfig": { |
|||
"access": "public" |
|||
}, |
|||
"dependencies": { |
|||
"@abp/aspnetcore.components.server.theming": "~5.3.0-rc.2" |
|||
"@abp/aspnetcore.components.server.theming": "~5.3.0-rc.3" |
|||
}, |
|||
"gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" |
|||
} |
|||
|
|||
@ -1,12 +1,12 @@ |
|||
{ |
|||
"version": "5.3.0-rc.2", |
|||
"version": "5.3.0-rc.3", |
|||
"name": "@abp/aspnetcore.components.server.theming", |
|||
"publishConfig": { |
|||
"access": "public" |
|||
}, |
|||
"dependencies": { |
|||
"@abp/bootstrap": "~5.3.0-rc.2", |
|||
"@abp/font-awesome": "~5.3.0-rc.2" |
|||
"@abp/bootstrap": "~5.3.0-rc.3", |
|||
"@abp/font-awesome": "~5.3.0-rc.3" |
|||
}, |
|||
"gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" |
|||
} |
|||
|
|||
@ -1,14 +1,14 @@ |
|||
{ |
|||
"version": "5.3.0-rc.2", |
|||
"version": "5.3.0-rc.3", |
|||
"name": "@abp/blogging", |
|||
"publishConfig": { |
|||
"access": "public" |
|||
}, |
|||
"dependencies": { |
|||
"@abp/aspnetcore.mvc.ui.theme.shared": "~5.3.0-rc.2", |
|||
"@abp/owl.carousel": "~5.3.0-rc.2", |
|||
"@abp/prismjs": "~5.3.0-rc.2", |
|||
"@abp/tui-editor": "~5.3.0-rc.2" |
|||
"@abp/aspnetcore.mvc.ui.theme.shared": "~5.3.0-rc.3", |
|||
"@abp/owl.carousel": "~5.3.0-rc.3", |
|||
"@abp/prismjs": "~5.3.0-rc.3", |
|||
"@abp/tui-editor": "~5.3.0-rc.3" |
|||
}, |
|||
"gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" |
|||
} |
|||
|
|||
@ -1,15 +1,15 @@ |
|||
{ |
|||
"version": "5.3.0-rc.2", |
|||
"version": "5.3.0-rc.3", |
|||
"name": "@abp/cms-kit.admin", |
|||
"publishConfig": { |
|||
"access": "public" |
|||
}, |
|||
"dependencies": { |
|||
"@abp/codemirror": "~5.3.0-rc.2", |
|||
"@abp/jstree": "~5.3.0-rc.2", |
|||
"@abp/slugify": "~5.3.0-rc.2", |
|||
"@abp/tui-editor": "~5.3.0-rc.2", |
|||
"@abp/uppy": "~5.3.0-rc.2" |
|||
"@abp/codemirror": "~5.3.0-rc.3", |
|||
"@abp/jstree": "~5.3.0-rc.3", |
|||
"@abp/slugify": "~5.3.0-rc.3", |
|||
"@abp/tui-editor": "~5.3.0-rc.3", |
|||
"@abp/uppy": "~5.3.0-rc.3" |
|||
}, |
|||
"gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" |
|||
} |
|||
|
|||
@ -1,12 +1,12 @@ |
|||
{ |
|||
"version": "5.3.0-rc.2", |
|||
"version": "5.3.0-rc.3", |
|||
"name": "@abp/cms-kit.public", |
|||
"publishConfig": { |
|||
"access": "public" |
|||
}, |
|||
"dependencies": { |
|||
"@abp/highlight.js": "~5.3.0-rc.2", |
|||
"@abp/star-rating-svg": "~5.3.0-rc.2" |
|||
"@abp/highlight.js": "~5.3.0-rc.3", |
|||
"@abp/star-rating-svg": "~5.3.0-rc.3" |
|||
}, |
|||
"gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" |
|||
} |
|||
|
|||
@ -1,12 +1,12 @@ |
|||
{ |
|||
"version": "5.3.0-rc.2", |
|||
"version": "5.3.0-rc.3", |
|||
"name": "@abp/cms-kit", |
|||
"publishConfig": { |
|||
"access": "public" |
|||
}, |
|||
"dependencies": { |
|||
"@abp/cms-kit.admin": "~5.3.0-rc.2", |
|||
"@abp/cms-kit.public": "~5.3.0-rc.2" |
|||
"@abp/cms-kit.admin": "~5.3.0-rc.3", |
|||
"@abp/cms-kit.public": "~5.3.0-rc.3" |
|||
}, |
|||
"gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" |
|||
} |
|||
|
|||
@ -1,15 +1,15 @@ |
|||
{ |
|||
"version": "5.3.0-rc.2", |
|||
"version": "5.3.0-rc.3", |
|||
"name": "@abp/docs", |
|||
"publishConfig": { |
|||
"access": "public" |
|||
}, |
|||
"dependencies": { |
|||
"@abp/anchor-js": "~5.3.0-rc.2", |
|||
"@abp/clipboard": "~5.3.0-rc.2", |
|||
"@abp/malihu-custom-scrollbar-plugin": "~5.3.0-rc.2", |
|||
"@abp/popper.js": "~5.3.0-rc.2", |
|||
"@abp/prismjs": "~5.3.0-rc.2" |
|||
"@abp/anchor-js": "~5.3.0-rc.3", |
|||
"@abp/clipboard": "~5.3.0-rc.3", |
|||
"@abp/malihu-custom-scrollbar-plugin": "~5.3.0-rc.3", |
|||
"@abp/popper.js": "~5.3.0-rc.3", |
|||
"@abp/prismjs": "~5.3.0-rc.3" |
|||
}, |
|||
"gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431" |
|||
} |
|||
|
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue