@ -1,21 +1,26 @@ |
|||
name: Compress Images |
|||
on: |
|||
pull_request: |
|||
paths: |
|||
- '**.jpg' |
|||
- '**.jpeg' |
|||
- '**.png' |
|||
- '**.webp' |
|||
jobs: |
|||
build: |
|||
if: github.event.pull_request.head.repo.full_name == github.repository |
|||
name: calibreapp/image-actions |
|||
runs-on: ubuntu-latest |
|||
steps: |
|||
- name: Checkout Repo |
|||
uses: actions/checkout@v2 |
|||
|
|||
- name: Compress Images |
|||
uses: calibreapp/image-actions@main |
|||
with: |
|||
githubToken: ${{ secrets.GITHUB_TOKEN }} |
|||
name: Compress Images |
|||
on: |
|||
pull_request: |
|||
paths: |
|||
- "**.jpg" |
|||
- "**.jpeg" |
|||
- "**.png" |
|||
- "**.webp" |
|||
types: |
|||
- opened |
|||
- synchronize |
|||
- reopened |
|||
- ready_for_review |
|||
jobs: |
|||
build: |
|||
if: github.event.pull_request.head.repo.full_name == github.repository && !github.event.pull_request.draft |
|||
name: calibreapp/image-actions |
|||
runs-on: ubuntu-latest |
|||
steps: |
|||
- name: Checkout Repo |
|||
uses: actions/checkout@v2 |
|||
|
|||
- name: Compress Images |
|||
uses: calibreapp/image-actions@main |
|||
with: |
|||
githubToken: ${{ secrets.GITHUB_TOKEN }} |
|||
|
|||
@ -1,5 +1,7 @@ |
|||
{ |
|||
"culture": "zh-Hans", |
|||
"texts": { |
|||
"AbpTitle": "ABP 框架 - 开源 Web 应用程序框架", |
|||
"AbpDescription": "ABP 是一个开源应用程序框架,专注于基于 AspNet Core 的 Web 应用程序开发。 Don't repeat yourself,专注于自己的业务代码。" |
|||
} |
|||
} |
|||
@ -0,0 +1,380 @@ |
|||
# ABP.IO Platform 6.0 RC Has Been Released |
|||
|
|||
Today, we are happy to release the [ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) version **6.0 RC** (release candidate). This blog post introduces the new features and important changes in this new version. |
|||
|
|||
> **The planned release date for the [6.0.0 Stable](https://github.com/abpframework/abp/milestone/71) version is September 06, 2022**. |
|||
|
|||
Try this version and provide feedback for the stable ABP v6.0! Thank you to all. |
|||
|
|||
## Get Started with the 6.0 RC |
|||
|
|||
Follow the steps below to try version 6.0.0 RC today: |
|||
|
|||
1) **Upgrade** the ABP CLI to version `6.0.0-rc.1` using a command line terminal: |
|||
|
|||
````bash |
|||
dotnet tool update Volo.Abp.Cli -g --version 6.0.0-rc.1 |
|||
```` |
|||
|
|||
**or install** it if you haven't before: |
|||
|
|||
````bash |
|||
dotnet tool install Volo.Abp.Cli -g --version 6.0.0-rc.1 |
|||
```` |
|||
|
|||
2) Create a **new application** with the `--preview` option: |
|||
|
|||
````bash |
|||
abp new BookStore --preview |
|||
```` |
|||
|
|||
See the [ABP CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all the available options. |
|||
|
|||
> You can also use the *Direct Download* tab on the [Get Started](https://abp.io/get-started) page by selecting the **Preview checkbox**. |
|||
|
|||
You can use any IDE that supports .NET 6.x, like **[Visual Studio 2022](https://visualstudio.microsoft.com/downloads/)**. |
|||
|
|||
## Migration Guides |
|||
|
|||
There are breaking changes in this version that may affect your application. |
|||
Please see the following migration documents, if you are upgrading from v5.3.0: |
|||
|
|||
* [ABP Framework 5.3 to 6.0 Migration Guide](https://docs.abp.io/en/abp/6.0/Migration-Guides/Abp-6_0) |
|||
* [ABP Commercial 5.3 to 6.0 Migration Guide](https://docs.abp.io/en/commercial/6.0/migration-guides/v6_0) |
|||
|
|||
## What's New with ABP Framework 6.0? |
|||
|
|||
In this section, I will introduce some major features released in this version. Here is a brief list of titles explained in the next sections: |
|||
|
|||
* **LeptonX Lite** is now the **default theme** for startup templates. |
|||
* Optional PWA support is added to [*Get Started*](https://abp.io/get-started) page. |
|||
* Introducing the **OpenIddict Module** and switching to OpenIddict for the startup templates. |
|||
* New **.NET MAUI** Startup Template. |
|||
* Introducing the `ITransientCachedServiceProvider` interface. |
|||
* Introducing the dynamic components for Blazor UI. |
|||
* Improvements on ABP CLI. |
|||
* Introducing the `Volo.Abp.RemoteServices` package. |
|||
* Create/Update user accounts for external logins. |
|||
* Sending test email in the setting page for MVC and Blazor user interfaces. |
|||
* Improvements on the **eShopOnAbp** project. |
|||
* Other news... |
|||
|
|||
### LeptonX Lite Theme on Startup Templates |
|||
|
|||
 |
|||
|
|||
With this version, startup templates (`app` and `app-nolayers` templates) use the **LeptonX Lite** as the default theme. However, it's still possible to create a project with **Basic Theme** either using the **ABP CLI** or downloading the project via [*Get Started*](https://abp.io/get-started) page on the [abp.io](https://abp.io/) website. |
|||
|
|||
#### via ABP CLI |
|||
|
|||
To create a new project with **Basic Theme**, you can use the `--theme` option as below: |
|||
|
|||
```bash |
|||
abp new Acme.BookStore --theme basic --preview |
|||
``` |
|||
|
|||
#### via Get Started page |
|||
|
|||
Also, you can create a new project with **LeptonX Lite** or **Basic Theme** on *Get Started* page. |
|||
|
|||
 |
|||
|
|||
> The "Preview" checkbox should be checked to be able to see the theme selection section on the *Get Started* page. |
|||
|
|||
|
|||
|
|||
### Optional PWA Support is Added to the Get Started Page |
|||
|
|||
We've introduced the PWA (Progressive Web Application) support for the startup templates for Angular & Blazor WASM UIs in **v5.3**. In this version, we also added this PWA support to the [*Get Started*](https://abp.io/get-started) page on the [abp.io](https://abp.io/) website. |
|||
|
|||
 |
|||
|
|||
If you check the "Progressive Web Application" checkbox while creating an application, the all required configurations will be done for you and you will get the benefit of PWA features in your application. |
|||
|
|||
|
|||
|
|||
### Introducing the **OpenIddict Module** and Switching to OpenIddict in the Startup Templates |
|||
|
|||
We already [announced the plan of replacing the IdentityServer with OpenIddict](https://github.com/abpframework/abp/issues/11989). |
|||
|
|||
Therefore, we have created the `OpenIddict` module in this version and switched to **OpenIddict** in the startup templates. The ABP Framework uses this module to add **OAuth** features to the applications. We created documentation for the **OpenIddict Module**. |
|||
|
|||
- You can see the following document to **learn about the OpenIddict Module**: |
|||
[https://docs.abp.io/en/abp/6.0/Modules/OpenIddict](https://docs.abp.io/en/abp/6.0/Modules/OpenIddict) |
|||
- You can check out the following migration guide to learn **how to migrate to OpenIddict**: |
|||
[https://docs.abp.io/en/abp/6.0/Migration-Guides/IdentityServer_To_OpenIddict](https://docs.abp.io/en/abp/6.0/Migration-Guides/IdentityServer_To_OpenIddict) |
|||
|
|||
|
|||
|
|||
> We will continue to ship Identity Server packages for a while but in the long term, you may need to replace it, because Identity Server support ends at the end of 2022. Please see the [announcement]((https://github.com/abpframework/abp/issues/11989)) for more info. |
|||
|
|||
|
|||
|
|||
### New .NET MAUI Startup Template |
|||
|
|||
 |
|||
|
|||
ABP Framework provides .NET MAUI startup templates with **v6.0.0**. You can create a new .NET MAUI project with the command below: |
|||
|
|||
```bash |
|||
abp new Acme.BookStore -t maui |
|||
``` |
|||
|
|||
|
|||
|
|||
### Introducing the `ITransientCachedServiceProvider` |
|||
|
|||
`ICachedServiceProvider` interface is used to resolve the cached services within a new scope. We created a new interface to resolve cached services **without creating scopes**. It's called `ITransientCachedServiceProvider`. The difference between `ICachedServiceProvider` and `ITransientCachedServiceProvider` is; `ITransientCachedServiceProvider` is transient. Check out [this issue](https://github.com/abpframework/abp/issues/12918) for more information. |
|||
|
|||
|
|||
|
|||
### Introducing the dynamic layout components for Blazor UI |
|||
|
|||
ABP Framework provides different ways of customizing the UI and one of them is to use [Layout Hooks](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Layout-Hooks) in MVC. The **Layout Hook System** allows you to add code to some specific parts of the layout and all layouts of the themes provided by the ABP Framework implement these hooks. |
|||
|
|||
However, Blazor UI doesn't have such a system yet and we are planning to implement [Layout Hooks for the Blazor UI](https://github.com/abpframework/abp/issues/6261) in version 7.0. |
|||
|
|||
We are introducing the dynamic layout components for the Blazor UI to be able to add components to the Blazor layouts. |
|||
|
|||
You can configure the `AbpDynamicLayoutComponentOptions` to render your components in the layout, as below: |
|||
|
|||
```csharp |
|||
Configure<AbpDynamicLayoutComponentOptions>(options => |
|||
{ |
|||
options.Components.Add(typeof(MyBlazorComponent), null); |
|||
}); |
|||
``` |
|||
|
|||
|
|||
|
|||
### Improvements in ABP CLI |
|||
|
|||
There are some enhancements in [ABP CLI](https://docs.abp.io/en/abp/6.0/CLI). You can see the brief list of some of these improvements below: |
|||
|
|||
* You can list all available templates by using the `abp list-templates` command with v6.0. See [#13083](https://github.com/abpframework/abp/pull/13083). |
|||
* You can select the theme when creating a new project by specifying the `--theme` option. You can see the *LeptonX Lite Theme on the Startup Templates* section above for an example. |
|||
* `abp update` command has been updating the version of the main application until now. With v6.0.0, this command updates all package versions **inside all solutions in the sub-folders**. Checkout the issue [#12735](https://github.com/abpframework/abp/pull/12738) for more information. |
|||
|
|||
|
|||
|
|||
### Introducing the `Volo.Abp.RemoteService` Package |
|||
|
|||
A new `Volo.Abp.RemoteService` package has been added to the framework. Some of the classes that are related to the remote service configurations such as `AbpRemoteServiceOptions` class moved from `Volo.Abp.Http.Client` to this package. In this way, it became more reusable for further usages. |
|||
|
|||
|
|||
|
|||
### Create/Update User Accounts For External Logins |
|||
|
|||
If a user authenticates from an external provider like `Keycloak`, the user is being redirected to this external provider, and comes back to the main application. In this process, the user's data is not being saved in the main application's database. With this version, ABP saves the user information and lists in the users page. And this fixes permission management, user information mismatches and other issues. For more info, see [the related issue](https://github.com/abpframework/abp/issues/12203). |
|||
|
|||
|
|||
|
|||
### Sending test email in the setting page for MVC and Blazor UIs |
|||
|
|||
"Sending Test Email" feature is added to the [Setting Management](https://docs.abp.io/en/abp/6.0/Modules/Setting-Management) module, which allows checking the email settings are configured properly and sending emails successfully to the target email address. |
|||
|
|||
 |
|||
|
|||
After configuring the email settings such as the target email address, you can click the "Send" button to send a test email to see if everything went well. |
|||
|
|||
> Note that this feature will be implemented for the Angular UI in the stable v6.0. |
|||
|
|||
|
|||
|
|||
### Improvements on eShopOnAbp Project |
|||
|
|||
The following improvements have been made on [eShopOnAbp project](https://github.com/abpframework/eShopOnAbp) with this version: |
|||
|
|||
* Some improvements have been made on the Admin Application for Order Management for Angular UI. See [#110](https://github.com/abpframework/eShopOnAbp/pull/110). |
|||
* `SignalR` error on Kubernetes & Docker Compose has been fixed. See [#113](https://github.com/abpframework/eShopOnAbp/pull/113). |
|||
* eShopOnAbp project has been deployed to Azure Kubernetes Service. See [#114](https://github.com/abpframework/eShopOnAbp/pull/114). The live demo can be seen from [eshoponabp.com](https://eshoponabp.com/). |
|||
* Configurations have been made for some services on the `docker-compose.yml` file. See [#112](https://github.com/abpframework/eShopOnAbp/pull/112). |
|||
* Gateway Redirect Loop problem on Kubernetes has been fixed. See [the commit](https://github.com/abpframework/eShopOnAbp/commit/6413ef15c91cd8a5309050b63bb4dbca23587607). |
|||
|
|||
|
|||
|
|||
### Other News |
|||
|
|||
* Autofac library has been upgraded to **v6.4.0**. Please see [#12816](https://github.com/abpframework/abp/pull/12816) for more info. |
|||
* Performance Improvements have been made in the **Settings Module** and tabs on the *Settings* page are lazy loading now. |
|||
* Some improvements have been made in the CMS Kit Module. You can see the improvements from [here](https://github.com/abpframework/abp/issues/11965). |
|||
|
|||
If you want to see more details, you can check [the release on GitHub](https://github.com/abpframework/abp/releases/tag/6.0.0-rc.1), which contains a list of all the issues and pull requests closed in this version. |
|||
|
|||
|
|||
|
|||
## What's New with ABP Commercial 6.0? |
|||
|
|||
|
|||
|
|||
### LeptonX Theme is the Default Theme |
|||
|
|||
With this version, the startup templates (`app-pro`, `app-nolayers-pro` and `microservice-pro` templates) use the **LeptonX Theme** as the default theme. However, it's still possible to create a new project with **Lepton Theme** or **Basic Theme**, either using the **ABP CLI** or **ABP Suite**. |
|||
|
|||
#### via ABP CLI |
|||
|
|||
To create a new project with **Lepton Theme** or **Basic Theme**, you can use the `--theme` option as below. For "Basic Theme" specify the theme name as `--theme basic`. |
|||
|
|||
```bash |
|||
abp new Acme.BookStore --theme lepton --preview |
|||
``` |
|||
|
|||
|
|||
|
|||
#### via ABP Suite |
|||
|
|||
Also, you can create a new project with **Lepton Theme** or **Basic Theme** from ABP Suite. |
|||
|
|||
 |
|||
|
|||
### Switching to OpenIddict in the Startup Templates |
|||
|
|||
We have also switched to the **OpenIddict** for the startup templates for ABP Commercial as explained above. |
|||
|
|||
|
|||
|
|||
### New .NET MAUI Mobile |
|||
|
|||
 |
|||
|
|||
ABP Commercial has been providing a [React Native](https://docs.abp.io/en/commercial/latest/getting-started-react-native) mobile app since with the very early versions. Alternative to this application, we created a new .NET MAUI mobile app. To create a new `app-pro` ABP project with the .NET MAUI mobile app, you can use the command below: |
|||
|
|||
```bash |
|||
abp new Acme.BookStore -t app-pro --mobile maui |
|||
``` |
|||
|
|||
> Note that, when Microsoft supports `WebAuthenticator` on Windows, we'll also support it to work on Windows OS. |
|||
|
|||
|
|||
|
|||
### GDPR: Cookie Consent |
|||
|
|||
 |
|||
|
|||
With this version, the **Cookie Consent** feature has been added to the **GDPR** module. It's enabled by default for the new startup templates. There are two pages in the templates: "Cookie Policy" page and "Privacy Policy" page. |
|||
|
|||
If you want to disable/hide the "Cookie Consent", you can simply open the startup project module class and set the `IsEnabled` property as **false** for the **AddAbpCookieConsent** method as below: |
|||
|
|||
```csharp |
|||
context.Services.AddAbpCookieConsent(options => |
|||
{ |
|||
options.IsEnabled = false; //disabled |
|||
options.CookiePolicyUrl = "/CookiePolicy"; |
|||
options.PrivacyPolicyUrl = "/PrivacyPolicy"; |
|||
}); |
|||
``` |
|||
|
|||
> These pages are used to build up the cookie consent text and you can change the content or url of these pages by your needs. |
|||
|
|||
If you want to use the Cookie Consent feature of the GDPR module in your existing project, please see the [GDPR Module](https://docs.abp.io/en/commercial/6.0/modules/gdpr) documentation for configurations. |
|||
|
|||
### Improvements/Developments on CMS Kit Poll |
|||
|
|||
Some improvements have been made on the Poll System of CMS Kit module as listed below: |
|||
|
|||
* The Widget rendering and Admin side for the Blazor UI improvements. |
|||
* A Widget can be picked from the editor as seen in the image below. |
|||
|
|||
 |
|||
|
|||
|
|||
|
|||
### Blazor UI for the Chat Module |
|||
|
|||
Chat Module is now also available for the Blazor UI after the MVC and Angular UIs. You can read the [Chat Module](https://docs.abp.io/en/commercial/6.0/modules/chat) documentation to get the overall knowledge about the module and add to your application. |
|||
|
|||
 |
|||
 |
|||
|
|||
|
|||
|
|||
### Blazor Admin UI for CMS Kit Module |
|||
|
|||
All admin side **CMS Kit** and **CMS Kit Pro** features have been implemented for the Blazor UI. Blazor UI will only be available to ABP Commercial customers. |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
|
|||
### Suite: Excel Export |
|||
|
|||
With v6.0, now it's possible to export the records as Excel for Blazor & MVC UIs. Angular UI is still in-progress, and we will implement it with the stable v6.0 release. Check the "Excel export" checkbox to add this feature. |
|||
|
|||
 |
|||
|
|||
|
|||
|
|||
A new Excel Export button is being located at the top of the generated page as seen below: |
|||
|
|||
 |
|||
|
|||
Then, you can download the records as `.xlsx` format by clicking the "Excel Export" button. Note that the exported Excel list is the filtered list. |
|||
|
|||
|
|||
|
|||
### ABP Suite: Optional PWA Support |
|||
|
|||
With this version, it's possible to add the [PWA (Progressive Web App)](https://web.dev/progressive-web-apps/?gclid=Cj0KCQjwxIOXBhCrARIsAL1QFCY0IB-W5k-lsXmRCbm00sl4nyBIYynAX3IdJkjyizyNUjuCE8zeu24aApxtEALw_wcB) support for Blazor & Angular UIs while creating the application via Suite. |
|||
|
|||
 |
|||
|
|||
You just need to check the "Progressive web application" checkbox, when creating a new application. Then, ABP Suite will add the PWA support to your application. When you publish your application, you get the full benefits of PWA features such as offline support. |
|||
|
|||
|
|||
|
|||
### Other News |
|||
|
|||
#### Explainer Videos |
|||
|
|||
We are creating explainer videos for the ABP Commercial Modules to provide an overview. Within this milestone, we've created four new explainer videos: |
|||
|
|||
* [Audit Logging Module](https://www.youtube.com/watch?v=NzSuFBpqfsc) |
|||
* [Identity Module](https://www.youtube.com/watch?v=W87jA_GBE54) |
|||
* [SaaS Module](https://www.youtube.com/watch?v=xXlaaXP6qqQ) |
|||
* [Forms Module](https://www.youtube.com/watch?v=MousWEPfrA8) |
|||
|
|||
You can subscribe to [Volosoft's YouTube channel](https://www.youtube.com/channel/UCO3XKlpvq8CA5MQNVS6b3dQ) to be informed about the future ABP events and videos. |
|||
|
|||
|
|||
|
|||
### Trial License is now available! |
|||
|
|||
 |
|||
|
|||
If you are considering purchasing a new ABP Commercial license, and you want to see ABP in action then, check out https://commercial.abp.io/pricing and click FREE TRIAL button. |
|||
|
|||
|
|||
|
|||
## Community News |
|||
|
|||
### New ABP Community Posts |
|||
|
|||
* [Alper Ebicoglu](https://twitter.com/alperebicoglu) has created a new community article to give a full overview of .NET MAUI. You can read it [here](https://community.abp.io/posts/all-about-.net-maui-gb4gkdg5). |
|||
* [Anto Subash](https://twitter.com/antosubash) has created a new video content to show "State Management in Blazor with Fluxor". You can read it [here](https://community.abp.io/posts/blazor-state-management-with-fluxor-raskpv19). |
|||
* [Learn ABP Framework](https://community.abp.io/members/learnabp) has also created a new video content to show "How to install LeptonX Lite Theme for ABP Framework 5.3 MVC UI". You can read it [here](https://community.abp.io/posts/how-to-install-leptonx-lite-theme-on-abp-framework-5.3-mvc-ui-epzng137). |
|||
* [Kirti Kulkarni](https://twitter.com/kirtimkulkarni) has created three new community articles. You can use the links below to read the articles: |
|||
* [Integrating the file management module with ABP Commercial application](https://community.abp.io/posts/integrating-the-file-management-module-with-abp-commercial-application-qd6v4dsr) |
|||
* [Work with PDF's in ABP Commercial Project using PDFTron](https://community.abp.io/posts/work-with-pdfs-in-abp-commercial-project-using-pdftron-tjw0hlgu) |
|||
* [Create a custom login page in ABP Commercial Angular app](https://community.abp.io/posts/create-a-custom-login-page-in-abp-commercial-angular-app-r2huidx7) |
|||
* [Don Boutwell](https://community.abp.io/members/dboutwell) has created his first ABP Community article. You can read it from [here](https://community.abp.io/posts/password-required-redis-with-abp-framework-and-docker-94old5rm). |
|||
|
|||
|
|||
|
|||
### Volosoft Has Attended the DNF Summit 2022 |
|||
|
|||
 |
|||
|
|||
Core team members of ABP Framework, [Halil Ibrahim Kalkan](https://twitter.com/hibrahimkalkan) and [Alper Ebicoglu](https://twitter.com/alperebicoglu) have attended the [DNF Summit](https://t.co/ngWnBLiAn5) on the 20th of July. Halil Ibrahim Kalkan talked about the creation of the ABP Framework and Alper Ebicoglu showed how easy to create a project with ABP Framework within 15 minutes. |
|||
|
|||
Watch DNF Summit session 👉 https://www.youtube.com/embed/VL0ewZ-0ruo |
|||
|
|||
 |
|||
|
|||
|
|||
|
|||
## Conclusion |
|||
|
|||
This version comes with some features and enhancements to the existing features. You can see the [Road Map](https://docs.abp.io/en/abp/6.0/Road-Map) documentation to learn about the release schedule and planned features for the next releases. The planned release date for the [6.0.0 Stable](https://github.com/abpframework/abp/milestone/71) version is September 06, 2022. Please try the ABP v6.0 RC and provide feedback to us. |
|||
|
|||
Thanks for being a part of this community! |
|||
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 187 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 113 KiB |
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 345 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 37 KiB |
@ -0,0 +1,690 @@ |
|||
# How to Design Multi-Lingual Entity |
|||
|
|||
## Introduction |
|||
|
|||
If you want to open up to the global market these days, end-to-end localization is a must. ABP provides an already established infrastructure for static texts. However, this may not be sufficient for many applications. You may need to fully customize your app for a particular language and region. |
|||
|
|||
Let's take a look at a few quotes from Christian Arno's article "[How Foreign-Language Internet Strategies Boost Sales](https://www.mediapost.com/publications/article/155250/how-foreign-language-internet-strategies-boost-sal.html)" to better understand the impact of this: |
|||
|
|||
- 82% of European consumers are less likely to buy online if the site is not in their native tongue ([Eurobarometer survey](http://europa.eu/rapid/pressReleasesAction.do?reference=IP/11/556)). |
|||
- 72.4% of global consumers are more likely to buy a product if the information is available in their own language ([Common Sense Advisory](http://www.commonsenseadvisory.com/)). |
|||
- The English language currently only accounts for 31% of all online use, and more than half of all searches are in languages other than English. |
|||
- Today, 42% of all Internet users are in Asia, while almost one-quarter are in Europe and just over 10% are in Latin America. |
|||
|
|||
- Foreign languages have experienced exponential growth in online usage in the past decade -- with Chinese now officially the [second-most-prominent-language](http://english.peopledaily.com.cn/90001/90776/90882/7438489.html) on the Web. [Arabic](http://www.internetworldstats.com/stats7.htm) has increased by a whopping 2500%, while English has only risen by 204% |
|||
|
|||
If you are looking for ways to expand your market share by fully customizing your application for a particular language and region, in this article I will explain how you can do it with ABP framework. |
|||
|
|||
### Source Code |
|||
|
|||
You can find the source code of the application at [abpframework/abp-samples](https://github.com/abpframework/abp-samples/tree/master/AcmeBookStoreMultiLingual). |
|||
|
|||
### Demo of the Final Application |
|||
|
|||
At the end of this article, we will have created an application same as in the gif below. |
|||
|
|||
 |
|||
|
|||
## Development |
|||
|
|||
In order to keep the article short and get rid of unrelated information in the article (like defining entities etc.), we'll be using the [BookStore](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) example, which is used in the "[Web Application Development Tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=MVC&DB=EF)" documentation of ABP Framework and we will make the Book entity as multi-lingual. If you do not want to finish this tutorial, you can find the application [here](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore). |
|||
|
|||
### Determining the data model |
|||
|
|||
We need a robust, maintainable, and efficient data model to store content in multiple languages. |
|||
|
|||
> I read many articles to determine the data model correctly, and as a result, I decided to use one of the many approaches that suit us. |
|||
> However, as in everything, there is a trade-off here. If you are wondering about the advantages and disadvantages of the model we will implement compared to other models, I recommend you to read [this article](https://vertabelo.com/blog/data-modeling-for-multiple-languages-how-to-design-a-localization-ready-system/). |
|||
|
|||
 |
|||
|
|||
As a result of the tutorial, we already have the `Book` and `Author` entities, as an extra, we will just add the `BookTranslation`. |
|||
|
|||
> In the article, we will make the Name property of the Book entity multi-lingual, but the article is independent of the Book entity, you can make the entity you want multi-lingual with similar codes according to your requirements. |
|||
|
|||
#### Acme.BookStore.Domain.Shared |
|||
|
|||
Create a folder named `MultiLingualObjects` and create the following interfaces in its contents. |
|||
|
|||
We will use the `IObjectTranslation` interface to mark the translation of a multi-lingual entity: |
|||
|
|||
```csharp |
|||
public interface IObjectTranslation |
|||
{ |
|||
string Language { get; set; } |
|||
} |
|||
``` |
|||
|
|||
We will use the `IMultiLingualObject<TTranslation>` interface to mark multi-lingual entities: |
|||
|
|||
```csharp |
|||
public interface IMultiLingualObject<TTranslation> |
|||
where TTranslation : class, IObjectTranslation |
|||
{ |
|||
ICollection<TTranslation> Translations { get; set; } |
|||
} |
|||
``` |
|||
|
|||
#### Acme.BookStore.Domain |
|||
|
|||
In the `Books` folder, create the `BookTranslation` class as follows: |
|||
|
|||
```csharp |
|||
public class BookTranslation : Entity, IObjectTranslation |
|||
{ |
|||
public Guid BookId { get; set; } |
|||
|
|||
public string Name { get; set; } |
|||
|
|||
public string Language { get; set; } |
|||
|
|||
public override object[] GetKeys() |
|||
{ |
|||
return new object[] {BookId, Language}; |
|||
} |
|||
} |
|||
``` |
|||
|
|||
`BookTranslation` contains the `Language` property, which contains a language code for translation and a reference to the multi-lingual entity. We also have the `BookId` foreign key to help us know which book is translated. |
|||
|
|||
Implement `IMultiLingualObject` in the `Book` class as follows: |
|||
|
|||
```csharp |
|||
public class Book : AuditedAggregateRoot<Guid>, IMultiLingualObject<BookTranslation> |
|||
{ |
|||
public Guid AuthorId { get; set; } |
|||
|
|||
public string Name { get; set; } |
|||
|
|||
public BookType Type { get; set; } |
|||
|
|||
public DateTime PublishDate { get; set; } |
|||
|
|||
public float Price { get; set; } |
|||
|
|||
public ICollection<BookTranslation> Translations { get; set; } |
|||
} |
|||
``` |
|||
|
|||
Create a folder named `MultiLingualObjects` and add the following class inside of this folder: |
|||
|
|||
```csharp |
|||
public class MultiLingualObjectManager : ITransientDependency |
|||
{ |
|||
protected const int MaxCultureFallbackDepth = 5; |
|||
|
|||
public async Task<TTranslation> FindTranslationAsync<TMultiLingual, TTranslation>( |
|||
TMultiLingual multiLingual, |
|||
string culture = null, |
|||
bool fallbackToParentCultures = true) |
|||
where TMultiLingual : IMultiLingualObject<TTranslation> |
|||
where TTranslation : class, IObjectTranslation |
|||
{ |
|||
culture ??= CultureInfo.CurrentUICulture.Name; |
|||
|
|||
if (multiLingual.Translations.IsNullOrEmpty()) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
var translation = multiLingual.Translations.FirstOrDefault(pt => pt.Language == culture); |
|||
if (translation != null) |
|||
{ |
|||
return translation; |
|||
} |
|||
|
|||
if (fallbackToParentCultures) |
|||
{ |
|||
translation = GetTranslationBasedOnCulturalRecursive( |
|||
CultureInfo.CurrentUICulture.Parent, |
|||
multiLingual.Translations, |
|||
0 |
|||
); |
|||
|
|||
if (translation != null) |
|||
{ |
|||
return translation; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
protected TTranslation GetTranslationBasedOnCulturalRecursive<TTranslation>( |
|||
CultureInfo culture, ICollection<TTranslation> translations, int currentDepth) |
|||
where TTranslation : class, IObjectTranslation |
|||
{ |
|||
if (culture == null || |
|||
culture.Name.IsNullOrWhiteSpace() || |
|||
translations.IsNullOrEmpty() || |
|||
currentDepth > MaxCultureFallbackDepth) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
var translation = translations.FirstOrDefault(pt => pt.Language.Equals(culture.Name, StringComparison.OrdinalIgnoreCase)); |
|||
return translation ?? GetTranslationBasedOnCulturalRecursive(culture.Parent, translations, currentDepth + 1); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
With `MultiLingualObjectManager`'s `FindTranslationAsync` method, we get the translated version of the book according to `CurrentUICulture`. If no translation of culture is found, we return null. |
|||
|
|||
> Every thread in .NET has `CurrentCulture` and `CurrentUICulture` objects. |
|||
|
|||
#### Acme.BookStore.EntityFrameworkCore |
|||
|
|||
In the `OnModelCreating` method of the `BookStoreDbContext` class, configure the `BookTranslation` as follows: |
|||
|
|||
```csharp |
|||
builder.Entity<BookTranslation>(b => |
|||
{ |
|||
b.ToTable(BookStoreConsts.DbTablePrefix + "BookTranslations", |
|||
BookStoreConsts.DbSchema); |
|||
|
|||
b.ConfigureByConvention(); |
|||
|
|||
b.HasKey(x => new {x.BookId, x.Language}); |
|||
}); |
|||
``` |
|||
|
|||
> I haven't explicitly set up a one-to-many relationship between `Book` and `BookTranslation` here, but the entity framework will do it for us. |
|||
|
|||
After that, you can just run the following command in a command-line terminal to add a new database migration (in the directory of the `EntityFrameworkCore` project): |
|||
|
|||
```bash |
|||
dotnet ef migrations add Added_BookTranslation |
|||
``` |
|||
|
|||
This will add a new migration class to your project. You can then run the following command (or run the `.DbMigrator` application) to apply changes to the database: |
|||
|
|||
```bash |
|||
dotnet ef database update |
|||
``` |
|||
|
|||
Add the following code to the `ConfigureServices` method of the `BookStoreEntityFrameworkCoreModule`: |
|||
|
|||
```csharp |
|||
Configure<AbpEntityOptions>(options => |
|||
{ |
|||
options.Entity<Book>(bookOptions => |
|||
{ |
|||
bookOptions.DefaultWithDetailsFunc = query => query.Include(o => o.Translations); |
|||
}); |
|||
}); |
|||
``` |
|||
|
|||
Now we can use `WithDetailsAsync` without any parameters on `BookAppService` knowing that `Translations` will be included. |
|||
|
|||
#### Acme.BookStore.Application.Contracts |
|||
|
|||
Implement `IObjectTranslation` in the `BookDto` class as follows: |
|||
|
|||
```csharp |
|||
public class BookDto : AuditedEntityDto<Guid>, IObjectTranslation |
|||
{ |
|||
public Guid AuthorId { get; set; } |
|||
|
|||
public string AuthorName { get; set; } |
|||
|
|||
public string Name { get; set; } |
|||
|
|||
public BookType Type { get; set; } |
|||
|
|||
public DateTime PublishDate { get; set; } |
|||
|
|||
public float Price { get; set; } |
|||
|
|||
public string Language { get; set; } |
|||
} |
|||
``` |
|||
|
|||
`Language` property is required to understand which language the translated book name belongs to in the UI. |
|||
|
|||
Create the `AddBookTranslationDto` class in the `Books` folder as follows: |
|||
|
|||
```csharp |
|||
public class AddBookTranslationDto : IObjectTranslation |
|||
{ |
|||
[Required] |
|||
public string Language { get; set; } |
|||
|
|||
[Required] |
|||
public string Name { get; set; } |
|||
} |
|||
``` |
|||
|
|||
Add the `AddTranslationsAsync` method to the `IBookAppService` as follows: |
|||
|
|||
```csharp |
|||
public interface IBookAppService : |
|||
ICrudAppService< |
|||
BookDto, |
|||
Guid, |
|||
PagedAndSortedResultRequestDto, |
|||
CreateUpdateBookDto> |
|||
{ |
|||
Task<ListResultDto<AuthorLookupDto>> GetAuthorLookupAsync(); |
|||
|
|||
Task AddTranslationsAsync(Guid id, AddBookTranslationDto input); // added this line |
|||
} |
|||
``` |
|||
|
|||
#### Acme.BookStore.Application |
|||
|
|||
Now, we need to implement the `AddTranslationsAsync` method in `BookAppService` and include `Translations` in the `Book` entity, for this you can change the `BookAppService` as follows: |
|||
|
|||
```csharp |
|||
[Authorize(BookStorePermissions.Books.Default)] |
|||
public class BookAppService : |
|||
CrudAppService< |
|||
Book, //The Book entity |
|||
BookDto, //Used to show books |
|||
Guid, //Primary key of the book entity |
|||
PagedAndSortedResultRequestDto, //Used for paging/sorting |
|||
CreateUpdateBookDto>, //Used to create/update a book |
|||
IBookAppService //implement the IBookAppService |
|||
{ |
|||
private readonly IAuthorRepository _authorRepository; |
|||
|
|||
public BookAppService( |
|||
IRepository<Book, Guid> repository, |
|||
IAuthorRepository authorRepository) |
|||
: base(repository) |
|||
{ |
|||
_authorRepository = authorRepository; |
|||
GetPolicyName = BookStorePermissions.Books.Default; |
|||
GetListPolicyName = BookStorePermissions.Books.Default; |
|||
CreatePolicyName = BookStorePermissions.Books.Create; |
|||
UpdatePolicyName = BookStorePermissions.Books.Edit; |
|||
DeletePolicyName = BookStorePermissions.Books.Create; |
|||
} |
|||
|
|||
public override async Task<BookDto> GetAsync(Guid id) |
|||
{ |
|||
//Get the IQueryable<Book> from the repository |
|||
var queryable = await Repository.WithDetailsAsync(); // this line changed |
|||
|
|||
//Prepare a query to join books and authors |
|||
var query = from book in queryable |
|||
join author in await _authorRepository.GetQueryableAsync() on book.AuthorId equals author.Id |
|||
where book.Id == id |
|||
select new { book, author }; |
|||
|
|||
//Execute the query and get the book with author |
|||
var queryResult = await AsyncExecuter.FirstOrDefaultAsync(query); |
|||
if (queryResult == null) |
|||
{ |
|||
throw new EntityNotFoundException(typeof(Book), id); |
|||
} |
|||
|
|||
var bookDto = ObjectMapper.Map<Book, BookDto>(queryResult.book); |
|||
bookDto.AuthorName = queryResult.author.Name; |
|||
return bookDto; |
|||
} |
|||
|
|||
public override async Task<PagedResultDto<BookDto>> GetListAsync(PagedAndSortedResultRequestDto input) |
|||
{ |
|||
//Get the IQueryable<Book> from the repository |
|||
var queryable = await Repository.WithDetailsAsync(); // this line changed |
|||
|
|||
//Prepare a query to join books and authors |
|||
var query = from book in queryable |
|||
join author in await _authorRepository.GetQueryableAsync() on book.AuthorId equals author.Id |
|||
select new {book, author}; |
|||
|
|||
//Paging |
|||
query = query |
|||
.OrderBy(NormalizeSorting(input.Sorting)) |
|||
.Skip(input.SkipCount) |
|||
.Take(input.MaxResultCount); |
|||
|
|||
//Execute the query and get a list |
|||
var queryResult = await AsyncExecuter.ToListAsync(query); |
|||
|
|||
//Convert the query result to a list of BookDto objects |
|||
var bookDtos = queryResult.Select(x => |
|||
{ |
|||
var bookDto = ObjectMapper.Map<Book, BookDto>(x.book); |
|||
bookDto.AuthorName = x.author.Name; |
|||
return bookDto; |
|||
}).ToList(); |
|||
|
|||
//Get the total count with another query |
|||
var totalCount = await Repository.GetCountAsync(); |
|||
|
|||
return new PagedResultDto<BookDto>( |
|||
totalCount, |
|||
bookDtos |
|||
); |
|||
} |
|||
|
|||
public async Task<ListResultDto<AuthorLookupDto>> GetAuthorLookupAsync() |
|||
{ |
|||
var authors = await _authorRepository.GetListAsync(); |
|||
|
|||
return new ListResultDto<AuthorLookupDto>( |
|||
ObjectMapper.Map<List<Author>, List<AuthorLookupDto>>(authors) |
|||
); |
|||
} |
|||
|
|||
public async Task AddTranslationsAsync(Guid id, AddBookTranslationDto input) |
|||
{ |
|||
var queryable = await Repository.WithDetailsAsync(); |
|||
|
|||
var book = await AsyncExecuter.FirstOrDefaultAsync(queryable, x => x.Id == id); |
|||
|
|||
if (book.Translations.Any(x => x.Language == input.Language)) |
|||
{ |
|||
throw new UserFriendlyException($"Translation already available for {input.Language}"); |
|||
} |
|||
|
|||
book.Translations.Add(new BookTranslation |
|||
{ |
|||
BookId = book.Id, |
|||
Name = input.Name, |
|||
Language = input.Language |
|||
}); |
|||
|
|||
await Repository.UpdateAsync(book); |
|||
} |
|||
|
|||
private static string NormalizeSorting(string sorting) |
|||
{ |
|||
if (sorting.IsNullOrEmpty()) |
|||
{ |
|||
return $"book.{nameof(Book.Name)}"; |
|||
} |
|||
|
|||
if (sorting.Contains("authorName", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
return sorting.Replace( |
|||
"authorName", |
|||
"author.Name", |
|||
StringComparison.OrdinalIgnoreCase |
|||
); |
|||
} |
|||
|
|||
return $"book.{sorting}"; |
|||
} |
|||
} |
|||
``` |
|||
|
|||
Create the `MultiLingualBookObjectMapper` class as follows: |
|||
|
|||
```csharp |
|||
public class MultiLingualBookObjectMapper : IObjectMapper<Book, BookDto>, ITransientDependency |
|||
{ |
|||
private readonly MultiLingualObjectManager _multiLingualObjectManager; |
|||
|
|||
private readonly ISettingProvider _settingProvider; |
|||
|
|||
public MultiLingualBookObjectMapper( |
|||
MultiLingualObjectManager multiLingualObjectManager, |
|||
ISettingProvider settingProvider) |
|||
{ |
|||
_multiLingualObjectManager = multiLingualObjectManager; |
|||
_settingProvider = settingProvider; |
|||
} |
|||
|
|||
public BookDto Map(Book source) |
|||
{ |
|||
var translation = AsyncHelper.RunSync(() => |
|||
_multiLingualObjectManager.FindTranslationAsync<Book, BookTranslation>(source)); |
|||
|
|||
return new BookDto |
|||
{ |
|||
Id = source.Id, |
|||
AuthorId = source.AuthorId, |
|||
Type = source.Type, |
|||
Name = translation?.Name ?? source.Name, |
|||
PublishDate = source.PublishDate, |
|||
Price = source.Price, |
|||
Language = translation?.Language ?? AsyncHelper.RunSync(() => _settingProvider.GetOrNullAsync(LocalizationSettingNames.DefaultLanguage)), |
|||
CreationTime = source.CreationTime, |
|||
CreatorId = source.CreatorId, |
|||
LastModificationTime = source.LastModificationTime, |
|||
LastModifierId = source.LastModifierId |
|||
}; |
|||
} |
|||
|
|||
public BookDto Map(Book source, BookDto destination) |
|||
{ |
|||
return default; |
|||
} |
|||
} |
|||
``` |
|||
|
|||
To map the multi-lingual `Book` entity to `BookDto`, we implement custom mapping using the `IObjectMapper<TSource, TDestination>` interface. If no translation is found, default values are returned. |
|||
|
|||
So far we have created the entire infrastructure. We don't need to change anything in the UI, if there is a translation according to the language chosen by the user, the list view will change. However, I want to create a simple modal where we can add new translations to an existing book in order to see what we have done. |
|||
|
|||
#### Acme.BookStore.Web |
|||
|
|||
Create a new razor page named `AddTranslationModal` in the `Books` folder as below. |
|||
|
|||
**View** |
|||
|
|||
```html |
|||
@page |
|||
@using Microsoft.AspNetCore.Mvc.TagHelpers |
|||
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal |
|||
@model Acme.BookStore.Web.Pages.Books.AddTranslationModal |
|||
|
|||
@{ |
|||
Layout = null; |
|||
} |
|||
|
|||
<form asp-page="/Books/AddTranslationModal"> |
|||
<abp-modal> |
|||
<abp-modal-header>Translations</abp-modal-header> |
|||
<abp-modal-body> |
|||
<abp-input asp-for="Id"></abp-input> |
|||
<abp-select asp-for="@Model.TranslationViewModel.Language" asp-items="Model.Languages" class="form-select"> |
|||
<option selected value="">Pick a language</option> |
|||
</abp-select> |
|||
<abp-input asp-for="TranslationViewModel.Name"></abp-input> |
|||
</abp-modal-body> |
|||
<abp-modal-footer buttons="@(AbpModalButtons.Cancel | AbpModalButtons.Save)"></abp-modal-footer> |
|||
</abp-modal> |
|||
</form> |
|||
``` |
|||
|
|||
**Model** |
|||
|
|||
```csharp |
|||
public class AddTranslationModal : BookStorePageModel |
|||
{ |
|||
[HiddenInput] |
|||
[BindProperty(SupportsGet = true)] |
|||
public Guid Id { get; set; } |
|||
|
|||
public List<SelectListItem> Languages { get; set; } |
|||
|
|||
[BindProperty] |
|||
public BookTranslationViewModel TranslationViewModel { get; set; } |
|||
|
|||
private readonly IBookAppService _bookAppService; |
|||
private readonly ILanguageProvider _languageProvider; |
|||
|
|||
public AddTranslationModal( |
|||
IBookAppService bookAppService, |
|||
ILanguageProvider languageProvider) |
|||
{ |
|||
_bookAppService = bookAppService; |
|||
_languageProvider = languageProvider; |
|||
} |
|||
|
|||
public async Task OnGetAsync() |
|||
{ |
|||
Languages = await GetLanguagesSelectItem(); |
|||
|
|||
TranslationViewModel = new BookTranslationViewModel(); |
|||
} |
|||
|
|||
public async Task<IActionResult> OnPostAsync() |
|||
{ |
|||
await _bookAppService.AddTranslationsAsync(Id, ObjectMapper.Map<BookTranslationViewModel, AddBookTranslationDto>(TranslationViewModel)); |
|||
|
|||
return NoContent(); |
|||
} |
|||
|
|||
private async Task<List<SelectListItem>> GetLanguagesSelectItem() |
|||
{ |
|||
var result = await _languageProvider.GetLanguagesAsync(); |
|||
|
|||
return result.Select( |
|||
languageInfo => new SelectListItem |
|||
{ |
|||
Value = languageInfo.CultureName, |
|||
Text = languageInfo.DisplayName + " (" + languageInfo.CultureName + ")" |
|||
} |
|||
).ToList(); |
|||
} |
|||
|
|||
public class BookTranslationViewModel |
|||
{ |
|||
[Required] |
|||
[SelectItems(nameof(Languages))] |
|||
public string Language { get; set; } |
|||
|
|||
[Required] |
|||
public string Name { get; set; } |
|||
|
|||
} |
|||
} |
|||
``` |
|||
|
|||
Then, we can open the `BookStoreWebAutoMapperProfile` class and define the required mapping as follows: |
|||
|
|||
```csharp |
|||
CreateMap<AddTranslationModal.BookTranslationViewModel, AddBookTranslationDto>(); |
|||
``` |
|||
|
|||
Finally, change the content of `index.js` in the `Books` folder as follows: |
|||
|
|||
```javascript |
|||
$(function () { |
|||
var l = abp.localization.getResource('BookStore'); |
|||
var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); |
|||
var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal'); |
|||
var addTranslationModal = new abp.ModalManager(abp.appPath + 'Books/AddTranslationModal'); // added this line |
|||
|
|||
var dataTable = $('#BooksTable').DataTable( |
|||
abp.libs.datatables.normalizeConfiguration({ |
|||
serverSide: true, |
|||
paging: true, |
|||
order: [[1, "asc"]], |
|||
searching: false, |
|||
scrollX: true, |
|||
ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList), |
|||
columnDefs: [ |
|||
{ |
|||
title: l('Actions'), |
|||
rowAction: { |
|||
items: |
|||
[ |
|||
{ |
|||
text: l('Edit'), |
|||
visible: abp.auth.isGranted('BookStore.Books.Edit'), |
|||
action: function (data) { |
|||
editModal.open({ id: data.record.id }); |
|||
} |
|||
}, |
|||
{ |
|||
text: l('Add Translation'), // added this action |
|||
visible: abp.auth.isGranted('BookStore.Books.Edit'), |
|||
action: function (data) { |
|||
addTranslationModal.open({ id: data.record.id }); |
|||
} |
|||
}, |
|||
{ |
|||
text: l('Delete'), |
|||
visible: abp.auth.isGranted('BookStore.Books.Delete'), |
|||
confirmMessage: function (data) { |
|||
return l( |
|||
'BookDeletionConfirmationMessage', |
|||
data.record.name |
|||
); |
|||
}, |
|||
action: function (data) { |
|||
acme.bookStore.books.book |
|||
.delete(data.record.id) |
|||
.then(function() { |
|||
abp.notify.info( |
|||
l('SuccessfullyDeleted') |
|||
); |
|||
dataTable.ajax.reload(); |
|||
}); |
|||
} |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
{ |
|||
title: l('Name'), |
|||
data: "name" |
|||
}, |
|||
{ |
|||
title: l('Author'), |
|||
data: "authorName" |
|||
}, |
|||
{ |
|||
title: l('Type'), |
|||
data: "type", |
|||
render: function (data) { |
|||
return l('Enum:BookType:' + data); |
|||
} |
|||
}, |
|||
{ |
|||
title: l('PublishDate'), |
|||
data: "publishDate", |
|||
render: function (data) { |
|||
return luxon |
|||
.DateTime |
|||
.fromISO(data, { |
|||
locale: abp.localization.currentCulture.name |
|||
}).toLocaleString(); |
|||
} |
|||
}, |
|||
{ |
|||
title: l('Price'), |
|||
data: "price" |
|||
}, |
|||
{ |
|||
title: l('CreationTime'), |
|||
data: "creationTime", |
|||
render: function (data) { |
|||
return luxon |
|||
.DateTime |
|||
.fromISO(data, { |
|||
locale: abp.localization.currentCulture.name |
|||
}).toLocaleString(luxon.DateTime.DATETIME_SHORT); |
|||
} |
|||
} |
|||
] |
|||
}) |
|||
); |
|||
|
|||
createModal.onResult(function () { |
|||
dataTable.ajax.reload(); |
|||
}); |
|||
|
|||
editModal.onResult(function () { |
|||
dataTable.ajax.reload(); |
|||
}); |
|||
|
|||
$('#NewBookButton').click(function (e) { |
|||
e.preventDefault(); |
|||
createModal.open(); |
|||
}); |
|||
}); |
|||
``` |
|||
|
|||
## Conclusion |
|||
|
|||
With a multi-lingual application, you can expand your market share, but if not designed well, may your application will be unusable. So, I've tried to explain how to design a sustainable multi-lingual entity in this article. |
|||
|
|||
### Source Code |
|||
|
|||
You can find source code of the example solution used in this article [here](https://github.com/abpframework/abp-samples/tree/master/AcmeBookStoreMultiLingual). |
|||
|
After Width: | Height: | Size: 172 KiB |
|
After Width: | Height: | Size: 2.7 MiB |
@ -0,0 +1,154 @@ |
|||
# How to Add Custom Properties to the User Entity |
|||
|
|||
> **Note:** If your application is less than version 4.4.x, please follow [this article](../2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md). |
|||
|
|||
## Introduction |
|||
|
|||
In this step-by-step article, I will explain how you can customize the user entity class, which is available in every web application you create using the ABP framework, according to your needs. When you read this article, you will learn how to override the services of built-in modules, extend the entities, extend data transfer objects and customize the user interface in the applications you develop using the ABP framework. |
|||
|
|||
> **Note:** This article is not about customizing the `Login` page. If you have such a need, please follow [this article](../2020-05-09-Customize-the-Login-Page-for-MVC-Razor-Page-Applications/POST.md). |
|||
|
|||
You can see the screenshots below which we will reach at the end of the article. |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
## Preparing the Project |
|||
|
|||
### Startup template and the initial run |
|||
|
|||
Abp Framework offers startup templates to get into the work faster. We can create a new startup template using Abp CLI: |
|||
|
|||
`abp new CustomizeUserDemo` |
|||
|
|||
> In this article, I will go through the MVC application, but it will work also in the [Angular](https://docs.abp.io/en/abp/latest/Getting-Started?UI=NG&DB=EF&Tiered=No), [Blazor Server](https://docs.abp.io/en/abp/latest/Getting-Started?UI=BlazorServer&DB=EF&Tiered=No), and [Blazor WebAssembly](https://docs.abp.io/en/abp/latest/Getting-Started?UI=Blazor&DB=EF&Tiered=No) application. |
|||
|
|||
After the download is finished, we can run **CustomizeUserDemo.DbMigrator** project to create the database migrations and seed the initial data (admin user, role, etc). Then we can run `CustomizeUserDemo.Web` to see that our application is working. |
|||
|
|||
> Default admin username is **admin** and password is **1q2w3E\*** |
|||
|
|||
 |
|||
|
|||
In this article, we will go through a scenario together and find the solutions to our questions through this scenario. However, since the scenario is not a real-life scenario, it may be strange, please don't get too about this issue :) |
|||
|
|||
## Step-1 |
|||
|
|||
Create the Users folder in the **CustomizeUserDemo.Domain.Shared** project, create the class `UserConsts` inside the folder and update the class you created as below: |
|||
|
|||
```csharp |
|||
public static class UserConsts |
|||
{ |
|||
public const string TitlePropertyName = "Title"; |
|||
|
|||
public const string ReputationPropertyName = "Reputation"; |
|||
|
|||
public const int MaxTitleLength = 64; |
|||
|
|||
public const double MaxReputationValue = 1_000; |
|||
|
|||
public const double MinReputationValue = 1; |
|||
} |
|||
``` |
|||
|
|||
## Step-2 |
|||
|
|||
Update the `CustomizeUserDemoEfCoreEntityExtensionMappings` class in the **CustomizeUserDemo.EntityFramework** project in the EntityFrameworkCore folder as below: |
|||
|
|||
```csharp |
|||
public static class CustomizeUserDemoEfCoreEntityExtensionMappings |
|||
{ |
|||
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); |
|||
|
|||
public static void Configure() |
|||
{ |
|||
CustomizeUserDemoGlobalFeatureConfigurator.Configure(); |
|||
CustomizeUserDemoModuleExtensionConfigurator.Configure(); |
|||
|
|||
OneTimeRunner.Run(() => |
|||
{ |
|||
ObjectExtensionManager.Instance |
|||
.MapEfCoreProperty<IdentityUser, string>( |
|||
UserConsts.TitlePropertyName, |
|||
(_, propertyBuilder) => |
|||
{ |
|||
propertyBuilder.HasDefaultValue(""); |
|||
propertyBuilder.HasMaxLength(UserConsts.MaxTitleLength); |
|||
} |
|||
).MapEfCoreProperty<IdentityUser, int>( |
|||
UserConsts.ReputationPropertyName, |
|||
(_, propertyBuilder) => |
|||
{ |
|||
propertyBuilder.HasDefaultValue(UserConsts.MinReputationValue); |
|||
} |
|||
); |
|||
}); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
This class can be used to map these extra properties to table fields in the database. Please read [this](https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Extending-Entities) article to improve your understanding of what we are doing. |
|||
|
|||
So far, we have added our extra features to the `User` entity and matched these features with the `ef core`. |
|||
|
|||
Now we need to add migration to see what has changed in our database. This for, open the Package Manager Console (PMC) under the menu Tools > NuGet Package Manager. |
|||
|
|||
 |
|||
|
|||
Select the **CustomizeUserDemo.EntityFramework** as the **default project** and execute the following command: |
|||
|
|||
```bash |
|||
Add-Migration "Updated-User-Entity" |
|||
``` |
|||
|
|||
 |
|||
|
|||
This will create a new migration class inside the `Migrations` folder of the **CustomizeUserDemo.EntityFrameworkCore** project. |
|||
|
|||
> If you are using another IDE than the Visual Studio, you can use `dotnet-ef` tool as [documented here](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli#create-a-migration). |
|||
|
|||
Finally, run the **CustomizeUserDemo.DbMigrator** project to update the database. |
|||
|
|||
When we updated the database, you can see that the `Title` and `Reputation` columns are added to the `Users` table. |
|||
|
|||
 |
|||
|
|||
## Step-3 |
|||
Open the `CustomizeUserDemoModuleExtensionConfigurator` in the **CustomizeUserDemo.Domain.Shared** project, and change the contents of the `ConfigureExtraProperties` method as shown below: |
|||
```csharp |
|||
private static void ConfigureExtraProperties() |
|||
{ |
|||
ObjectExtensionManager.Instance.Modules().ConfigureIdentity(identity => |
|||
{ |
|||
identity.ConfigureUser(user => |
|||
{ |
|||
user.AddOrUpdateProperty<string>( |
|||
UserConsts.TitlePropertyName, |
|||
options => |
|||
{ |
|||
options.Attributes.Add(new RequiredAttribute()); |
|||
options.Attributes.Add( |
|||
new StringLengthAttribute(UserConsts.MaxTitleLength) |
|||
); |
|||
} |
|||
); |
|||
user.AddOrUpdateProperty<int>( |
|||
UserConsts.ReputationPropertyName, |
|||
options => |
|||
{ |
|||
options.DefaultValue = UserConsts.MinReputationValue; |
|||
options.Attributes.Add( |
|||
new RangeAttribute(UserConsts.MinReputationValue, UserConsts.MaxReputationValue) |
|||
); |
|||
} |
|||
); |
|||
}); |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
That's it. Now let's run the application and look at the Identity user page. You can also try to edit and recreate a record if you want, it will work even though we haven't done anything extra. Here is the magic code behind ABP framework. |
|||
|
|||
If there is a situation you want to add, you can click the contribute button or make a comment. Also, if you like the article, don't forget to share it :) |
|||
|
|||
Happy coding :) |
|||
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 193 KiB |
@ -0,0 +1,452 @@ |
|||
# Deploying ABP Project to Azure App Service |
|||
|
|||
In this document, you will learn how to create and deploy your first ABP web app to [Azure App Service](https://docs.microsoft.com/en-us/azure/app-service/overview). The App Service supports various versions of .NET apps, and provides a highly scalable, self-patching web hosting service. ABP web apps are cross-platform and can be hosted on Linux, Windows or MacOS. |
|||
|
|||
****Prerequisites**** |
|||
|
|||
- An Azure account with an active subscription. [Create an account for free](https://azure.microsoft.com/free/dotnet). |
|||
- A GitHub account [Create an account for free](http://github.com/). |
|||
|
|||
|
|||
|
|||
## Creating a new ABP application |
|||
|
|||
Create a repository on [GitHub.com](https://github.com/) (keep all settings as default). |
|||
|
|||
Open the command prompt and clone the repository into a folder on your computer |
|||
|
|||
```bash |
|||
git clone https://github.com/your-username/your-repository-name.git |
|||
``` |
|||
|
|||
Check your dotnet version. It should be at least 3.1.x |
|||
|
|||
```bash |
|||
dotnet --version |
|||
``` |
|||
|
|||
Install or update the [ABP CLI](https://docs.abp.io/en/abp/latest/cli) with the following command: |
|||
|
|||
```bash |
|||
dotnet tool install -g Volo.Abp.Cli || dotnet tool update -g Volo.Abp.Cli |
|||
``` |
|||
|
|||
Open the command prompt in the *GitHub repository folder* and create a new ABP Blazor solution with the command below: |
|||
|
|||
```bash |
|||
abp new YourAppName -u blazor |
|||
``` |
|||
|
|||
|
|||
|
|||
## Running the application |
|||
|
|||
Open the command prompt in the *[YourAppName].DbMigrator* project and enter the command below to apply the database migrations: |
|||
|
|||
```bash |
|||
dotnet run |
|||
``` |
|||
|
|||
Open the command prompt in the *[YourAppName].HttpApi.Host* project to run the API project: |
|||
|
|||
```bash |
|||
dotnet run |
|||
``` |
|||
|
|||
Navigate to the *applicationUrl* specified in *the launchSettings.json* file of the *[YourAppName].HttpApi.Host project*. You should get the *Swagger window* |
|||
|
|||
Open the command prompt in the *[YourAppName].Blazor* folder and enter the command below to run the Blazor project: |
|||
|
|||
```bash |
|||
dotnet run |
|||
``` |
|||
|
|||
Navigate to the *applicationUrl* specified in the *launchSettings.json* file of the *[YourAppName].Blazor* project and you should see the landing page. |
|||
|
|||
Stop both the *API* and the *Blazor* project by pressing **CTRL+C** |
|||
|
|||
|
|||
|
|||
## Committing to GitHub |
|||
|
|||
Before the GitHub commit, you have to delete the line "**/wwwroot/libs/*" at *.gitignore* file. |
|||
|
|||
 |
|||
|
|||
Open the command prompt in the root folder of your project and *add, commit and push* all your changes to your GitHub repository: |
|||
|
|||
```bash |
|||
git add . |
|||
git commit -m initialcommit |
|||
git push |
|||
``` |
|||
|
|||
|
|||
|
|||
## Configuring Azure database connection string |
|||
|
|||
Create a SQL database on Azure and change the connection string in all the *appsettings.json* files. |
|||
|
|||
* Login into [Azure Portal](https://portal.azure.com/) |
|||
|
|||
* Click **Create a resource** |
|||
|
|||
* Search for *SQL Database* |
|||
|
|||
* Click the **Create** button in the *SQL Database window* |
|||
|
|||
* Create a new resource group. Name it *rg[YourAppName]* |
|||
|
|||
* Enter *[YourAppName]Db* as database name |
|||
|
|||
* Create a new Server and name it *[yourappname]server* |
|||
|
|||
* Enter a serveradmin login and passwords. Click the **OK** button |
|||
|
|||
* Select your *Location* |
|||
|
|||
* Check *Allow Azure services to access server* |
|||
|
|||
* Click **Configure database**. Go to the *Basic* version and click the **Apply** button |
|||
|
|||
* Click the **Review + create** button. Click **Create** |
|||
|
|||
* Click **Go to resource** and click **SQL server** when the SQL Database is created |
|||
|
|||
* Click **Networking** under Security left side menu |
|||
|
|||
* Select **Selected networks** and click **Add your client IP$ address** at the Firewall rules |
|||
|
|||
* Select **Allow Azure and resources to access this seerver** and save |
|||
|
|||
* Go to your **SQL database**, click **Connection strings** and copy the connection string |
|||
|
|||
* Copy/paste the *appsettings.json* files of the *[YourAppName].HttpApi.Host* and the *[YourAppName].DbMigrator* project |
|||
|
|||
* Do not forget to replace {your_password} with the correct server password you entered in Azure SQL Database |
|||
|
|||
|
|||
|
|||
## Running DB Migrations |
|||
|
|||
Open the command prompt in the *[YourAppName].DbMigrator* project again and enter the command below to apply the database migrations: |
|||
|
|||
```bash |
|||
dotnet run |
|||
``` |
|||
|
|||
Open the command prompt in the *[YourAppName].HttpApi.Host* project and enter the command below to check your API is working: |
|||
|
|||
```bash |
|||
dotnet run |
|||
``` |
|||
|
|||
Stop the *[YourAppName].HttpApi.Host* by pressing CTRL+C. |
|||
|
|||
|
|||
|
|||
## Committing to GitHub |
|||
|
|||
Open the command prompt in the root folder of your project and add, commit and push all your changes to your GitHub repository |
|||
|
|||
```bash |
|||
git add . |
|||
git commit -m initialcommit |
|||
git push |
|||
``` |
|||
|
|||
|
|||
|
|||
## Setting up the Build pipeline in AzureDevops and publish the Build Artifacts |
|||
|
|||
* Sign in Azure DevOps |
|||
|
|||
* Click **New organization** and follow the steps to create a new organisation. Name it [YourAppName]org |
|||
|
|||
* Enter [YourAppName]Proj as project name in the ***Create a project to get started*** window |
|||
|
|||
* Select **Public visibility** and click the **Create project** button |
|||
|
|||
* Click the **Pipelines** button to continue |
|||
|
|||
* Click the **Create Pipeline** button |
|||
|
|||
Select GitHub in the Select your repository window |
|||
|
|||
 |
|||
|
|||
* Enter the Connection name. *[YourAppName]GitHubConnection* and click **Authorize using OAuth** |
|||
|
|||
* Select your **GitHub** [YourAppName]repo and click Continue |
|||
|
|||
* Search for **ASP.NET** in the ***Select a template*** window |
|||
|
|||
 |
|||
|
|||
* Select the ASP.NET Core template and click the **Apply** button |
|||
|
|||
* Add the below commands block as a first step in the pipeline |
|||
|
|||
``` |
|||
- task: UseDotNet@2 |
|||
inputs: |
|||
packageType: 'sdk' |
|||
version: '6.0.106' |
|||
``` |
|||
|
|||
 |
|||
|
|||
* Select **Settings** on the second task(Nugetcommand@2) in the pipeline |
|||
|
|||
* Select **Feeds in my Nuget.config** and type **Nuget.config** in the text box |
|||
|
|||
 |
|||
|
|||
* Add the below commands block to the end of the pipeline |
|||
|
|||
``` |
|||
- task: PublishBuildArtifacts@1 |
|||
displayName: 'Publish Artifact' |
|||
inputs: |
|||
PathtoPublish: '$(build.artifactstagingdirectory)' |
|||
ArtifactName: '$(Parameters.ArtifactName)' |
|||
condition: succeededOrFailed() |
|||
``` |
|||
|
|||
 |
|||
|
|||
``` |
|||
# ASP.NET |
|||
# Build and test ASP.NET projects. |
|||
# Add steps that publish symbols, save build artifacts, deploy, and more: |
|||
# https://docs.microsoft.com/azure/devops/pipelines/apps/aspnet/build-aspnet-4 |
|||
|
|||
trigger: |
|||
- main |
|||
|
|||
pool: |
|||
vmImage: 'windows-latest' |
|||
|
|||
variables: |
|||
solution: '**/*.sln' |
|||
buildPlatform: 'Any CPU' |
|||
buildConfiguration: 'Release' |
|||
|
|||
steps: |
|||
- task: UseDotNet@2 |
|||
inputs: |
|||
packageType: 'sdk' |
|||
version: '6.0.106' |
|||
|
|||
- task: NuGetToolInstaller@1 |
|||
|
|||
- task: NuGetCommand@2 |
|||
inputs: |
|||
command: 'restore' |
|||
restoreSolution: '$(solution)' |
|||
feedsToUse: 'config' |
|||
nugetConfigPath: 'NuGet.config' |
|||
|
|||
- task: VSBuild@1 |
|||
inputs: |
|||
solution: '$(solution)' |
|||
msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactStagingDirectory)"' |
|||
platform: '$(buildPlatform)' |
|||
configuration: '$(buildConfiguration)' |
|||
|
|||
- task: VSTest@2 |
|||
inputs: |
|||
platform: '$(buildPlatform)' |
|||
configuration: '$(buildConfiguration)' |
|||
|
|||
- task: PublishBuildArtifacts@1 |
|||
displayName: 'Publish Artifact' |
|||
inputs: |
|||
PathtoPublish: '$(build.artifactstagingdirectory)' |
|||
ArtifactName: '$(Parameters.ArtifactName)' |
|||
publishLocation: 'Container' |
|||
condition: succeededOrFailed() |
|||
``` |
|||
|
|||
* Click **Save & queue** in the top menu. Click **Save & queue** again and click **Save and run** to run the Build pipeline |
|||
|
|||
* When the Build pipeline has finished. Click **1 published; 1 consumed** |
|||
|
|||
|
|||
|
|||
## Creating a Web App in the Azure Portal to deploy [YourAppName].HttpApi.Host project |
|||
|
|||
* Search for Web App in the *Search the Marketplace* field |
|||
|
|||
* Click the **Create** button in the Web App window |
|||
|
|||
* Select rg[YourAppName] in the *Resource Group* dropdown |
|||
|
|||
* Enter [YourAppName]API in the *Name input* field |
|||
|
|||
* Select code, .NET Core 3.1 (LTS) and windows as *Operating System* |
|||
|
|||
* Enter [YourAppName]API in the *Name input* field |
|||
|
|||
* Select .NET Core 3.1 (LTS) in the *Runtime stack* dropdown |
|||
|
|||
* Select Windows as *Operating System* |
|||
|
|||
* Select the same *Region* as in the SQL server you created in Part 3 |
|||
|
|||
 |
|||
|
|||
* Click **Create new** in the Windows Plan. Name it [YourAppName]ApiWinPlan |
|||
|
|||
* Click **Change size** in Sku and size. Go to the Dev/Test Free F1 version and click the **Apply** button |
|||
|
|||
 |
|||
|
|||
* Click the **Review + create** button. Click the **Create** button |
|||
|
|||
* Click **Go to resource** when the Web App has been created |
|||
|
|||
|
|||
|
|||
## Creating a release pipeline in the AzureDevops and deploy [YourAppName].HttpApi.Host project |
|||
|
|||
* Sign in into [Azure DevOps](https://azure.microsoft.com/en-us/services/devops/) |
|||
|
|||
* Click [YourAppName]Proj and click **Releases** in the *Pipelines* menu |
|||
|
|||
* Click the **New pipeline** button in the *No release pipelines found* window |
|||
|
|||
* Select *Azure App Service deployment* and click the **Apply** button |
|||
|
|||
 |
|||
|
|||
* Enter *[YourAppName]staging* in the *Stage name* field in the *Stage* window. And close the window |
|||
|
|||
* Click **+ Add an artifact** in the *Pipeline* tab |
|||
|
|||
* Select the **Build** icon as *Source type* in the *Add an artifact* window |
|||
|
|||
* Select Build pipeline in the *Source (build pipeline)* dropdown and click the **Add** button |
|||
|
|||
 |
|||
|
|||
* Click the **Continuous deployment trigger (thunderbolt icon)** |
|||
|
|||
* Set the toggle to **Enabled** in the the *Continuous deployment trigger* window |
|||
|
|||
* Click **+ Add** in *No filters added*. Select **Include** in the *Type* dropdown. Select your branch in the *Build branch* dropdown and close the window |
|||
|
|||
 |
|||
|
|||
* Click **the little red circle with the exclamation mark** in the *Tasks* tab menu |
|||
|
|||
* Select your subscription in the *Azure subscription* dropdown. |
|||
|
|||
 |
|||
|
|||
* Click **Authorize** and enter your credentials in the next screens |
|||
|
|||
* After Authorization, select the **[YourAppName]API** in the *App service name* dropdown |
|||
|
|||
* Click the **Deploy Azure App Service** task |
|||
|
|||
* Select **[YourAppName].HttpApi.Host.zip** in the *Package or folder* input field |
|||
|
|||
 |
|||
|
|||
* Click the **Save** icon in the top menu and click **OK** |
|||
|
|||
* Click **Create release** in the top menu. Click **Create** to create a release |
|||
|
|||
* Click the *Pipeline* tab and wait until the Deployment succeeds |
|||
|
|||
 |
|||
|
|||
* Open a browser and navigate to the URL of your Web App |
|||
|
|||
``` |
|||
https://[YourAppName]api.azurewebsites.net |
|||
``` |
|||
|
|||
 |
|||
|
|||
|
|||
|
|||
## Creating a Web App in Azure Portal to deploy [YourAppName].Blazor project |
|||
|
|||
* Login into [Azure Portal](https://portal.azure.com/) |
|||
|
|||
* Click **Create a resource** |
|||
|
|||
* Search for *Web App* in the *Search the Marketplace* field |
|||
|
|||
* Click the **Create** button in the *Web App* window |
|||
|
|||
* Select *rg[YourAppName]* in the *Resource Group* dropdown |
|||
|
|||
* Enter *[YourAppName]Blazor* in the *Name* input field |
|||
|
|||
* Select *.NET Core 3.1 (LTS)* in the *Runtime stack* dropdown |
|||
|
|||
* Select *Windows* as *Operating System* |
|||
|
|||
* Select the same region as the SQL server you created in Part 3 |
|||
|
|||
* Select the [YourAppName]ApiWinPlan in the *Windows Plan* dropdown |
|||
|
|||
 |
|||
|
|||
* Click the **Review + create** button. Click **Create** button |
|||
|
|||
* Click **Go to resource** when the Web App has been created |
|||
|
|||
* Copy the URL of the Blazor Web App for later use |
|||
|
|||
``` |
|||
https://[YourAppName]blazor.azurewebsites.net |
|||
``` |
|||
|
|||
|
|||
## Changing the Web App configuration for the Azure App Service |
|||
|
|||
Copy the URL of the Api Host and Blazor Web App. Change appsettings.json files in the Web App as follows images. |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
|
|||
|
|||
## Adding an extra Stage in the Release pipeline in the AzureDevops to deploy [YourAppName].Blazor project |
|||
|
|||
* Go to the *Release* pipeline in [Azure DevOps](https://azure.microsoft.com/en-us/services/devops/) and click **Edit** |
|||
|
|||
* Click the **+ Add** link and add a **New Stage** |
|||
|
|||
 |
|||
|
|||
* Select *Azure App Service deployment* and click the **Apply** button |
|||
|
|||
* Enter *BlazorDeployment* in the *Stage name* input field and close the *Stage* window |
|||
|
|||
* Click the **little red circle with the exclamation mark** in the BlazorDeployment stage |
|||
|
|||
* Select your subscription in the *Azure subscription* dropdown |
|||
|
|||
* Select your Blazor Web App in the *App service name* dropdown |
|||
|
|||
* Click the **Deploy Azure App Service task** |
|||
|
|||
* Select *[YourAppName].Blazor.zip* in the *Package or folder* input field |
|||
|
|||
 |
|||
|
|||
* Click **Save** in the top menu and click the **OK** button after |
|||
|
|||
* Click **Create release** in the top menu and click the **Create** button |
|||
|
|||
 |
|||
|
|||
 |
|||
@ -0,0 +1,29 @@ |
|||
# ABP Version 6.0 Migration Guide |
|||
|
|||
This document is a guide for upgrading ABP v5.3 solutions to ABP v6.0. There is a change in this version that may affect your applications, please read it carefully and apply the necessary changes to your application. |
|||
|
|||
## Added IsActive property |
|||
|
|||
`IsActive` property is added to `IUserData`. This property is set to **true** by default. **Cmskit** and **Blog** modules are affected by this change. You need to add new migration to your existing application if you are using any of these modules. Please see [#11417](https://github.com/abpframework/abp/pull/11417) for more info. |
|||
|
|||
## Default behavior change in MultiTenancyMiddlewareErrorPageBuilder |
|||
|
|||
If you have customized the `MultiTenancyMiddlewareErrorPageBuilder` of `AbpMultiTenancyOptions`, the pipeline now returns **true** to stop the pipeline as the default behavior. See [AbpMultiTenancyOptions: Handle inactive and non-existent tenants](https://github.com/abpframework/abp/blob/dev/docs/en/Multi-Tenancy.md#abpmultitenancyoptions-handle-inactive-and-non-existent-tenants) for more info. |
|||
|
|||
## Migrating to LeptonX Lite |
|||
|
|||
LeptonX Lite is now being introduced and you can follow the guides below to migrate your existing applications: |
|||
|
|||
- [Migrating to LeptonX MVC UI](../themes/LeptonXLite/AspNetCore.md) |
|||
- [Migrating to LeptonX Angular UI](../themes/LeptonXLite/angular.md) |
|||
- [Migrating to LeptonX Blazor UI](../themes/LeptonXLite/blazor.md) |
|||
|
|||
## Migrating to OpenIddict |
|||
|
|||
After the [announcement of plan to replace the IdentityServer](https://github.com/abpframework/abp/issues/11989), we have successfully implemented [Openiddict](https://github.com/openiddict/openiddict-core) as a replacement for IdentityServer4 as an OpenID-Provider. |
|||
|
|||
You can follow the [IdentityServer to OpenIddict Step by Step Guide](OpenIddict-Step-by-Step.md) for migrating your existing application in detail with a sample projects. |
|||
|
|||
## See Also |
|||
|
|||
* [Official blog post for the 6.0 release](https://blog.abp.io/abp/ABP.IO-Platform-6.0-RC-Has-Been-Published) |
|||
@ -0,0 +1,78 @@ |
|||
# Migration Identity Server to OpenIddict Guide |
|||
|
|||
This document explains how to migrate to [OpenIddict](https://github.com/openiddict/openiddict-core) from Identity Server. From now on the ABP startup templates uses `OpenIddict` as the auth server by default since version v6.0.0. |
|||
|
|||
## History |
|||
We are not removing Identity Server packages and we will continue to release new versions of Identity Server related NuGet/NPM packages. That means you won't have an issue while upgrading to v6.0 when the stable version releases. We will continue to fix bugs in our packages for a while. ABP 7.0 will be based on .NET 7. If Identity Server continues to work with .NET 7, we will also continue to ship NuGet packages for our IDS integration. |
|||
|
|||
On the other hand, Identity Server ends support for the open-source Identity Server in the end of 2022. The Identity Server team has decided to move to Duende IDS and ABP will not be migrated to the commercial Duende IDS. You can see the Duende Identity Server announcement from [this link](https://blog.duendesoftware.com/posts/20220111_fair_trade). |
|||
|
|||
## OpenIddict Migration Steps |
|||
|
|||
* Update all `Volo's` packages to `6.x`. |
|||
* Replace all `Volo's` `IdentityServer.*` packages with corresponding `OpenIddict.*` packages. eg `Volo.Abp.IdentityServer.Domain` to `Volo.Abp.OpenIddict.Domain`, `Volo.Abp.Account.Web.IdentityServer` to `Volo.Abp.Account.Web.OpenIddict`. |
|||
* Replace all `IdentityServer` modules with corresponding `OpenIddict` modules. eg `AbpIdentityServerDomainModule` to `AbpOpenIddictDomainModule`, `AbpAccountWebIdentityServerModule` to `AbpAccountWebOpenIddictModule`. |
|||
* Rename the `ConfigureIdentityServer` to `ConfigureOpenIddict` in your `ProjectNameDbContext` class. |
|||
* Remove the `UseIdentityServer` and add `UseAbpOpenIddictValidation` after `UseAuthentication`. |
|||
* Add follow code to your startup module. |
|||
|
|||
```cs |
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
PreConfigure<OpenIddictBuilder>(builder => |
|||
{ |
|||
builder.AddValidation(options => |
|||
{ |
|||
options.AddAudiences("ProjectName"); // Change ProjectName to your project name. |
|||
options.UseLocalServer(); |
|||
options.UseAspNetCore(); |
|||
}); |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
* If your project is not separate AuthServer please also add `ForwardIdentityAuthenticationForBearer` |
|||
|
|||
```cs |
|||
private void ConfigureAuthentication(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); |
|||
} |
|||
``` |
|||
|
|||
* Remove the `IdentityServerDataSeedContributor` from the `Domain` project. |
|||
* Create a new version of the project, with the same name as your existing project. |
|||
* Copy the `ProjectName.Domain\OpenIddict\OpenIddictDataSeedContributor.cs` of new project into your project and update `appsettings.json` base on `ProjectName.DbMigrator\appsettings.json`, Be careful to change the port number. |
|||
* Copy the `Index.cshtml.cs` and `Index.cs` of new project to your project if you're using `IClientRepository` in `IndexModel`. |
|||
* Update the scope name from `role` to `roles` in `AddAbpOpenIdConnect` method. |
|||
* Remove `options.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]);` from `HttpApi.Host` project. |
|||
* AuthServer no longer requires `JWT bearer authentication`. Please remove it. eg `AddJwtBearer` and `UseJwtTokenMiddleware`. |
|||
* Try compiling the project in the IDE and following the errors to remove and reference the code and namespaces. |
|||
* Add migrations and update the database if you are using EF Core as the database provider. |
|||
|
|||
## Module packages |
|||
### Open source side |
|||
* Volo.Abp.OpenIddict.Domain (`AbpOpenIddictDomainModule`) |
|||
* Volo.Abp.OpenIddict.Domain.Shared (`AbpOpenIddictDomainSharedModule`) |
|||
* Volo.Abp.OpenIddict.EntityFrameworkCore (`AbpOpenIddictEntityFrameworkCoreModule`) |
|||
* Volo.Abp.OpenIddict.AspNetCore (`AbpOpenIddictAspNetCoreModule`) |
|||
* Volo.Abp.OpenIddict.MongoDB (`AbpOpenIddictMongoDbModule`) |
|||
* Volo.Abp.Account.Web.OpenIddict (`AbpAccountWebOpenIddictModule`) |
|||
* Volo.Abp.PermissionManagement.Domain.OpenIddict (`AbpPermissionManagementDomainOpenIddictModule`) |
|||
|
|||
### Commercial side |
|||
* Volo.Abp.OpenIddict.Pro.Application.Contracts (`AbpOpenIddictProApplicationContractsModule`) |
|||
* Volo.Abp.OpenIddict.Pro.Application (`AbpOpenIddictProApplicationModule`) |
|||
* Volo.Abp.OpenIddict.Pro.HttpApi.Client (`AbpOpenIddictProHttpApiClientModule`) |
|||
* Volo.Abp.OpenIddict.Pro.HttpApi (`AbpOpenIddictProHttpApiModule`) |
|||
* Volo.Abp.OpenIddict.Pro.Blazor(`AbpOpenIddictProBlazorModule`) |
|||
* Volo.Abp.OpenIddict.Pro.Blazor.Server (`AbpOpenIddictProBlazorServerModule`) |
|||
* Volo.Abp.OpenIddict.Pro.Blazor.WebAssembly (`AbpOpenIddictProBlazorWebAssemblyModule`) |
|||
* Volo.Abp.OpenIddict.Pro.Web (`AbpOpenIddictProWebModule`) |
|||
|
|||
## Source code of samples and module |
|||
|
|||
* [Open source tiered & separate auth server application migrate Identity Server to OpenIddct](https://github.com/abpframework/abp-samples/tree/master/Ids2OpenId) |
|||
* [Commercial tiered & separate auth server application migrate Identity Server to OpenIddct](https://abp.io/Account/Login?returnUrl=/api/download/samples/Ids2OpenId) |
|||
* [OpenIddict module document](https://docs.abp.io/en/abp/6.0/Modules/OpenIddict) |
|||
* [OpenIddict module source code](https://github.com/abpframework/abp/tree/rel-6.0/modules/openiddict) |
|||
@ -0,0 +1,170 @@ |
|||
# OpenIddict Angular UI Migration Guide |
|||
|
|||
## Angular Project |
|||
|
|||
- In `environment.ts` and `environment.prod.ts` **add a trailing slash at the end of the issuer**: |
|||
|
|||
```typescript |
|||
oAuthConfig: { |
|||
issuer: 'https://localhost:44377/', |
|||
... |
|||
}, |
|||
``` |
|||
|
|||
## Http.Api.Host (Non-Separated IdentityServer) |
|||
|
|||
- In **MyApplication.HttpApi.Host.csproj** replace **project references**: |
|||
|
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.AspNetCore.Authentication.JwtBearer" Version="6.0.0-rc.1" /> |
|||
<PackageReference Include="Volo.Abp.Account.Web.IdentityServer" Version="6.0.0-rc.1" /> |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.Account.Web.OpenIddict" Version="6.0.0-rc.1" /> |
|||
``` |
|||
|
|||
- In the **MyApplicationHttpApiHostModule.cs** replace usings and **module dependencies**: |
|||
|
|||
```csharp |
|||
using Volo.Abp.AspNetCore.Authentication.JwtBearer; |
|||
... |
|||
typeof(AbpAspNetCoreAuthenticationJwtBearerModule), |
|||
typeof(AbpAccountWebIdentityServerModule), |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
using OpenIddict.Validation.AspNetCore; |
|||
... |
|||
typeof(AbpAccountWebOpenIddictModule), |
|||
``` |
|||
|
|||
- In the **MyApplicationHttpApiHostModule.cs** add `PreConfigureServices` like below with your application name as the audience: |
|||
|
|||
```csharp |
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
PreConfigure<OpenIddictBuilder>(builder => |
|||
{ |
|||
builder.AddValidation(options => |
|||
{ |
|||
options.AddAudiences("MyApplication"); // Replace with your application name |
|||
options.UseLocalServer(); |
|||
options.UseAspNetCore(); |
|||
}); |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
- In the **MyApplicationHttpApiHostModule.cs** `ConfigureServices` method, **replace the method call**: |
|||
|
|||
From `ConfigureAuthentication(context, configuration);` to `ConfigureAuthentication(context);` and update the method as: |
|||
|
|||
```csharp |
|||
private void ConfigureAuthentication(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); |
|||
} |
|||
``` |
|||
|
|||
- In the **MyApplicationHttpApiHostModule.cs** `OnApplicationInitialization` method, **replace the midware**: |
|||
|
|||
```csharp |
|||
app.UseJwtTokenMiddleware(); |
|||
app.UseIdentityServer(); |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
app.UseAbpOpenIddictValidation(); |
|||
``` |
|||
|
|||
- In the **MyApplicationHttpApiHostModule.cs** `OnApplicationInitialization` method, delete `c.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]);` in `app.UseAbpSwaggerUI` options configurations which is no longer needed. |
|||
|
|||
- In `appsettings.json` delete **SwaggerClientSecret** from the *AuthServer* section like below: |
|||
|
|||
```json |
|||
"AuthServer": { |
|||
"Authority": "https://localhost:44345", |
|||
"RequireHttpsMetadata": "false", |
|||
"SwaggerClientId": "MyApplication_Swagger" |
|||
}, |
|||
``` |
|||
|
|||
## Http.Api.Host (Separated IdentityServer) |
|||
|
|||
- In the **MyApplicationHttpApiHostModule.cs** `OnApplicationInitialization` method, delete `c.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]);` in `app.UseAbpSwaggerUI` options configurations which is no longer needed. |
|||
|
|||
- In `appsettings.json` delete **SwaggerClientSecret** from the *AuthServer* section like below: |
|||
|
|||
```json |
|||
"AuthServer": { |
|||
"Authority": "https://localhost:44345", |
|||
"RequireHttpsMetadata": "false", |
|||
"SwaggerClientId": "MyApplication_Swagger" |
|||
}, |
|||
``` |
|||
|
|||
## IdentityServer |
|||
|
|||
This project is renamed to **AuthServer** after v6.0.0-rc1. You can also refactor and rename your project to *AuthServer* for easier updates in the future. |
|||
|
|||
- In **MyApplication.IdentityServer.csproj** replace **project references**: |
|||
|
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.Account.Web.IdentityServer" Version="6.0.0-rc.1" /> |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.Account.Web.OpenIddict" Version="6.0.0-rc.1" /> |
|||
``` |
|||
|
|||
- In the **MyApplicationIdentityServerModule.cs** replace usings and **module dependencies**: |
|||
|
|||
```csharp |
|||
typeof(AbpAccountWebIdentityServerModule), |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
typeof(AbpAccountWebOpenIddictModule), |
|||
``` |
|||
|
|||
- In the **MyApplicationIdentityServerModule.cs** add `PreConfigureServices` like below with your application name as the audience: |
|||
|
|||
```csharp |
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
PreConfigure<OpenIddictBuilder>(builder => |
|||
{ |
|||
builder.AddValidation(options => |
|||
{ |
|||
options.AddAudiences("MyApplication"); // Replace with your application name |
|||
options.UseLocalServer(); |
|||
options.UseAspNetCore(); |
|||
}); |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
- In the **MyApplicationIdentityServerModule.cs** `OnApplicationInitialization` method, **remove the midware**: |
|||
|
|||
```csharp |
|||
app.UseIdentityServer(); |
|||
``` |
|||
|
|||
- To use the new AuthServer page, replace **Index.cshtml.cs** with [AuthServer Index.cshtml.cs](https://github.com/abpframework/abp-samples/blob/master/Ids2OpenId/src/Ids2OpenId.IdentityServer/Pages/Index.cshtml) and **Index.cshtml** file with [AuthServer Index.cshtml](https://github.com/abpframework/abp-samples/blob/master/Ids2OpenId/src/Ids2OpenId.IdentityServer/Pages/Index.cshtml.cs) and rename **Ids2OpenId** with your application namespace. |
|||
|
|||
> Note: It can be found under the *Pages* folder. |
|||
|
|||
## See Also |
|||
|
|||
* [OpenIddict Step-by-Step Guide](OpenIddict-Step-by-Step.md) |
|||
@ -0,0 +1,175 @@ |
|||
# OpenIddict Blazor-Server UI Migration Guide |
|||
|
|||
## Blazor Project (Non-Tiered Solution) |
|||
|
|||
- In the **MyApplication.Blazor.csproj** replace **project references**: |
|||
|
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.AspNetCore.Authentication.JwtBearer" Version="6.0.0-rc.1" /> |
|||
<PackageReference Include="Volo.Abp.Account.Web.IdentityServer" Version="6.0.0-rc.1" /> |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.Account.Web.OpenIddict" Version="6.0.0-rc.1" /> |
|||
``` |
|||
|
|||
- In the **MyApplicationBlazorModule.cs** replace usings and **module dependencies**: |
|||
|
|||
```csharp |
|||
using System; |
|||
using System.Net.Http; |
|||
using Volo.Abp.AspNetCore.Authentication.JwtBearer; |
|||
... |
|||
typeof(AbpAspNetCoreAuthenticationJwtBearerModule), |
|||
typeof(AbpAccountWebIdentityServerModule), |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
using OpenIddict.Validation.AspNetCore; |
|||
... |
|||
typeof(AbpAccountWebOpenIddictModule), |
|||
``` |
|||
|
|||
- In the **MyApplicationBlazorModule.cs** add `PreConfigureServices` like below with your application name as the audience: |
|||
|
|||
```csharp |
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
PreConfigure<OpenIddictBuilder>(builder => |
|||
{ |
|||
builder.AddValidation(options => |
|||
{ |
|||
options.AddAudiences("MyApplication"); // Replace with your application name |
|||
options.UseLocalServer(); |
|||
options.UseAspNetCore(); |
|||
}); |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
- In the **MyApplicationBlazorModule.cs** `ConfigureServices` method, **replace the method call**: |
|||
|
|||
From `ConfigureAuthentication(context, configuration);` to `ConfigureAuthentication(context);` and update the method as: |
|||
|
|||
```csharp |
|||
private void ConfigureAuthentication(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); |
|||
} |
|||
``` |
|||
|
|||
- In the **MyApplicationBlazorModule.cs** `OnApplicationInitialization` method, **replace the midware**: |
|||
|
|||
```csharp |
|||
app.UseJwtTokenMiddleware(); |
|||
app.UseIdentityServer(); |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
app.UseAbpOpenIddictValidation(); |
|||
``` |
|||
|
|||
## Blazor Project (Tiered Solution) |
|||
|
|||
- In the **MyApplicationWebModule.cs** update the `AddAbpOpenIdConnect` configurations: |
|||
|
|||
```csharp |
|||
.AddAbpOpenIdConnect("oidc", options => |
|||
{ |
|||
options.Authority = configuration["AuthServer:Authority"]; |
|||
options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); |
|||
options.ResponseType = OpenIdConnectResponseType.CodeIdToken; |
|||
|
|||
options.ClientId = configuration["AuthServer:ClientId"]; |
|||
options.ClientSecret = configuration["AuthServer:ClientSecret"]; |
|||
|
|||
options.SaveTokens = true; |
|||
options.GetClaimsFromUserInfoEndpoint = true; |
|||
|
|||
options.Scope.Add("roles"); // Replace "role" with "roles" |
|||
options.Scope.Add("email"); |
|||
options.Scope.Add("phone"); |
|||
options.Scope.Add("MyApplication"); |
|||
}); |
|||
``` |
|||
|
|||
Replace **role** scope with **roles**. |
|||
|
|||
## IdentityServer |
|||
|
|||
This project is renamed to **AuthServer** after v6.0.0-rc1. You can also refactor and rename your project to *AuthServer* for easier updates in the future. |
|||
|
|||
- In **MyApplication.IdentityServer.csproj** replace **project references**: |
|||
|
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.Account.Web.IdentityServer" Version="6.0.0-rc.1" /> |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.Account.Web.OpenIddict" Version="6.0.0-rc.1" /> |
|||
``` |
|||
|
|||
- In **MyApplicationIdentityServerModule.cs** replace usings and **module dependencies**: |
|||
|
|||
```csharp |
|||
typeof(AbpAccountWebIdentityServerModule), |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
typeof(AbpAccountWebOpenIddictModule), |
|||
``` |
|||
|
|||
- In the **MyApplicationIdentityServerModule.cs** add `PreConfigureServices` like below with your application name as the audience: |
|||
|
|||
```csharp |
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
PreConfigure<OpenIddictBuilder>(builder => |
|||
{ |
|||
builder.AddValidation(options => |
|||
{ |
|||
options.AddAudiences("MyApplication"); // Replace with your application name |
|||
options.UseLocalServer(); |
|||
options.UseAspNetCore(); |
|||
}); |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
- In **MyApplicationIdentityServerModule.cs** `OnApplicationInitialization` method **remove IdentityServer midware**: |
|||
|
|||
```csharp |
|||
app.UseIdentityServer(); |
|||
``` |
|||
|
|||
## Http.Api.Host |
|||
|
|||
- In the **MyApplicationHttpApiHostModule.cs** `OnApplicationInitialization` method, delete `c.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]);` in `app.UseAbpSwaggerUI` options configurations which is no longer needed. |
|||
|
|||
- In `appsettings.json` delete **SwaggerClientSecret** from the *AuthServer* section like below: |
|||
|
|||
```json |
|||
"AuthServer": { |
|||
"Authority": "https://localhost:44345", |
|||
"RequireHttpsMetadata": "false", |
|||
"SwaggerClientId": "MyApplication_Swagger" |
|||
}, |
|||
``` |
|||
|
|||
- To use the new AuthServer page, replace **Index.cshtml.cs** with [AuthServer Index.cshtml.cs](https://github.com/abpframework/abp-samples/blob/master/Ids2OpenId/src/Ids2OpenId.IdentityServer/Pages/Index.cshtml) and **Index.cshtml** file with [AuthServer Index.cshtml](https://github.com/abpframework/abp-samples/blob/master/Ids2OpenId/src/Ids2OpenId.IdentityServer/Pages/Index.cshtml.cs) and rename **Ids2OpenId** with your application namespace. |
|||
|
|||
> Note: It can be found under the *Pages* folder. |
|||
|
|||
## See Also |
|||
|
|||
* [OpenIddict Step-by-Step Guide](OpenIddict-Step-by-Step.md) |
|||
@ -0,0 +1,189 @@ |
|||
# OpenIddict Blazor Wasm UI Migration Guide |
|||
|
|||
## Blazor Project |
|||
|
|||
- In the **MyApplicationBlazorModule.cs** update the `ConfigureAuthentication` method: |
|||
|
|||
```csharp |
|||
builder.Services.AddOidcAuthentication(options => |
|||
{ |
|||
... |
|||
options.UserOptions.RoleClaim = JwtClaimTypes.Role; |
|||
|
|||
options.ProviderOptions.DefaultScopes.Add("role"); |
|||
... |
|||
}); |
|||
``` |
|||
|
|||
Update **UserOptions** and **role scope** as below |
|||
|
|||
```csharp |
|||
builder.Services.AddOidcAuthentication(options => |
|||
{ |
|||
... |
|||
options.UserOptions.NameClaim = OpenIddictConstants.Claims.Name; |
|||
options.UserOptions.RoleClaim = OpenIddictConstants.Claims.Role; |
|||
|
|||
options.ProviderOptions.DefaultScopes.Add("roles"); |
|||
... |
|||
}); |
|||
``` |
|||
|
|||
## Http.Api.Host (Non-Separated IdentityServer) |
|||
|
|||
- In the **MyApplication.HttpApi.Host.csproj** replace **project references**: |
|||
|
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.AspNetCore.Authentication.JwtBearer" Version="6.0.0-rc.1" /> |
|||
<PackageReference Include="Volo.Abp.Account.Web.IdentityServer" Version="6.0.0-rc.1" /> |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.Account.Web.OpenIddict" Version="6.0.0-rc.1" /> |
|||
``` |
|||
|
|||
- In the **MyApplicationHttpApiHostModule.cs** replace usings and **module dependencies**: |
|||
|
|||
```csharp |
|||
using System.Net.Http; |
|||
using Volo.Abp.AspNetCore.Authentication.JwtBearer; |
|||
... |
|||
typeof(AbpAspNetCoreAuthenticationJwtBearerModule), |
|||
typeof(AbpAccountWebIdentityServerModule), |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
using OpenIddict.Validation.AspNetCore; |
|||
... |
|||
typeof(AbpAccountWebOpenIddictModule), |
|||
``` |
|||
|
|||
- In the **MyApplicationBlazorModule.cs** add `PreConfigureServices` like below with your application name as the audience: |
|||
|
|||
```csharp |
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
PreConfigure<OpenIddictBuilder>(builder => |
|||
{ |
|||
builder.AddValidation(options => |
|||
{ |
|||
options.AddAudiences("MyApplication"); // Replace with your application name |
|||
options.UseLocalServer(); |
|||
options.UseAspNetCore(); |
|||
}); |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
- In the **MyApplicationBlazorModule.cs** `ConfigureServices` method, **replace the method call**: |
|||
|
|||
From `ConfigureAuthentication(context, configuration);` to `ConfigureAuthentication(context);` and update the method as: |
|||
|
|||
```csharp |
|||
private void ConfigureAuthentication(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); |
|||
} |
|||
``` |
|||
|
|||
- In the **MyApplicationBlazorModule.cs** `OnApplicationInitialization` method, **replace the midware**: |
|||
|
|||
```csharp |
|||
app.UseJwtTokenMiddleware(); |
|||
app.UseIdentityServer(); |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
app.UseAbpOpenIddictValidation(); |
|||
``` |
|||
|
|||
- Delete `c.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]);` in `app.UseAbpSwaggerUI` options configurations which is no longer needed. |
|||
|
|||
- In `appsettings.json` delete **SwaggerClientSecret** from the *AuthServer* section like below: |
|||
|
|||
```json |
|||
"AuthServer": { |
|||
"Authority": "https://localhost:44345", |
|||
"RequireHttpsMetadata": "false", |
|||
"SwaggerClientId": "MyApplication_Swagger" |
|||
}, |
|||
``` |
|||
|
|||
## Http.Api.Host (Separated IdentityServer) |
|||
|
|||
- In the **MyApplicationHttpApiHostModule.cs** `OnApplicationInitialization` method, delete `c.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]);` in `app.UseAbpSwaggerUI` options configurations which is no longer needed. |
|||
|
|||
- In `appsettings.json` delete **SwaggerClientSecret** from the *AuthServer* section like below: |
|||
|
|||
```json |
|||
"AuthServer": { |
|||
"Authority": "https://localhost:44345", |
|||
"RequireHttpsMetadata": "false", |
|||
"SwaggerClientId": "MyApplication_Swagger" |
|||
}, |
|||
``` |
|||
|
|||
## IdentityServer |
|||
|
|||
This project is renamed to **AuthServer** after v6.0.0-rc1. You can also refactor and rename your project to *AuthServer* for easier updates in the future. |
|||
|
|||
- In **MyApplication.IdentityServer.csproj** replace **project references**: |
|||
|
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.Account.Web.IdentityServer" Version="6.0.0-rc.1" /> |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.Account.Web.OpenIddict" Version="6.0.0-rc.1" /> |
|||
``` |
|||
|
|||
- In the **MyApplicationIdentityServerModule.cs** replace usings and **module dependencies**: |
|||
|
|||
```csharp |
|||
typeof(AbpAccountWebIdentityServerModule), |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
typeof(AbpAccountWebOpenIddictModule), |
|||
``` |
|||
|
|||
- In the **MyApplicationIdentityServerModule.cs** add `PreConfigureServices` like below with your application name as the audience: |
|||
|
|||
```csharp |
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
PreConfigure<OpenIddictBuilder>(builder => |
|||
{ |
|||
builder.AddValidation(options => |
|||
{ |
|||
options.AddAudiences("MyApplication"); // Replace with your application name |
|||
options.UseLocalServer(); |
|||
options.UseAspNetCore(); |
|||
}); |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
- In the **MyApplicationIdentityServerModule.cs** `OnApplicationInitialization` method, **remove the midware**: |
|||
|
|||
```csharp |
|||
app.UseIdentityServer(); |
|||
``` |
|||
|
|||
- To use the new AuthServer page, replace **Index.cshtml.cs** with [AuthServer Index.cshtml.cs](https://github.com/abpframework/abp-samples/blob/master/Ids2OpenId/src/Ids2OpenId.IdentityServer/Pages/Index.cshtml) and **Index.cshtml** file with [AuthServer Index.cshtml](https://github.com/abpframework/abp-samples/blob/master/Ids2OpenId/src/Ids2OpenId.IdentityServer/Pages/Index.cshtml.cs) and rename **Ids2OpenId** with your application namespace. |
|||
|
|||
> Note: It can be found under the *Pages* folder. |
|||
|
|||
## See Also |
|||
|
|||
* [OpenIddict Step-by-Step Guide](OpenIddict-Step-by-Step.md) |
|||
@ -0,0 +1,166 @@ |
|||
# OpenIddict MVC/Razor UI Migration Guide |
|||
|
|||
## Web Project (Non-Tiered Solution) |
|||
|
|||
- In **MyApplication.Web.csproj** replace **project references**: |
|||
|
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.AspNetCore.Authentication.JwtBearer" Version="6.0.0-rc.1" /> |
|||
<PackageReference Include="Volo.Abp.Account.Web.IdentityServer" Version="6.0.0-rc.1" /> |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.Account.Web" Version="6.0.0-rc.1" /> |
|||
``` |
|||
|
|||
- In **MyApplicationWebModule.cs** replace usings and **module dependencies**: |
|||
|
|||
```csharp |
|||
using Volo.Abp.AspNetCore.Authentication.JwtBearer; |
|||
... |
|||
typeof(AbpAccountWebIdentityServerModule), |
|||
typeof(AbpAspNetCoreAuthenticationJwtBearerModule), |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
typeof(AbpAccountWebModule), |
|||
``` |
|||
|
|||
- In **MyApplicationWebModule.cs** `ConfigureServices` method **update authentication configuration**: |
|||
|
|||
```csharp |
|||
ConfigureAuthentication(context, configuration); |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
ConfigureAuthentication(context); |
|||
``` |
|||
|
|||
and update the `ConfigureAuthentication` private method to: |
|||
|
|||
```csharp |
|||
private void ConfigureAuthentication(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); |
|||
} |
|||
``` |
|||
|
|||
- In **MyApplicationWebModule.cs** `OnApplicationInitialization` method **replace IdentityServer and JwtToken midwares**: |
|||
|
|||
```csharp |
|||
app.UseJwtTokenMiddleware(); |
|||
app.UseIdentityServer(); |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
app.UseAbpOpenIddictValidation(); |
|||
``` |
|||
|
|||
|
|||
## Web Project (Tiered Solution) |
|||
|
|||
- In the **MyApplicationWebModule.cs** update the `AddAbpOpenIdConnect` configurations: |
|||
|
|||
```csharp |
|||
.AddAbpOpenIdConnect("oidc", options => |
|||
{ |
|||
options.Authority = configuration["AuthServer:Authority"]; |
|||
options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); |
|||
options.ResponseType = OpenIdConnectResponseType.CodeIdToken; |
|||
|
|||
options.ClientId = configuration["AuthServer:ClientId"]; |
|||
options.ClientSecret = configuration["AuthServer:ClientSecret"]; |
|||
|
|||
options.UsePkce = true; // Add this line |
|||
options.SaveTokens = true; |
|||
options.GetClaimsFromUserInfoEndpoint = true |
|||
|
|||
options.Scope.Add("roles"); // Replace "role" with "roles" |
|||
options.Scope.Add("email"); |
|||
options.Scope.Add("phone"); |
|||
options.Scope.Add("MyApplication"); |
|||
}); |
|||
``` |
|||
|
|||
Replace role scope to **roles** and add **UsePkce** and **SignoutScheme** options. |
|||
|
|||
## IdentityServer |
|||
|
|||
This project is renamed to **AuthServer** after v6.0.0-rc1. You can also refactor and rename your project to *AuthServer* for easier updates in the future. |
|||
|
|||
- In **MyApplication.IdentityServer.csproj** replace **project references**: |
|||
|
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.Account.Web.IdentityServer" Version="6.0.0-rc.1" /> |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.Account.Web.OpenIddict" Version="6.0.0-rc.1" /> |
|||
``` |
|||
|
|||
- In **MyApplicationIdentityServerModule.cs** replace usings and **module dependencies**: |
|||
|
|||
```csharp |
|||
typeof(AbpAccountWebIdentityServerModule), |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
typeof(AbpAccountWebOpenIddictModule), |
|||
``` |
|||
|
|||
- In the **MyApplicationIdentityServerModule.cs** add `PreConfigureServices` like below with your application name as the audience: |
|||
|
|||
```csharp |
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
PreConfigure<OpenIddictBuilder>(builder => |
|||
{ |
|||
builder.AddValidation(options => |
|||
{ |
|||
options.AddAudiences("MyApplication"); // Replace with your application name |
|||
options.UseLocalServer(); |
|||
options.UseAspNetCore(); |
|||
}); |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
- In **MyApplicationIdentityServerModule.cs** `OnApplicationInitialization` method **remove IdentityServer midware**: |
|||
|
|||
```csharp |
|||
app.UseIdentityServer(); |
|||
``` |
|||
|
|||
- To use the new AuthServer page, replace **Index.cshtml.cs** with [AuthServer Index.cshtml.cs](https://github.com/abpframework/abp-samples/blob/master/Ids2OpenId/src/Ids2OpenId.IdentityServer/Pages/Index.cshtml) and **Index.cshtml** file with [AuthServer Index.cshtml](https://github.com/abpframework/abp-samples/blob/master/Ids2OpenId/src/Ids2OpenId.IdentityServer/Pages/Index.cshtml.cs) and rename **Ids2OpenId** with your application namespace. |
|||
|
|||
> Note: It can be found under the *Pages* folder. |
|||
|
|||
## Http.Api.Host |
|||
|
|||
- In the **MyApplicationHttpApiHostModule.cs** `OnApplicationInitialization` method, delete `c.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]);` in `app.UseAbpSwaggerUI` options configurations which is no longer needed. |
|||
|
|||
- In `appsettings.json` delete **SwaggerClientSecret** from the *AuthServer* section like below: |
|||
|
|||
```json |
|||
"AuthServer": { |
|||
"Authority": "https://localhost:44345", |
|||
"RequireHttpsMetadata": "false", |
|||
"SwaggerClientId": "MyApplication_Swagger" |
|||
}, |
|||
``` |
|||
|
|||
## See Also |
|||
|
|||
* [OpenIddict Step-by-Step Guide](OpenIddict-Step-by-Step.md) |
|||
@ -0,0 +1,231 @@ |
|||
# Migrating from IdentityServer to OpenIddict Step by Step Guide |
|||
|
|||
This guide provides layer-by-layer guidance for migrating your existing application to [OpenIddict](https://github.com/openiddict/openiddict-core) from IdentityServer. ABP startup templates use `OpenIddict` OpenId provider from v6.0.0-rc1 by default and `IdentityServer` projects are renamed to `AuthServer` in tiered/separated solutions. Since OpenIddict is only available with ABP v6.0, you will need to update your existing application in order to apply OpenIddict changes. |
|||
|
|||
## History |
|||
We are not removing Identity Server packages and we will continue to release new versions of IdentityServer-related NuGet/NPM packages. That means you won't have an issue while upgrading to v6.0 when the stable version releases. We will continue to fix bugs in our packages for a while. ABP 7.0 will be based on .NET 7. If Identity Server continues to work with .NET 7, we will also continue to ship NuGet packages for our IDS integration. |
|||
|
|||
On the other hand, Identity Server ends support for the open-source Identity Server at the end of 2022. The Identity Server team has decided to move to Duende IDS and ABP will not be migrated to the commercial Duende IDS. You can see the Duende Identity Server announcement from [this link](https://blog.duendesoftware.com/posts/20220111_fair_trade). |
|||
|
|||
## OpenIddict Migration Steps |
|||
|
|||
Use the `abp update` command to update your existing application. See [Upgrading docs](../Upgrading.md) for more info. Apply required migrations by following the [Migration Guides](Index.md) based on your application version. |
|||
|
|||
### Domain.Shared Layer |
|||
|
|||
- In **MyApplication.Domain.Shared.csproj** replace **project reference**: |
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.IdentityServer.Domain.Shared" Version="6.0.0-rc.1" /> |
|||
``` |
|||
with |
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.OpenIddict.Domain.Shared" Version="6.0.0-rc.1" /> |
|||
``` |
|||
|
|||
- In **MyApplicationDomainSharedModule.cs** replace usings and **module dependencies:** |
|||
|
|||
```csharp |
|||
using Volo.Abp.IdentityServer; |
|||
... |
|||
typeof(AbpIdentityServerDomainSharedModule) |
|||
``` |
|||
with |
|||
```csharp |
|||
using Volo.Abp.OpenIddict; |
|||
... |
|||
typeof(AbpOpenIddictDomainSharedModule) |
|||
|
|||
### Domain Layer |
|||
|
|||
- In **MyApplication.Domain.csproj** replace **project references**: |
|||
|
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.IdentityServer.Domain" Version="6.0.0-rc.1" /> |
|||
<PackageReference Include="Volo.Abp.PermissionManagement.Domain.IdentityServer" Version="6.0.0-rc.1" /> |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.OpenIddict.Domain" Version="6.0.0-rc.1" /> |
|||
<PackageReference Include="Volo.Abp.PermissionManagement.Domain.OpenIddict" Version="6.0.0-rc.1" /> |
|||
``` |
|||
|
|||
- In **MyApplicationDomainModule.cs** replace usings and **module dependencies**: |
|||
|
|||
```csharp |
|||
using Volo.Abp.IdentityServer; |
|||
using Volo.Abp.PermissionManagement.IdentityServer; |
|||
... |
|||
typeof(AbpIdentityServerDomainModule), |
|||
typeof(AbpPermissionManagementDomainIdentityServerModule), |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
using Volo.Abp.OpenIddict; |
|||
using Volo.Abp.PermissionManagement.OpenIddict; |
|||
... |
|||
typeof(AbpOpenIddictDomainModule), |
|||
typeof(AbpPermissionManagementDomainOpenIddictModule), |
|||
``` |
|||
|
|||
#### OpenIddictDataSeedContributor |
|||
|
|||
- Create a folder named *OpenIddict* under the Domain project and copy the [OpenIddictDataSeedContributor.cs](https://github.com/abpframework/abp-samples/blob/master/Ids2OpenId/src/Ids2OpenId.Domain/OpenIddict/OpenIddictDataSeedContributor.cs) under this folder. Rename all the `Ids2OpenId` with your project name. |
|||
- Delete *IdentityServer* folder that contains `IdentityServerDataSeedContributor.cs` which is no longer needed. |
|||
|
|||
You can also create a project with the same name and copy the `OpenIddict` folder of the new project into your project. |
|||
|
|||
### EntityFrameworkCore Layer |
|||
|
|||
If you are using MongoDB, skip this step and check the *MongoDB* layer section. |
|||
|
|||
- In **MyApplication.EntityFrameworkCore.csproj** replace **project reference**: |
|||
|
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.IdentityServer.EntityFrameworkCore" Version="6.0.0-rc.1" /> |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.OpenIddict.EntityFrameworkCore" Version="6.0.0-rc.1" /> |
|||
``` |
|||
|
|||
- In **MyApplicationEntityFrameworkCoreModule.cs** replace usings and **module dependencies**: |
|||
|
|||
```csharp |
|||
using Volo.Abp.IdentityServer.EntityFrameworkCore; |
|||
... |
|||
typeof(AbpIdentityServerEntityFrameworkCoreModule), |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
using Volo.Abp.OpenIddict.EntityFrameworkCore; |
|||
... |
|||
typeof(AbpOpenIddictEntityFrameworkCoreModule), |
|||
``` |
|||
|
|||
- In **MyApplicationDbContext.cs** replace usings and **fluent api configurations**: |
|||
|
|||
```csharp |
|||
using Volo.Abp.IdentityServer.EntityFrameworkCore; |
|||
... |
|||
using Volo.Abp.OpenIddict.EntityFrameworkCore; |
|||
... |
|||
protected override void OnModelCreating(ModelBuilder builder) |
|||
{ |
|||
base.OnModelCreating(builder); |
|||
|
|||
/* Include modules to your migration db context */ |
|||
|
|||
... |
|||
builder.ConfigureIdentityServer(); |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
using Volo.Abp.OpenIddict.EntityFrameworkCore; |
|||
... |
|||
protected override void OnModelCreating(ModelBuilder builder) |
|||
{ |
|||
base.OnModelCreating(builder); |
|||
|
|||
/* Include modules to your migration db context */ |
|||
|
|||
... |
|||
builder.ConfigureOpenIddict(); |
|||
``` |
|||
|
|||
### MongoDB Layer |
|||
|
|||
If you are using EntityFrameworkCore, skip this step and check the *EntityFrameworkCore* layer section. |
|||
|
|||
- In **MyApplication.MongoDB.csproj** replace **project reference**: |
|||
|
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.IdentityServer.MongoDB" Version="6.0.0-rc.1" /> |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
<PackageReference Include="Volo.Abp.OpenIddict.MongoDB" Version="6.0.0-rc.1" /> |
|||
``` |
|||
|
|||
- In **MyApplicationMongoDbModule.cs** replace usings and **module dependencies**: |
|||
|
|||
```csharp |
|||
using Volo.Abp.IdentityServer.MongoDB; |
|||
... |
|||
typeof(AbpIdentityServerMongoDbModule), |
|||
``` |
|||
|
|||
with |
|||
|
|||
```csharp |
|||
using Volo.Abp.OpenIddict.MongoDB; |
|||
... |
|||
typeof(AbpOpenIddictMongoDbModule), |
|||
``` |
|||
|
|||
### DbMigrator Project |
|||
|
|||
- In **MyApplication.DbMigrator.csproj** **add project reference**: |
|||
|
|||
```csharp |
|||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" /> |
|||
``` |
|||
|
|||
for creating the host builder. |
|||
|
|||
- In `appsettings.json` **replace IdentityServer section with OpenIddict:** |
|||
|
|||
```json |
|||
"OpenIddict": { |
|||
"Applications": { |
|||
"MyApplication_Web": { |
|||
"ClientId": "MyApplication_Web", |
|||
"ClientSecret": "1q2w3e*", |
|||
"RootUrl": "https://localhost:44384" |
|||
}, |
|||
"MyApplication_App": { |
|||
"ClientId": "MyApplication_App", |
|||
"RootUrl": "http://localhost:4200" |
|||
}, |
|||
"MyApplication_BlazorServerTiered": { |
|||
"ClientId": "MyApplication_BlazorServerTiered", |
|||
"ClientSecret": "1q2w3e*", |
|||
"RootUrl": "https://localhost:44346" |
|||
}, |
|||
"MyApplication_Swagger": { |
|||
"ClientId": "MyApplication_Swagger", |
|||
"RootUrl": "https://localhost:44391" |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
Replace **MyApplication** with your application name. |
|||
|
|||
### UI Layer |
|||
|
|||
- [Angular UI Migration](OpenIddict-Angular.md) |
|||
- [MVC/Razor UI Migration](OpenIddict-Mvc.md) |
|||
- [Blazor-Server UI Migration](OpenIddict-Blazor-Server.md) |
|||
- [Blazor-Wasm UI Migration](OpenIddict-Blazor.md) |
|||
|
|||
## Source code of samples and module |
|||
|
|||
* [Open source tiered & separate auth server application migrate Identity Server to OpenIddct](https://github.com/abpframework/abp-samples/tree/master/Ids2OpenId) |
|||
* [OpenIddict module document](https://docs.abp.io/en/abp/6.0/Modules/OpenIddict) |
|||
* [OpenIddict module source code](https://github.com/abpframework/abp/tree/rel-6.0/modules/openiddict) |
|||
|
|||
## See Also |
|||
|
|||
* [ABP Version 6.0 Migration Guide](Abp-6_0.md) |
|||
@ -0,0 +1,137 @@ |
|||
# Dynamic Widget |
|||
|
|||
CMS kit provides a dynamic [widget](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Widgets) used to render the components previously developed by the software in the content of the pages and blog posts. Its means, that in static content you can use dynamic content. We will mention how you can do it. You have two choices to define the widget in the system: Writing and UI. |
|||
|
|||
### Adding the widget |
|||
Firstly we will show how to use the widget system via writing manually in the page and blogpost contents. |
|||
|
|||
Let's define the view component |
|||
|
|||
```csharp |
|||
[Widget] |
|||
[ViewComponent(Name = "CmsToday")] |
|||
public class TodayViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View("~/ViewComponents/Today.cshtml", |
|||
new TodayViewComponent()); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
```html |
|||
@model Volo.CmsKit.ViewComponents.TodayViewComponent |
|||
|
|||
<p>Welcome Today Component</p> |
|||
<p>@DateTime.Now.ToString()</p> |
|||
|
|||
``` |
|||
|
|||
Now configuration time on YourModule.cs |
|||
```csharp |
|||
Configure<CmsKitContentWidgetOptions>(options => |
|||
{ |
|||
options.AddWidget("Today","CmsToday"); |
|||
}); |
|||
``` |
|||
|
|||
Now you're ready to add your widget by writing. |
|||
[Widget Type="Today"] |
|||
|
|||
After completing the above steps, you can see the output at the right of the below screenshot. |
|||
 |
|||
|
|||
### Adding by using UI |
|||
Now we will mention the second option, using UI. |
|||
Once writing these definitions can make some mistakes hence we added a new feature to use the widget system easily. To the right of the editor, you will see the customized `W` button to add a dynamic widget like the below image. Don't forget please this is design mode and you need to view your page in view mode after saving. Also `Preview` tab on the editor will be ready to check your output easily for widget configurations in the next features. |
|||
|
|||
 |
|||
|
|||
### Adding by using UI with parameters |
|||
Let's improve the above example by adding a new parameter named format. Via this feature, we can use the widget system with many different scenarios but not prolong the document. Also, these examples can be expandable with dependency injection and getting values from the database, but we will use a basic example. We will add the format parameter to customize the date. |
|||
|
|||
```csharp |
|||
[Widget] |
|||
[ViewComponent(Name = "CmsToday")] |
|||
public class TodayViewComponent : AbpViewComponent |
|||
{ |
|||
public string Format { get; set; } |
|||
|
|||
public IViewComponentResult Invoke(string format) |
|||
{ |
|||
return View("~/ViewComponents/Today.cshtml", |
|||
new TodayViewComponent() { Format = format }); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
```html |
|||
@model Volo.CmsKit.ViewComponents.TodayViewComponent |
|||
|
|||
<p>Welcome Today Component</p> |
|||
<p>@DateTime.Now.ToString(Format)</p> |
|||
|
|||
``` |
|||
|
|||
Let's define the format component. |
|||
```csharp |
|||
[Widget] |
|||
[ViewComponent(Name = "Format")] |
|||
public class FormatViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View("~/ViewComponents/Format.cshtml", |
|||
new FormatViewModel()); |
|||
} |
|||
} |
|||
|
|||
public class FormatViewModel |
|||
{ |
|||
[DisplayName("Format your date in the component")] |
|||
public string Format { get; set; } |
|||
} |
|||
``` |
|||
> Important Note: To get properties properly you should set the `name` property on the razor page or you may use the ABP component. ABP handles that automatically. |
|||
|
|||
```html |
|||
@using Volo.CmsKit.ViewComponents |
|||
@model FormatViewModel |
|||
|
|||
<div> |
|||
<abp-input asp-for="Format" /> |
|||
</div> |
|||
``` |
|||
|
|||
```csharp |
|||
Configure<CmsKitContentWidgetOptions>(options => |
|||
{ |
|||
options.AddWidget("Today", "CmsToday", "Format"); |
|||
}); |
|||
``` |
|||
|
|||
 |
|||
|
|||
In this image, after choosing your widget (on the other case, it changes automatically up to your configuration, mine is `Today`. Its parameter name `parameterWidgetName` and its value is `Format`) you will see the next widget. Enter input values or choose them and click `Add`. You will see the underlined output in the editor. Right of the image, also you can see its previewed output. |
|||
|
|||
You can edit this output manually if do any wrong coding for that (wrong value or typo) you won't see the widget, even so, your page will be viewed successfully. |
|||
|
|||
## Options |
|||
To configure the widget, you should define the below code in YourModule.cs |
|||
|
|||
```csharp |
|||
Configure<CmsKitContentWidgetOptions>(options => |
|||
{ |
|||
options.AddWidget(widgetType: "Today", widgetName: "CmsToday", parameterWidgetName: "Format"); |
|||
}); |
|||
``` |
|||
|
|||
Let's look at these parameters in detail |
|||
* `widgetType` is used for end-user and more readable names. The following bold word represents widgetType. |
|||
[Widget Type="**Today**" Format="yyyy-dd-mm HH:mm:ss"]. |
|||
|
|||
* `widgetName` is used for your widget name used in code for the name of the `ViewComponent`. |
|||
|
|||
* `parameterWidgetName` is used the for editor component side to see on the `Add Widget` modal. |
|||
After choosing the widget type from listbox (now just defined `Format`) and renders this widget automatically. It's required only to see UI once using parameters |
|||
@ -1,19 +1,27 @@ |
|||
# Nightly Builds |
|||
|
|||
All framework & module packages are deployed to MyGet every night in weekdays. So, you can use or test the latest code without waiting the next release. |
|||
All framework & module packages are deployed to MyGet every night in weekdays. So, you can install the latest dev-brach builds to try out functionality prior to release. |
|||
|
|||
## Install & Uninstall Nightly Preview Packages |
|||
|
|||
The latest version of nightly preview packages can be installed by the running below command in the root folder of application: |
|||
The latest version of nightly preview packages can be installed by the running below command in the root folder of the application: |
|||
|
|||
```bash |
|||
abp switch-to-nightly |
|||
``` |
|||
|
|||
> Note that this command doesn't create a project with nightly preview packages. Instead, it switches package versions of a project with the nightly preview packages. |
|||
|
|||
After this command, a new NuGet feed will be added to the `NuGet.Config` file of your project. Then, you can get the latest code of ABP Framework without waiting for the next release. |
|||
|
|||
> You can check the [abp-nightly gallery](https://www.myget.org/gallery/abp-nightly) to see the all nightly preview packages. |
|||
|
|||
If you're using the ABP Framework nightly preview packages, you can switch back to stable version using this command: |
|||
|
|||
```bash |
|||
abp switch-to-stable |
|||
``` |
|||
|
|||
ABP nightly NuGet feed is [https://www.myget.org/F/abp-nightly/api/v3/index.json](https://www.myget.org/F/abp-nightly/api/v3/index.json). |
|||
|
|||
See the [ABP CLI documentation](./CLI.md) for more information. |
|||
|
|||
@ -0,0 +1,215 @@ |
|||
# LeptonX Lite MVC UI |
|||
LeptonX Lite has implementation for the ABP Framework Razor Pages. It's a simplified variation of the [LeptonX Theme](https://x.leptontheme.com/). |
|||
|
|||
> If you are looking for a professional, enterprise ready theme, you can check the [LeptonX Theme](https://x.leptontheme.com/), which is a part of [ABP Commercial](https://commercial.abp.io/). |
|||
|
|||
> See the [Theming document](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Theming) to learn about themes. |
|||
|
|||
## Installation |
|||
|
|||
This theme is **already installed** when you create a new solution using the startup templates. If you are using any other template, you can install this theme by following the steps below: |
|||
|
|||
- Add the **Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite** package to your **Web** application. |
|||
|
|||
```bash |
|||
dotnet add package Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite --prerelease |
|||
``` |
|||
|
|||
- Remove the **Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic** reference from the project since it's not necessary after switching to LeptonX Lite. |
|||
|
|||
- Make sure the old theme is removed and LeptonX is added in your Module class. |
|||
|
|||
```diff |
|||
[DependsOn( |
|||
// Remove the BasicTheme module from DependsOn attribute |
|||
- typeof(AbpAspNetCoreMvcUiBasicThemeModule), |
|||
|
|||
// Add the LeptonX Lite module to DependsOn attribute |
|||
+ typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule), |
|||
)] |
|||
``` |
|||
|
|||
- Replace `BasicThemeBundles` with `LeptonXLiteThemeBundles` in AbpBundlingOptions |
|||
|
|||
```diff |
|||
Configure<AbpBundlingOptions>(options => |
|||
{ |
|||
options.StyleBundles.Configure( |
|||
// Remove the following line |
|||
- BasicThemeBundles.Styles.Global, |
|||
// Add the following line instead |
|||
+ LeptonXLiteThemeBundles.Styles.Global |
|||
bundle => |
|||
{ |
|||
bundle.AddFiles("/global-styles.css"); |
|||
} |
|||
); |
|||
}); |
|||
``` |
|||
|
|||
## Customization |
|||
|
|||
### Layouts |
|||
|
|||
LeptonX Lite Mvc provides **layouts** for your **user interface** based [ABP Framework Theming](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Theming). You can use **layouts** to **organize your user interface**. |
|||
|
|||
The main responsibility of a theme is to **provide** the layouts. There are **three pre-defined layouts that must be implemented by all the themes:** |
|||
|
|||
* **Application:** The **default** layout which is used by the **main** application pages. |
|||
|
|||
* **Account:** Mostly used by the **account module** for **login**, **register**, **forgot password**... pages. |
|||
|
|||
* **Empty:** The **Minimal** layout that **has no layout components** at all. |
|||
|
|||
**Layout names** are **constants** defined in the `LeptonXLiteTheme` class in the **Mvc** project **root**. |
|||
|
|||
> The layout pages define under the `Themes/LeptonXLite/Layouts` folder and you can **override it** by creating a file with the **same name** and **under** the **same folder**. |
|||
|
|||
### Toolbars |
|||
LeptonX Lite includes separeted toolbars for desktop & mobile. You can manage toolbars independently. Toolbar names can be accessible in the **LeptonXLiteToolbars** class. |
|||
|
|||
- `LeptonXLiteToolbars.Main` |
|||
- `LeptonXLiteToolbars.MainMobile` |
|||
|
|||
```csharp |
|||
public class MyProjectNameMainToolbarContributor : IToolbarContributor |
|||
{ |
|||
public async Task ConfigureToolbarAsync(IToolbarConfigurationContext context) |
|||
{ |
|||
if (context.Toolbar.Name == LeptonXLiteToolbars.Main) |
|||
{ |
|||
context.Toolbar.Items.Add(new ToolbarItem(typeof(MyDesktopComponent))); |
|||
} |
|||
|
|||
if (context.Toolbar.Name == LeptonXLiteToolbars.MainMobile) |
|||
{ |
|||
context.Toolbar.Items.Add(new ToolbarItem(typeof(MyMobileComponent))); |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
# LeptonX Lite Mvc Components |
|||
|
|||
Abp **helps** you make **highly customizable UI**. You can easily **customize** your themes to fit your needs. **The Virtual File System** makes it possible to **manage files** that **do not physically** exist on the **file system** (disk). It's mainly used to embed **(js, css, image..)** files into assemblies and **use them like** physical files at runtime. An application (or another module) can **override** a **virtual file of a module** just like placing a file with the **same name** and **extension** into the **same folder** of the **virtual file**. |
|||
|
|||
LeptonX Lite is built on the [Abp Framework](https://abp.io/), so you can **easily** customize your Asp.Net Core Mvc user interface by following [Abp Mvc UI Customization](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Customization-xUser-Interface). |
|||
|
|||
## Brand Component |
|||
|
|||
The **brand component** is a simple component that can be used to display your brand. It contains a **logo** and a **company name**. |
|||
|
|||
 |
|||
|
|||
### How to override the Brand Component in LeptonX Lite Mvc |
|||
|
|||
* The **brand component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/Brand/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. |
|||
|
|||
* The **brand component (C# file)** is defined in the `Themes/LeptonXLite/Components/Brand/MainNavbarBrandViewComponent.cs` file and you can **override it** by creating a file with the **same name** and under the **same folder**. |
|||
|
|||
## Breadcrumb Component |
|||
|
|||
On websites that have a lot of pages, **breadcrumb navigation** can greatly **enhance the way users find their way** around. In terms of **usability**, breadcrumbs reduce the number of actions a website **visitor** needs to take in order to get to a **higher-level page**, and they **improve** the **findability** of **website sections** and **pages**. |
|||
|
|||
 |
|||
|
|||
### How to override the Breadcrumb Component in LeptonX Lite Mvc |
|||
|
|||
* The **breadcrumb component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/Breadcrumbs/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. |
|||
|
|||
* The **breadcrumb component (C# file)** is defined in the `Themes/LeptonXLite/Components/Breadcrumbs/BreadcrumbsViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. |
|||
|
|||
## Sidebar Menu Component |
|||
|
|||
Sidebar menus have been used as **a directory for Related Pages** to a **Service** offering, **Navigation** items to a **specific service** or topic and even just as **Links** the user may be interested in. |
|||
|
|||
 |
|||
|
|||
### How to override the Sidebar Menu Component in LeptonX Lite Mvc |
|||
|
|||
* **Sidebar menu page (.cshtml)** is defined in the `Themes/LeptonXLite/Components/Menu/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. |
|||
|
|||
* If you want to **override the menu component (C#)** you can override the `Themes/LeptonXLite/Components/Menu/MainMenuViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. |
|||
|
|||
> The **sidebar menu** renders menu items **dynamically**. The **menu item** is a **partial view** and is defined in the `Themes/LeptonXLite/Components/Menu/_MenuItem.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. |
|||
|
|||
## Page Alerts Component |
|||
|
|||
Provides contextual **feedback messages** for typical user actions with the handful of **available** and **flexible** **alert messages**. Alerts are available for any length of text, as well as an **optional dismiss button**. |
|||
|
|||
 |
|||
|
|||
### How to override the Page Alerts Component in LeptonX Lite Mvc |
|||
|
|||
* The **page alerts component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/PageAlerts/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. |
|||
|
|||
* The **page alerts component (C#)** is defined in the `Themes/LeptonXLite/Components/PageAlerts/PageAlertsViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. |
|||
|
|||
## Toolbar Component |
|||
|
|||
Toolbar items are used to add **extra functionality to the toolbar**. The toolbar is a **horizontal bar** that **contains** a group of **toolbar items**. |
|||
|
|||
### How to override the Toolbar Component in LeptonX Lite Mvc |
|||
|
|||
* The **toolbar component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/Toolbar/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. |
|||
|
|||
* The **toolbar component (C#)** is defined in the `Themes/LeptonXLite/Components/Toolbar/ToolbarViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. |
|||
|
|||
## Toolbar Item Component |
|||
|
|||
The toolbar item is a **single item** that **contains** a **link**, an **icon**, a **label** etc.. |
|||
|
|||
### How to override the Toolbar Item Component in LeptonX Lite Mvc |
|||
|
|||
* The **toolbar item component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/ToolbarItems/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. |
|||
|
|||
* The **toolbar item component (C#)** is defined in the `Themes/LeptonXLite/Components/ToolbarItems/ToolbarItemsViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. |
|||
|
|||
You can find the toolbar components below: |
|||
|
|||
## Language Switch Component |
|||
|
|||
Think about a **multi-lingual** website and the first thing that could **hit your mind** is **the language switch component**. A **navigation bar** is a **great place** to **embed a language switch**. By embedding the language switch in the navigation bar of your website, you would **make it simpler** for users to **find it** and **easily** switch the **language** <u>**without trying to locate it across the website.**</u> |
|||
|
|||
 |
|||
|
|||
### How to override the Language Switch Component in LeptonX Lite Mvc |
|||
|
|||
* The **language switch component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/LanguageSwitch/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. |
|||
|
|||
* The **language switch component (C#)** is defined in the `Themes/LeptonXLite/Components/LanguageSwitch/LanguageSwitchViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. |
|||
|
|||
## Mobile Language Switch Component |
|||
|
|||
The **mobile** **language switch component** is used to switch the language of the website **on mobile devices**. The mobile language switch component is a **dropdown menu** that **contains all the languages** of the website. |
|||
|
|||
 |
|||
|
|||
### How to override the Mobile Language Switch Component in LeptonX Lite Mvc |
|||
|
|||
* The **mobile language switch component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/MobileLanguageSwitch/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. |
|||
|
|||
* The **mobile language switch component (C#)** is defined in the `Themes/LeptonXLite/Components/MobileLanguageSwitch/MobileLanguageSwitchViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. |
|||
|
|||
## User Menu Component |
|||
|
|||
The **User Menu** is the **menu** that **drops down** when you **click your name** or **profile picture** in the **upper right corner** of your page (**in the toolbar**). It drops down options such as **Settings**, **Logout**, etc. |
|||
|
|||
 |
|||
|
|||
### How to override the User Menu Component in LeptonX Lite Mvc |
|||
|
|||
* The **user menu component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/UserMenu/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. |
|||
|
|||
* The **user menu component (C#)** is defined in the `Themes/LeptonXLite/Components/UserMenu/UserMenuViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. |
|||
|
|||
## Mobile User Menu Component |
|||
|
|||
The **mobile user menu component** is used to display the **user menu on mobile devices**. The mobile user menu component is a **dropdown menu** that contains all the **options** of the **user menu**. |
|||
|
|||
 |
|||
|
|||
### How to override the Mobile User Menu Component in LeptonX Lite Mvc |
|||
|
|||
* The **mobile user menu component page (.cshtml file)** is defined in the `Themes/LeptonXLite/Components/MobileUserMenu/Default.cshtml` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. |
|||
|
|||
* The **mobile user menu component (C#)** is defined in the `Themes/LeptonLite/Components/MobileUserMenu/MobileUserMenuViewComponent.cs` file and you can **override it** by creating a file with the **same name** and **under** the **same folder**. |
|||