@ -1,165 +1,89 @@ |
|||
GNU LESSER GENERAL PUBLIC LICENSE |
|||
Version 3, 29 June 2007 |
|||
|
|||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> |
|||
Everyone is permitted to copy and distribute verbatim copies |
|||
of this license document, but changing it is not allowed. |
|||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> |
|||
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. |
|||
|
|||
This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public. |
|||
The license is supplemented by the additional permissions listed below. |
|||
|
|||
This version of the GNU Lesser General Public License incorporates |
|||
the terms and conditions of version 3 of the GNU General Public |
|||
License, supplemented by the additional permissions listed below. |
|||
|
|||
0. Additional Definitions. |
|||
|
|||
As used herein, "this License" refers to version 3 of the GNU Lesser |
|||
General Public License, and the "GNU GPL" refers to version 3 of the GNU |
|||
General Public License. |
|||
**0. Additional Definitions.** |
|||
|
|||
As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. |
|||
|
|||
"The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. |
|||
|
|||
An "Application" is any work that makes use of an interface provided by the Library but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. |
|||
|
|||
A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". |
|||
|
|||
The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application and not on the Linked Version. |
|||
|
|||
The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. |
|||
|
|||
|
|||
|
|||
**1. Exception to Section 3 of the GNU GPL.** |
|||
|
|||
You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. |
|||
|
|||
|
|||
|
|||
**2. Conveying Modified Versions.** |
|||
|
|||
If you modify a copy of the Library and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: |
|||
|
|||
**a)** under this License, provided that you make a good-faith effort to ensure that, in the event an Application does not supply the |
|||
function or data, the facility still operates and performs whatever part of its purpose remains meaningful, or |
|||
|
|||
**b)** under the GNU GPL, with none of the additional permissions of this License is applicable to that copy. |
|||
|
|||
|
|||
|
|||
**3. Object Code Incorporating Material from Library Header Files.** |
|||
|
|||
The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such an object code under terms of your choice, provided that if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: |
|||
|
|||
**a)** Give prominent notice with each copy of the object code that the Library is used in it, and the Library and its use are covered by this License. |
|||
|
|||
**b)** Accompany the object code with a copy of the GNU GPL and this license document. |
|||
|
|||
|
|||
|
|||
**4. Combined Works.** |
|||
|
|||
You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: |
|||
|
|||
**a)** Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. |
|||
|
|||
**b)** Accompany the Combined Work with a copy of the GNU GPL and this license document. |
|||
|
|||
**c)** For a Combined Work that displays copyright notices during execution, including the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. |
|||
|
|||
**d)** Do one of the following: |
|||
|
|||
0) Convey the Minimal Corresponding Source under the terms of this License and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. |
|||
1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. |
|||
|
|||
**e)** Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying the Corresponding Source.) |
|||
|
|||
|
|||
"The Library" refers to a covered work governed by this License, |
|||
other than an Application or a Combined Work as defined below. |
|||
|
|||
An "Application" is any work that makes use of an interface provided |
|||
by the Library, but which is not otherwise based on the Library. |
|||
Defining a subclass of a class defined by the Library is deemed a mode |
|||
of using an interface provided by the Library. |
|||
|
|||
A "Combined Work" is a work produced by combining or linking an |
|||
Application with the Library. The particular version of the Library |
|||
with which the Combined Work was made is also called the "Linked |
|||
Version". |
|||
|
|||
The "Minimal Corresponding Source" for a Combined Work means the |
|||
Corresponding Source for the Combined Work, excluding any source code |
|||
for portions of the Combined Work that, considered in isolation, are |
|||
based on the Application, and not on the Linked Version. |
|||
|
|||
The "Corresponding Application Code" for a Combined Work means the |
|||
object code and/or source code for the Application, including any data |
|||
and utility programs needed for reproducing the Combined Work from the |
|||
Application, but excluding the System Libraries of the Combined Work. |
|||
|
|||
1. Exception to Section 3 of the GNU GPL. |
|||
|
|||
You may convey a covered work under sections 3 and 4 of this License |
|||
without being bound by section 3 of the GNU GPL. |
|||
|
|||
2. Conveying Modified Versions. |
|||
|
|||
If you modify a copy of the Library, and, in your modifications, a |
|||
facility refers to a function or data to be supplied by an Application |
|||
that uses the facility (other than as an argument passed when the |
|||
facility is invoked), then you may convey a copy of the modified |
|||
version: |
|||
|
|||
a) under this License, provided that you make a good faith effort to |
|||
ensure that, in the event an Application does not supply the |
|||
function or data, the facility still operates, and performs |
|||
whatever part of its purpose remains meaningful, or |
|||
|
|||
b) under the GNU GPL, with none of the additional permissions of |
|||
this License applicable to that copy. |
|||
|
|||
3. Object Code Incorporating Material from Library Header Files. |
|||
|
|||
The object code form of an Application may incorporate material from |
|||
a header file that is part of the Library. You may convey such object |
|||
code under terms of your choice, provided that, if the incorporated |
|||
material is not limited to numerical parameters, data structure |
|||
layouts and accessors, or small macros, inline functions and templates |
|||
(ten or fewer lines in length), you do both of the following: |
|||
|
|||
a) Give prominent notice with each copy of the object code that the |
|||
Library is used in it and that the Library and its use are |
|||
covered by this License. |
|||
|
|||
b) Accompany the object code with a copy of the GNU GPL and this license |
|||
document. |
|||
|
|||
4. Combined Works. |
|||
|
|||
You may convey a Combined Work under terms of your choice that, |
|||
taken together, effectively do not restrict modification of the |
|||
portions of the Library contained in the Combined Work and reverse |
|||
engineering for debugging such modifications, if you also do each of |
|||
the following: |
|||
|
|||
a) Give prominent notice with each copy of the Combined Work that |
|||
the Library is used in it and that the Library and its use are |
|||
covered by this License. |
|||
|
|||
b) Accompany the Combined Work with a copy of the GNU GPL and this license |
|||
document. |
|||
|
|||
c) For a Combined Work that displays copyright notices during |
|||
execution, include the copyright notice for the Library among |
|||
these notices, as well as a reference directing the user to the |
|||
copies of the GNU GPL and this license document. |
|||
|
|||
d) Do one of the following: |
|||
|
|||
0) Convey the Minimal Corresponding Source under the terms of this |
|||
License, and the Corresponding Application Code in a form |
|||
suitable for, and under terms that permit, the user to |
|||
recombine or relink the Application with a modified version of |
|||
the Linked Version to produce a modified Combined Work, in the |
|||
manner specified by section 6 of the GNU GPL for conveying |
|||
Corresponding Source. |
|||
|
|||
1) Use a suitable shared library mechanism for linking with the |
|||
Library. A suitable mechanism is one that (a) uses at run time |
|||
a copy of the Library already present on the user's computer |
|||
system, and (b) will operate properly with a modified version |
|||
of the Library that is interface-compatible with the Linked |
|||
Version. |
|||
|
|||
e) Provide Installation Information, but only if you would otherwise |
|||
be required to provide such information under section 6 of the |
|||
GNU GPL, and only to the extent that such information is |
|||
necessary to install and execute a modified version of the |
|||
Combined Work produced by recombining or relinking the |
|||
Application with a modified version of the Linked Version. (If |
|||
you use option 4d0, the Installation Information must accompany |
|||
the Minimal Corresponding Source and Corresponding Application |
|||
Code. If you use option 4d1, you must provide the Installation |
|||
Information in the manner specified by section 6 of the GNU GPL |
|||
for conveying Corresponding Source.) |
|||
|
|||
5. Combined Libraries. |
|||
|
|||
You may place library facilities that are a work based on the |
|||
Library side by side in a single library together with other library |
|||
facilities that are not Applications and are not covered by this |
|||
License, and convey such a combined library under terms of your |
|||
choice, if you do both of the following: |
|||
|
|||
a) Accompany the combined library with a copy of the same work based |
|||
on the Library, uncombined with any other library facilities, |
|||
conveyed under the terms of this License. |
|||
|
|||
b) Give prominent notice with the combined library that part of it |
|||
is a work based on the Library, and explaining where to find the |
|||
accompanying uncombined form of the same work. |
|||
|
|||
6. Revised Versions of the GNU Lesser General Public License. |
|||
|
|||
The Free Software Foundation may publish revised and/or new versions |
|||
of the GNU Lesser General Public License from time to time. Such new |
|||
versions will be similar in spirit to the present version, but may |
|||
differ in detail to address new problems or concerns. |
|||
|
|||
Each version is given a distinguishing version number. If the |
|||
Library as you received it specifies that a certain numbered version |
|||
of the GNU Lesser General Public License "or any later version" |
|||
applies to it, you have the option of following the terms and |
|||
conditions either of that published version or of any later version |
|||
published by the Free Software Foundation. If the Library as you |
|||
received it does not specify a version number of the GNU Lesser |
|||
General Public License, you may choose any version of the GNU Lesser |
|||
General Public License ever published by the Free Software Foundation. |
|||
|
|||
If the Library as you received it specifies that a proxy can decide |
|||
whether future versions of the GNU Lesser General Public License shall |
|||
apply, that proxy's public statement of acceptance of any version is |
|||
permanent authorization for you to choose that version for the |
|||
Library. |
|||
You may place library facilities that are work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License and convey such a combined library under the terms of your choice if you do both of the following: |
|||
|
|||
**a)** Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. |
|||
|
|||
**b)** Give prominent notice with the combined library that part of it is a work based on the Library and explains where to find the accompanying uncombined form of the same work. |
|||
|
|||
|
|||
|
|||
**6. Revised Versions of the GNU Lesser General Public License.** |
|||
|
|||
The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version but may differ in detail to address new problems or concerns. |
|||
|
|||
Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received, it does not specify a version number of the GNU Lesser |
|||
General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. |
|||
|
|||
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 391 KiB |
|
After Width: | Height: | Size: 262 KiB |
|
After Width: | Height: | Size: 195 KiB |
@ -0,0 +1,111 @@ |
|||
# Unifying the ABP.IO Platform |
|||
|
|||
I am very excited to announce that some big changes and improvements are coming to the ABP.IO Platform soon. In this post, I will explain the changes we are currently working on. Here, a brief list of these changes: |
|||
|
|||
* We are merging the subdomains of the ABP.IO Platform websites: Community.abp.io, commercial.abp.io, blog.abp.io, docs.abp.io websites and their contents are being merged into the main domain, abp.io. |
|||
* ABP (open source) and ABP Commercial documents are being merged into a single documentation. |
|||
* Introducing ABP Studio Community Edition. |
|||
|
|||
These changes won't effect the license conditions. The open source part will remain the same and the commercial license contents will also be the same. The aim of the changes is to make the platform more consistent, holistic, understandable and easy to start. |
|||
|
|||
Let's dive deep... |
|||
|
|||
## Merging the ABP.IO Websites |
|||
|
|||
ABP.IO website has many subdomains currently: |
|||
|
|||
* **abp.io**: Home page of the open source ABP Framework project. |
|||
* **community.abp.io**: A website that community can share contents and we organize events. |
|||
* **commercial.abp.io**: A website to promote and sell commercial ABP licenses which have pre-built modules, themes, tooling and support on top of the ABP Framework. |
|||
* **docs.abp.io**: The technical documentation of the ABP Framework and ABP Commercial. |
|||
* **blog.abp.io**: A blog website to announce the news on the platform. |
|||
* **support.abp.io**: Premium support for the ABP Commercial customers. |
|||
|
|||
All these subdomains (except the support website for now) are being merged to the abp.io domain. All their contents and UI designs are being revised and enriched. |
|||
|
|||
Some fundamental purposes of that change are; |
|||
|
|||
* Making content more coherent and holistic, |
|||
* Making the design more harmonious, |
|||
* Making the contents of the old subdomains more visible and reachable, |
|||
* Allow you to navigate through the web pages much easier, |
|||
* Reducing duplications between different websites, |
|||
|
|||
I will highlight a few important changes in the next sections. |
|||
|
|||
### The New Mega Menu |
|||
|
|||
As I said above, the abp.io UI design is also being revised. One of the big revisions is the main menu. We are replacing the current main navigation by a mega menu as shown in the following figure: |
|||
|
|||
 |
|||
|
|||
We believe that new mega menu will allow you to navigate through the web pages much easier. |
|||
|
|||
### The New Get Started Page |
|||
|
|||
We are constantly working to improve ABP's onboarding experience. With the new platform changes, we now offer ABP Studio as the starting point for the ABP Platform. You can still use the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) to created new ABP solutions, but the new ABP Studio makes it much easier and understandable. It also provides features to easily run and monitor your applications, even in the Community edition. |
|||
|
|||
 |
|||
|
|||
You can easily download and install ABP Studio, login with your abp.io account and create your first ABP solution. |
|||
|
|||
### The New Pricing Page |
|||
|
|||
Since the [ABP Commercial website](https://commercial.abp.io/) has merged with the main website, you will see the *Pricing* page located on the main menu of the abp.io website. We have completely revised the design and content of this page to better reflect which features are open source and free, and what is included in the paid licenses. |
|||
|
|||
 |
|||
|
|||
As mentioned above, all the free & open source features are still free & open source. In addition, we included the ABP Studio Community edition (will be explained below) to the free license. |
|||
|
|||
## Merging the ABP Platform Documentation |
|||
|
|||
Currently, ABP Framework (open source) and ABP Commercial [documents](https://docs.abp.io/) are completely separated. You can switch between them on the left side: |
|||
|
|||
 |
|||
|
|||
Based on our and customers' experiences, there are some problems with that approach: |
|||
|
|||
* Getting started, development tutorials, release notes, road map and some other documents are duplicated (or very similar) among ABP Framework and ABP Commercial documents. |
|||
* For ABP Commercial users, it is not clear if they also need to read the ABP Framework (open source) documentation or not. Also, when they read the framework document, some parts are different for ABP Commercial users, and it is also not clear in some cases. |
|||
|
|||
We are currently working to completely merge the ABP Framework (open source) and ABP Commercial documentation, remove duplications and revisit the contents. We will clearly indicate if a part of a document requires a paid license. |
|||
|
|||
The left navigation panel tree is also completely revisited and simplified: |
|||
|
|||
 |
|||
|
|||
## The ABP Studio Community Edition |
|||
|
|||
[ABP Studio](https://docs.abp.io/en/commercial/latest/studio/index) is a cross-platform desktop application designed for ABP and .NET developers. It aims to provide a comfortable development environment by automating tasks, providing insights about your solution, and simplifying the processes of creation, development, execution, browsing, monitoring, tracing, and deploying your solutions. |
|||
|
|||
Here, a screenshot from the *Solution Runner* screen of ABP Studio: |
|||
|
|||
 |
|||
|
|||
ABP Studio has been started as a commercial product, as a part of [ABP Commercial](https://commercial.abp.io/). We are very excited to announce that the *Community Edition* will be available soon for free. It will have some missing features and limitations compared to the full edition, but will be enough to create, explore and run ABP solutions easily. |
|||
|
|||
We will be offering ABP Studio as a starting point to the ABP platform. The [Getting Started](https://docs.abp.io/en/abp/latest/Getting-Started-Overall) and other documents will use ABP Studio to create new solutions and perform ABP-related operations. |
|||
|
|||
## Other News |
|||
|
|||
We are also working on some other topics related to these changes. Some of them are; |
|||
|
|||
* Completely renewing the [startup templates](https://docs.abp.io/en/abp/latest/Startup-Templates/Index) (with ABP Studio), so they will be more flexible and will provide more options. |
|||
* Providing a tool to automatically convert ABP solutions created with open source startup templates into ABP commercial. |
|||
|
|||
## Questions |
|||
|
|||
I tried to explain all the important changes in this post. However, you may have some questions in your mind. |
|||
|
|||
### What should open source users expect? |
|||
|
|||
Since the [ABP Commercial](https://commercial.abp.io/) website content is merged with the main [abp.io](https://abp.io/) website, you will see paid features being introduced on the main website. The pricing page will also be available on the same website. This may lead you to wonder whether the ABP Platform is a fully paid product. The simple answer to this question is "No". Actually, nothing has changed on the open source side. Everything will be the same. Additionally, open source users will now have ABP Studio Community Edition for free. So open source has more for its users than before. |
|||
|
|||
### What should ABP Commercial customers expect? |
|||
|
|||
ABP Commercial license holders may wonder if any license change happens. The answer is "No". All the license types, rules, restrictions and features are the same. With the changes explained in this post, you will follow the documentation easier (since you won't need to go to another website for the framework documentation) and you will better understand what special features are available to you. |
|||
|
|||
## Last Words |
|||
|
|||
With this post, we wanted to announce the changes to be made on the ABP platform to the ABP community, so don't be surprised or curious about what happened. If you have any questions or suggestions, feel free to write a comment for this blog post or send an email to info@abp.io. |
|||
|
|||
@ -0,0 +1,269 @@ |
|||
# ABP.IO Platform 8.2 RC Has Been Released |
|||
|
|||
Today, we are happy to release the [ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) version **8.2 RC** (Release Candidate). This blog post introduces the new features and important changes in this new version. |
|||
|
|||
Try this version and provide feedback for a more stable version of ABP v8.2! Thanks to all of you. |
|||
|
|||
## Get Started with the 8.2 RC |
|||
|
|||
Follow the steps below to try version 8.2.0 RC today: |
|||
|
|||
1) **Upgrade** the ABP CLI to version `8.2.0-rc.3` using a command line terminal: |
|||
|
|||
````bash |
|||
dotnet tool update Volo.Abp.Cli -g --version 8.2.0-rc.3 |
|||
```` |
|||
|
|||
**or install** it if you haven't before: |
|||
|
|||
````bash |
|||
dotnet tool install Volo.Abp.Cli -g --version 8.2.0-rc.3 |
|||
```` |
|||
|
|||
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 [Get Started](https://abp.io/get-started) page to generate a CLI command to create a new application. |
|||
|
|||
You can use any IDE that supports .NET 8.x, like [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/). |
|||
|
|||
## Migration Guides |
|||
|
|||
There are a few breaking changes in this version that may affect your application. |
|||
Please see the following migration documents, if you are upgrading from v8.x or earlier: |
|||
|
|||
* [ABP Framework 8.x to 8.2 Migration Guide](https://docs.abp.io/en/abp/8.2/Migration-Guides/Abp-8_2) |
|||
* [ABP Commercial 8.x to 8.2 Migration Guide](https://docs.abp.io/en/commercial/8.2/migration-guides/v8_2) |
|||
|
|||
## What's New with ABP Framework 8.2? |
|||
|
|||
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: |
|||
|
|||
* Blazor Full-Stack Web App UI |
|||
* Introducing the `IBlockUiService` for Blazor UI |
|||
* Allowing Case-Insensitive Indexes for MongoDB |
|||
* Other News... |
|||
|
|||
### Blazor Full-Stack Web App UI |
|||
|
|||
ASP.NET Blazor in .NET 8 allows you to use a single powerful component model to handle all of your web UI needs, including server-side rendering, client-side rendering, streaming rendering, progressive enhancement, and much more! |
|||
|
|||
ABP v8.2.x supports the new Blazor Web App template, which you can directly create with the following command: |
|||
|
|||
```bash |
|||
abp new BookStore -t app -u blazor-webapp |
|||
``` |
|||
|
|||
When you create the project, you will typically see two main projects for Blazor UI, besides other projects: |
|||
|
|||
* **MyCompanyName.MyProjectName.Blazor.WebApp** (startup project of your application, and contains `App.razor` component, which is the root component of your application) |
|||
* **MyCompanyName.MyProjectName.Blazor.WebApp.Client** |
|||
|
|||
This new template overcomes the disadvantages of both Blazor WASM and Blazor Server applications and allows you to decide which approaches to use for a specific page or component. Therefore, you can imagine this new web UI as a combination of both Blazor Server and Blazor WASM. |
|||
|
|||
> This approach mainly overcomes the **large binary downloads of Blazor WASM**, and it resolves the Blazor Server's problem, which **always needs to be connected to the server via SignalR**. |
|||
|
|||
> If you are considering migrating your existing Blazor project to Blazor WebApp or want to learn more about this new template, please read the [Migrating to Blazor Web App](https://docs.abp.io/en/abp/8.2/Migration-Guides/Abp-8-2-Blazor-Web-App) guide. |
|||
|
|||
### Introducing the `IBlockUiService` for Blazor UI |
|||
|
|||
In this version, ABP Framework introduces the [`IBlockUiService`](https://docs.abp.io/en/abp/8.2/UI/Blazor/Block-Busy) for Blazor UI. This service uses UI Block API to disable/block the page or a part of the page. |
|||
|
|||
You just need to simply inject the `IBlockUiService` to your page or component and call the `Block` or `UnBlock` method to block/unblock the specified element: |
|||
|
|||
```csharp |
|||
namespace MyProject.Blazor.Pages |
|||
{ |
|||
public partial class Index |
|||
{ |
|||
private readonly IBlockUiService _blockUiService; |
|||
|
|||
public Index(IBlockUiService _blockUiService) |
|||
{ |
|||
_blockUiService = blockUiService; |
|||
} |
|||
|
|||
public async Task BlockForm() |
|||
{ |
|||
/* |
|||
Parameters of Block method: |
|||
selectors: A string containing one or more selectors to match. https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector#selectors |
|||
busy : Set to true to show a progress indicator on the blocked area. |
|||
*/ |
|||
await _blockUiService.Block(selectors: "#MySelectors", busy: true); |
|||
|
|||
//Unblocking the element |
|||
await _blockUiService.UnBlock(selectors: "#MySelectors"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
``` |
|||
|
|||
Here is the resulting UI with all possible options (**block**, **block busy**, and **unblock**): |
|||
|
|||
 |
|||
|
|||
### Allowing Case-Insensitive Indexes for MongoDB |
|||
|
|||
MongoDB allows case-insensitive string comparisons by using case-insensitive indexes. You can create a case-insensitive index by specifying a **collation**. |
|||
|
|||
To do that, you should override the `CreateModal` method, configure the `CreateCollectionOptions`, and specify the **collation** as below: |
|||
|
|||
```csharp |
|||
protected override void CreateModel(IMongoModelBuilder modelBuilder) |
|||
{ |
|||
base.CreateModel(modelBuilder); |
|||
|
|||
modelBuilder.Entity<Question>(b => |
|||
{ |
|||
b.CreateCollectionOptions.Collation = new Collation(locale:"en_US", strength: CollationStrength.Secondary); |
|||
|
|||
b.ConfigureIndexes(indexes => |
|||
{ |
|||
indexes.CreateOne( |
|||
new CreateIndexModel<BsonDocument>( |
|||
Builders<BsonDocument>.IndexKeys.Ascending("MyProperty"), |
|||
new CreateIndexOptions { Unique = true } |
|||
) |
|||
); |
|||
} |
|||
); |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
After this configuration, a unique index will be created for the `MyProperty` property and then you can perform case-insensitive string comparisons without the need to worry. See [#19073](https://github.com/abpframework/abp/pull/19073) for more information. |
|||
|
|||
### Other News |
|||
|
|||
* Angular package version has been updated to v17.3.0. See [#19915](https://github.com/abpframework/abp/pull/19915) for more info. |
|||
* OpenIddict [5.4.0 has been released on March 26](https://github.com/openiddict/openiddict-core/releases/tag/5.4.0). Therefore, we decided to upgrade the OpenIddict packages to v5.4.0. See [#19427](https://github.com/abpframework/abp/issues/19427). |
|||
* AutoMapper [13.0.1 was released on February 8](https://github.com/AutoMapper/AutoMapper/releases/tag/v13.0.1) and in this version, we upgraded AutoMapper packages to v13.0.1. See [#19564](https://github.com/abpframework/abp/pull/19564/). |
|||
* See other completed tasks in this version: [https://github.com/abpframework/abp/releases?q=8.2.0-rc](https://github.com/abpframework/abp/releases?q=8.2.0-rc&expanded=true) |
|||
|
|||
## What's New with ABP Commercial 8.2? |
|||
|
|||
We've also worked on ABP Commercial to align the features and changes made in the ABP Framework. The following sections introduce a few new features coming with ABP Commercial 8.2. |
|||
|
|||
### Session Management |
|||
|
|||
The [Session Management](https://docs.abp.io/en/commercial/8.2/modules/identity/session-management) feature allows you to prevent concurrent login and manage user sessions. You can allow concurrent login, allow only one session of the same device type, or logout from all other devices when a new session is created, by specifying in the settings page: |
|||
|
|||
 |
|||
|
|||
Also, you can view and manage users sessions on the `Users` page of the [Identity Module](https://docs.abp.io/en/commercial/8.2/modules/identity): |
|||
|
|||
 |
|||
 |
|||
|
|||
### Suite: File/Image Property |
|||
|
|||
In this version, ABP Suite allows you to add a file/image property for an entity. You can select "File" as the property type for your properties as in the following figure: |
|||
|
|||
 |
|||
|
|||
Then, when you generate your entity and try to insert a record, you will see the file upload component on the create/update models: |
|||
|
|||
 |
|||
|
|||
You can upload a file with any supported extensions and under 10MB (this can be increased in the generated code, if you wish) and after that, you can download, delete and update the existing file any time you want: |
|||
|
|||
 |
|||
|
|||
> **Note:** This feature has already been implemented for MVC & Blazor UIs, but not implemented for Angular UI yet. We aim to implement it for Angular UI with v8.2.0. |
|||
|
|||
### Suite: DateOnly & TimeOnly Types |
|||
|
|||
In this version on, ABP Suite provides `DateOnly` and `TimeOnly` types as property types. You can select these types when you create an entity: |
|||
|
|||
 |
|||
|
|||
Then, all related configurations (including db configurations) will be made by ABP Suite, and you will be able to see the fields in the UI: |
|||
|
|||
 |
|||
|
|||
> **Note**: The `DateOnly` and `TimeOnly` types were introduced with .NET 6. Therefore, please make sure that all of your projects' target frameworks are .NET8+. With ABP v8.2, all startup templates target a single target framework, which is .NET8, so if you created your project with version 8.2+, you don't need to make any changes. |
|||
|
|||
### Periodic Log Deletion for Audit Logs |
|||
|
|||
In this version, the [Audit Logging Module](https://docs.abp.io/en/commercial/8.2/modules/audit-logging) provides a built-in periodic log deletion system. You can enable/disable the clean-up service system wide, in this way, you can turn off the clean up service for all tenants and their hosts: |
|||
|
|||
 |
|||
|
|||
> If the system wide clean up service is enabled, you can configure the global *Expired Item Deletion Period* for all tenants and hosts. |
|||
|
|||
When configuring the global settings for the audit log module from the host side in this manner, ensure that each tenant and host uses the global values. If you want to set tenant/host-specific values, you can do so under *Settings* -> *Audit Log* -> *General*. This way, you can disable the clean up service for specific tenants or host. It overrides the global settings: |
|||
|
|||
 |
|||
|
|||
> **Note**: To view the audit log settings, you need to enable the feature. For the host side, navigate to *Settings* -> *Feature Management* -> *Manage Host Features* -> *Audit Logging* -> *Enable audit log setting management*. |
|||
|
|||
## Community News |
|||
|
|||
### ABP Dotnet Conf 2024 Wrap Up |
|||
|
|||
 |
|||
|
|||
We organized [ABP Dotnet Conference 2024](https://abp.io/conference/2024) on May 2024 and we are happy to share the success of the conference, which captivated overwhelmingly interested live viewers from all over the world. 29 great line up of speakers which includes .NET experts and Microsoft MVPs delivered captivating talks that resonated with the audiences. Each of the talks attracted a great amount of interest and a lot of questions, sparking curiosity in the attendees. |
|||
|
|||
Thanks to all speakers and attendees for joining our event. 🙏 |
|||
|
|||
> We shared our takeaways in a blog post, which you can read at [https://blog.abp.io/abp/ABP-Dotnet-Conference-2024-Wrap-Up](https://blog.abp.io/abp/ABP-Dotnet-Conference-2024-Wrap-Up). |
|||
|
|||
### DevDays Europe 2024 |
|||
|
|||
 |
|||
|
|||
Co-founder of [Volosoft](https://volosoft.com/), [Alper Ebiçoğlu](https://twitter.com/alperebicoglu) gave a speech about "How to Build a Multi-Tenant ASP.NET Core Application" at the [DevDays Europe 2024](https://devdays.lt/) on the 20th of May. |
|||
|
|||
### DevOps Pro Europe 2024 |
|||
|
|||
 |
|||
|
|||
We are thrilled to announce that the co-founder of [Volosoft](https://volosoft.com/) and Lead Developer of the [ABP Framework](https://abp.io/), [Halil Ibrahim Kalkan](https://x.com/hibrahimkalkan) gave a speech about "Building a Kubernetes Integrated Local Development Environment" in the [DevOps Pro Europe](https://devopspro.lt/) on the 24th of May. |
|||
|
|||
### Devnot Dotnet Conference 2024 |
|||
|
|||
We are happy to announce that core team members of the [ABP Framework](https://abp.io/), [Alper Ebiçoğlu](https://twitter.com/alperebicoglu) and [Enis Necipoğlu](https://twitter.com/EnisNecipoglu) will give speeches at the [Devnot Dotnet Conference 2024](https://dotnet.devnot.com/) on 25th of May. |
|||
|
|||
[Alper Ebiçoğlu](https://twitter.com/alperebicoglu) will talk about **"AspNet Core & Multitenancy"**: |
|||
|
|||
 |
|||
|
|||
On the other hand, [Enis Necipoğlu](https://twitter.com/EnisNecipoglu) will talk about **"Reactive Programming with .NET MAUI"**: |
|||
|
|||
 |
|||
|
|||
### New ABP Community Articles |
|||
|
|||
There are exciting articles contributed by the ABP community as always. I will highlight some of them here: |
|||
|
|||
* [Ahmed Tarek](https://twitter.com/AhmedTarekHasa1) has created **four** new community articles: |
|||
* [🤔 When Implementations Affect Abstractions ⁉️](https://community.abp.io/posts/-when-implementations-affect-abstractions--ekx1o5xn) |
|||
* [👍 Design Best Practices In .NET C# 👀](https://community.abp.io/posts/design-best-practices-in-.net-c--eg8q8xh0) |
|||
* [👍 Chain of Responsibility Design Pattern In .NET C# 👀](https://community.abp.io/posts/chain-of-responsibility-design-pattern-in-.net-c--djmvkug1) |
|||
* [Flagged Enumerations: How To Represent Features Combinations Into One Field](https://community.abp.io/posts/flagged-enumerations-how-to-represent-features-combinations-into-one-field-9gj4l670) |
|||
* [Engincan Veske](https://github.com/EngincanV) has created **three** new community articles: |
|||
* [Performing Case-Insensitive Search in ABP Based-PostgreSQL Application: Using citext and Collation](https://community.abp.io/posts/caseinsensitive-search-in-abp-basedpostgresql-application-c9kb05dc) |
|||
* [Sentiment Analysis Within ABP-Based Application](https://community.abp.io/posts/sentiment-analysis-within-abpbased-application-lbsfkoxq) |
|||
* [Reusing and Optimizing Machine Learning Models in .NET](https://community.abp.io/posts/reusing-and-optimizing-machine-learning-models-in-.net-qj4ycnwu) |
|||
* [Unlocking Modularity in ABP.io A Closer Look at the Contributor Pattern](https://community.abp.io/posts/unlocking-modularity-in-abp.io-a-closer-look-at-the-contributor-pattern-ixf6wgbw) by [Qais Al khateeb](https://community.abp.io/members/qais.alkhateeb@devnas-jo.com) |
|||
* [Deploy Your ABP Framework MVC Project to Azure Container Apps](https://community.abp.io/posts/deploy-your-abp-framework-mvc-project-to-azure-container-apps-r93u9c6d) by [Selman Koç](https://community.abp.io/members/selmankoc) |
|||
* [How claim type works in ASP NET Core and ABP Framework](https://community.abp.io/posts/how-claim-type-works-in-asp-net-core-and-abp-framework-km5dw6g1) by [Liming Ma](https://github.com/maliming) |
|||
* [Using FluentValidation with ABP Framework](https://community.abp.io/posts/using-fluentvalidation-with-abp-framework-2cxuwl70) by [Enes Döner](https://community.abp.io/members/Enes) |
|||
* [Using Blob Storage with ABP](https://community.abp.io/posts/using-blob-storage-with-abp-framework-jygtmhn4) by [Emre Kendirli](https://community.abp.io/members/emrekenderli) |
|||
|
|||
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://community.abp.io/posts/submit) to the ABP Community. |
|||
|
|||
## Conclusion |
|||
|
|||
This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://docs.abp.io/en/abp/8.2/Road-Map) documentation to learn about the release schedule and planned features for the next releases. Please try ABP v8.2 RC and provide feedback to help us release a more stable version. |
|||
|
|||
Thanks for being a part of this community! |
|||
|
After Width: | Height: | Size: 443 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 208 KiB |
|
After Width: | Height: | Size: 207 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 16 KiB |
@ -0,0 +1,114 @@ |
|||
# Using Blob Storage with ABP |
|||
ABP Framework provides a comprehensive solution to meet the needs of modern application development, while addressing the important requirement of BLOB Storing. ABP Framework [provides an easy integration for Blob Storing](https://docs.abp.io/en/abp/latest/Blob-Storing) and offers many storage services that you can easily integrate. In addition to efficiently storing large files, these services offer significant advantages such as scalability, security and backup. |
|||
|
|||
## What is Blob Storage ? |
|||
|
|||
Blob Storage is a service for storing unstructured data. It is becoming increasingly important to efficiently store and manage large data types (e.g. images, videos, documents). Blob Storage was developed to meet these needs and offers a secure solution with the advantages of scalability, durability and low cost. |
|||
|
|||
 |
|||
|
|||
|
|||
## How to use Blob Storage ? |
|||
|
|||
I would like to explain this to you with an example.For example, storing large files such as user profile pictures in the database negatively affects the performance and database.You can store this data using Blob storage. One of the advantages of storing user profile pictures in blob storage is that it improves database performance. Blob storage is a more efficient option than storing large size files in the database and allows database queries to run faster. Furthermore, blob storage provides scalability, so that the number of profile pictures can grow with the number of users, but without storage issues. This approach also maintains database normalization and makes the database design cleaner. |
|||
|
|||
How do we store user profile pictures with Blob Storage using ABP Framework? |
|||
|
|||
- #### Step 1: Configure the Blob Container |
|||
|
|||
Define a Blob Container named `profile-pictures` using the `[BlobContainerName("profile-pictures")]` attribute. |
|||
|
|||
````csharp |
|||
[BlobContainerName("profile-pictures")] |
|||
public class ProfilePictureContainer |
|||
{ |
|||
|
|||
} |
|||
```` |
|||
- #### Step 2: Create the ProfileAppService (Saving & Reading BLOBs) |
|||
|
|||
Create the `ProfileAppService` class and derive it from the `ApplicationService` class. This class will perform the necessary operations to store and retrieve profile pictures. |
|||
|
|||
````csharp |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
public class ProfileAppService : ApplicationService |
|||
{ |
|||
// Code snippets will come here |
|||
} |
|||
```` |
|||
|
|||
- #### Step 3: Inject the `IBlobContainer` Service |
|||
|
|||
Inject the `IBlobContainer` service, in the constructor of the `ProfileAppService` class. The `IBlobContainer` is the main interface to store and read BLOB and is used to interact with the container. |
|||
|
|||
````csharp |
|||
private readonly IBlobContainer<ProfilePictureContainer> _blobContainer; |
|||
|
|||
public ProfileAppService(IBlobContainer<ProfilePictureContainer> blobContainer) |
|||
{ |
|||
_blobContainer = blobContainer; |
|||
} |
|||
```` |
|||
|
|||
- #### Step 4: Save Profile Picture |
|||
|
|||
The SaveProfilePictureAsync method is used to store the user's profile picture. A unique name is generated based on the user's credentials and the profile picture byte array with this name is saved in the Blob Container. |
|||
|
|||
````csharp |
|||
public async Task SaveProfilePictureAsync(byte[] bytes) |
|||
{ |
|||
var blobName = CurrentUser.GetId().ToString(); |
|||
await _blobContainer.SaveAsync(blobName, bytes); |
|||
} |
|||
```` |
|||
|
|||
- #### Step 5: Getting Profile Picture |
|||
|
|||
The GetProfilePictureAsync method is used to get the user's profile picture. A profile picture byte array is retrieved from the Blob Container with a specified name based on the user's credential. |
|||
|
|||
````csharp |
|||
public async Task<byte[]> GetProfilePictureAsync() |
|||
{ |
|||
var blobName = CurrentUser.GetId().ToString(); |
|||
return await _blobContainer.GetAllBytesOrNullAsync(blobName); |
|||
} |
|||
```` |
|||
|
|||
|
|||
Finally, add controls in the user interface that will allow users to upload and view their profile pictures. These controls will perform the operations by calling the corresponding methods in the ProfileAppService class. |
|||
|
|||
These steps cover the basic steps to store user profile pictures with Blob Storage using the ABP Framework. [Check out the documentation for more information.](https://docs.abp.io/en/abp/latest/Blob-Storing) |
|||
|
|||
|
|||
## What are the Advantages/Disadvantages of Keeping the BLOBs in a Database? |
|||
|
|||
#### Advantages: |
|||
|
|||
- Data Integrity and Relational Model: To ensure data integrity and preserve the relational model, it is important to store blob data in the database. This approach preserves the relationships between data and maintains the structural integrity of the database. |
|||
|
|||
- A Single Storage Location: Storing blob data in the database allows you to collect all data in a single storage location. This simplifies the management of data and increases data integrity. |
|||
|
|||
- Advanced Security Controls: Database systems often offer advanced security controls. Storing blob data in a database allows you to take advantage of these security features and ensures that data is accessed by authorized users. |
|||
|
|||
#### Disadvantages: |
|||
|
|||
- Performance Issues: Storing blob data in a database can negatively impact database performance. Oversized blob data can slow down query processing and reduce database performance. |
|||
|
|||
- Storage Space Issue: Storing blob data in the database can increase the size of the database and require more storage space. This can increase storage costs and complicate infrastructure requirements. |
|||
|
|||
- Backup and Recovery Challenges: Storing blob data in a database can make backup and recovery difficult. The large size of blob data can make backup and recovery time-consuming and data recovery difficult. |
|||
|
|||
|
|||
## Other Blob Storage Providers |
|||
|
|||
ABP Framework provides developers with a variety of options and flexibility by offering integration infrastructure for multiple cloud providers. This makes it easy for users to choose between different cloud platforms and select the most suitable solution for their business needs. |
|||
|
|||
|
|||
- Azure Blob Storage: A cloud storage service offered on the Microsoft Azure platform. It is used to store and access large amounts of data. It supports various data types such as files, images, videos and provides high scalability. ABP Framework provides integration with [Azure Blob Storage](https://docs.abp.io/en/abp/latest/Blob-Storing-Azure). |
|||
|
|||
- Aliyun Object Storage Service (OSS): OSS, Alibaba Cloud's cloud storage service, is an ideal solution for use cases such as big data storage, backup and media storage. It offers flexible storage options and provides a high level of security. ABP Framework interfaces with [Aliyun Blob Storage](https://docs.abp.io/en/abp/latest/Blob-Storing-Aliyun), making it easier for developers to manage data storage and access. |
|||
|
|||
- MinIO: MinIO is known as an open source object storage system and offers an Amazon S3 compatible cloud storage solution. It is a high-performance, scalable and fast storage service. ABP Framework integrates with [MinIO Blob Storage](https://docs.abp.io/en/abp/latest/Blob-Storing-Minio) to provide developers with cloud-based file and object storage. |
|||
|
|||
- Amazon Simple Storage Service (S3): Amazon S3 is a cloud storage service offered on the Amazon Web Services (AWS) platform. It can be used to store virtually unlimited amounts of data. It provides high durability, scalability and low cost.ABP Framework integrates with [Amazon S3 Blob Storage](https://docs.abp.io/en/abp/latest/Blob-Storing-Aws) to provide developers with cloud-based file and object storage. |
|||
@ -0,0 +1,201 @@ |
|||
# How claim type works in ASP NET Core and ABP Framework |
|||
|
|||
## The Claim Type |
|||
|
|||
A web application may use one or more authentication schemes to obtain the current user's information, such as `Cookies`, `JwtBearer`, `OpenID Connect`, `Google` etc. |
|||
|
|||
After authentication, we get a set of claims that can be issued using a trusted identity provider. A claim is a type/name-value pair representing the subject. The type property provides the semantic content of the claim, that is, it states what the claim is about. |
|||
|
|||
The [`ICurrentUser`](https://docs.abp.io/en/abp/latest/CurrentUser) service of the ABP Framework provides a convenient way to access the current user's information from the claims. |
|||
|
|||
The claim type is the key to getting the correct value of the current user, and we have a static `AbpClaimTypes` class that defines the names of the standard claims in the ABP Framework: |
|||
|
|||
```cs |
|||
public static class AbpClaimTypes |
|||
{ |
|||
public static string UserId { get; set; } = ClaimTypes.NameIdentifier; |
|||
public static string UserName { get; set; } = ClaimTypes.Name; |
|||
public static string Role { get; set; } = ClaimTypes.Role; |
|||
public static string Email { get; set; } = ClaimTypes.Email; |
|||
//... |
|||
} |
|||
``` |
|||
|
|||
As you can see, the default claim type of `AbpClaimTypes` comes from the [`System.Security.Claims.ClaimTypes`](https://learn.microsoft.com/en-us/dotnet/api/system.security.claims.claimtypes) class, which is the recommended practice in NET. |
|||
|
|||
## Claim type in different authentication schemes |
|||
|
|||
We usually see two types of claim types in our daily development. One of them is the [`System.Security.Claims.ClaimTypes`](https://learn.microsoft.com/en-us/dotnet/api/system.security.claims.claimtypes) and the other one is the `OpenId Connect` [standard claims](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims). |
|||
|
|||
### ASP NET Core Identity |
|||
|
|||
There is a [`ClaimsIdentityOptions`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.claimsidentityoptions) property in the `IdentityOptions`, which can be used to configure the claim type: |
|||
|
|||
| Property | Description | |
|||
|----------------------|---------------------------------------------------------------------------------------------------------------| |
|||
| EmailClaimType | Gets or sets the ClaimType used for the user email claim. Defaults to Email. | |
|||
| RoleClaimType | Gets or sets the ClaimType used for a Role claim. Defaults to Role. | |
|||
| SecurityStampClaimType | Gets or sets the ClaimType used for the security stamp claim. Defaults to "AspNet.Identity.SecurityStamp". | |
|||
| UserIdClaimType | Gets or sets the ClaimType used for the user identifier claim. Defaults to NameIdentifier. | |
|||
| UserNameClaimType | Gets or sets the ClaimType used for the user name claim. Defaults to Name. | |
|||
|
|||
* The Identity creates a `ClaimsIdentity` object with the claim type that you have configured in the `ClaimsIdentityOptions` class. |
|||
* The ABP Framework configures it based on `AbpClaimTypes,` so usually you don't need to worry about it. |
|||
|
|||
### JwtBearer/OpenID Connect Client |
|||
|
|||
The `JwtBearer/OpenID Connect` gets claims from `id_token` or fetches user information from the `AuthServer`, and then maps/adds it to the current `ClaimsIdentity`. |
|||
|
|||
To map the [standard claim](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims) type to the [`System.Security.Claims.ClaimTypes`](https://learn.microsoft.com/en-us/dotnet/api/system.security.claims.claimtypes) via [azure-activedirectory-identitymodel-extensions-for-dotnet](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) library by default, which is maintained by the Microsoft team: |
|||
|
|||
```cs |
|||
Dictionary<string, string> ClaimTypeMapping = new Dictionary<string, string> |
|||
{ |
|||
{ "actort", ClaimTypes.Actor }, |
|||
{ "birthdate", ClaimTypes.DateOfBirth }, |
|||
{ "email", ClaimTypes.Email }, |
|||
{ "family_name", ClaimTypes.Surname }, |
|||
{ "gender", ClaimTypes.Gender }, |
|||
{ "given_name", ClaimTypes.GivenName }, |
|||
{ "nameid", ClaimTypes.NameIdentifier }, |
|||
{ "sub", ClaimTypes.NameIdentifier }, |
|||
{ "website", ClaimTypes.Webpage }, |
|||
{ "unique_name", ClaimTypes.Name }, |
|||
{ "oid", "http://schemas.microsoft.com/identity/claims/objectidentifier" }, |
|||
{ "scp", "http://schemas.microsoft.com/identity/claims/scope" }, |
|||
{ "tid", "http://schemas.microsoft.com/identity/claims/tenantid" }, |
|||
{ "acr", "http://schemas.microsoft.com/claims/authnclassreference" }, |
|||
{ "adfs1email", "http://schemas.xmlsoap.org/claims/EmailAddress" }, |
|||
{ "adfs1upn", "http://schemas.xmlsoap.org/claims/UPN" }, |
|||
{ "amr", "http://schemas.microsoft.com/claims/authnmethodsreferences" }, |
|||
{ "authmethod", ClaimTypes.AuthenticationMethod }, |
|||
{ "certapppolicy", "http://schemas.microsoft.com/2012/12/certificatecontext/extension/applicationpolicy" }, |
|||
{ "certauthoritykeyidentifier", "http://schemas.microsoft.com/2012/12/certificatecontext/extension/authoritykeyidentifier" }, |
|||
{ "certbasicconstraints", "http://schemas.microsoft.com/2012/12/certificatecontext/extension/basicconstraints" }, |
|||
{ "certeku", "http://schemas.microsoft.com/2012/12/certificatecontext/extension/eku" }, |
|||
{ "certissuer", "http://schemas.microsoft.com/2012/12/certificatecontext/field/issuer" }, |
|||
{ "certissuername", "http://schemas.microsoft.com/2012/12/certificatecontext/field/issuername" }, |
|||
{ "certkeyusage", "http://schemas.microsoft.com/2012/12/certificatecontext/extension/keyusage" }, |
|||
{ "certnotafter", "http://schemas.microsoft.com/2012/12/certificatecontext/field/notafter" }, |
|||
{ "certnotbefore", "http://schemas.microsoft.com/2012/12/certificatecontext/field/notbefore" }, |
|||
{ "certpolicy", "http://schemas.microsoft.com/2012/12/certificatecontext/extension/certificatepolicy" }, |
|||
{ "certpublickey", ClaimTypes.Rsa }, |
|||
{ "certrawdata", "http://schemas.microsoft.com/2012/12/certificatecontext/field/rawdata" }, |
|||
{ "certserialnumber", ClaimTypes.SerialNumber }, |
|||
{ "certsignaturealgorithm", "http://schemas.microsoft.com/2012/12/certificatecontext/field/signaturealgorithm" }, |
|||
{ "certsubject", "http://schemas.microsoft.com/2012/12/certificatecontext/field/subject" }, |
|||
{ "certsubjectaltname", "http://schemas.microsoft.com/2012/12/certificatecontext/extension/san" }, |
|||
{ "certsubjectkeyidentifier", "http://schemas.microsoft.com/2012/12/certificatecontext/extension/subjectkeyidentifier" }, |
|||
{ "certsubjectname", "http://schemas.microsoft.com/2012/12/certificatecontext/field/subjectname" }, |
|||
{ "certtemplateinformation", "http://schemas.microsoft.com/2012/12/certificatecontext/extension/certificatetemplateinformation" }, |
|||
{ "certtemplatename", "http://schemas.microsoft.com/2012/12/certificatecontext/extension/certificatetemplatename" }, |
|||
{ "certthumbprint", ClaimTypes.Thumbprint }, |
|||
{ "certx509version", "http://schemas.microsoft.com/2012/12/certificatecontext/field/x509version" }, |
|||
{ "clientapplication", "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-application" }, |
|||
{ "clientip", "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-ip" }, |
|||
{ "clientuseragent", "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-user-agent" }, |
|||
{ "commonname", "http://schemas.xmlsoap.org/claims/CommonName" }, |
|||
{ "denyonlyprimarygroupsid", ClaimTypes.DenyOnlyPrimaryGroupSid }, |
|||
{ "denyonlyprimarysid", ClaimTypes.DenyOnlyPrimarySid }, |
|||
{ "denyonlysid", ClaimTypes.DenyOnlySid }, |
|||
{ "devicedispname", "http://schemas.microsoft.com/2012/01/devicecontext/claims/displayname" }, |
|||
{ "deviceid", "http://schemas.microsoft.com/2012/01/devicecontext/claims/identifier" }, |
|||
{ "deviceismanaged", "http://schemas.microsoft.com/2012/01/devicecontext/claims/ismanaged" }, |
|||
{ "deviceostype", "http://schemas.microsoft.com/2012/01/devicecontext/claims/ostype" }, |
|||
{ "deviceosver", "http://schemas.microsoft.com/2012/01/devicecontext/claims/osversion" }, |
|||
{ "deviceowner", "http://schemas.microsoft.com/2012/01/devicecontext/claims/userowner" }, |
|||
{ "deviceregid", "http://schemas.microsoft.com/2012/01/devicecontext/claims/registrationid" }, |
|||
{ "endpointpath", "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-endpoint-absolute-path" }, |
|||
{ "forwardedclientip", "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-forwarded-client-ip" }, |
|||
{ "group", "http://schemas.xmlsoap.org/claims/Group" }, |
|||
{ "groupsid", ClaimTypes.GroupSid }, |
|||
{ "idp", "http://schemas.microsoft.com/identity/claims/identityprovider" }, |
|||
{ "insidecorporatenetwork", "http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork" }, |
|||
{ "isregistereduser", "http://schemas.microsoft.com/2012/01/devicecontext/claims/isregistereduser" }, |
|||
{ "ppid", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier" }, |
|||
{ "primarygroupsid", ClaimTypes.PrimaryGroupSid }, |
|||
{ "primarysid", ClaimTypes.PrimarySid }, |
|||
{ "proxy", "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-proxy" }, |
|||
{ "pwdchgurl", "http://schemas.microsoft.com/ws/2012/01/passwordchangeurl" }, |
|||
{ "pwdexpdays", "http://schemas.microsoft.com/ws/2012/01/passwordexpirationdays" }, |
|||
{ "pwdexptime", "http://schemas.microsoft.com/ws/2012/01/passwordexpirationtime" }, |
|||
{ "relyingpartytrustid", "http://schemas.microsoft.com/2012/01/requestcontext/claims/relyingpartytrustid" }, |
|||
{ "role", ClaimTypes.Role }, |
|||
{ "roles", ClaimTypes.Role }, |
|||
{ "upn", ClaimTypes.Upn }, |
|||
{ "winaccountname", ClaimTypes.WindowsAccountName }, |
|||
}; |
|||
``` |
|||
|
|||
#### Disable JwtBearer/OpenID Connect Client Claim Type Mapping |
|||
|
|||
To turn off the claim type mapping, you can set the `MapInboundClaims` property of `JwtBearerOptions` or `OpenIdConnectOptions` to `false`. Then, you can get the original claim types from the token(`access_token` or `id_token`): |
|||
|
|||
JWT Example: |
|||
|
|||
```json |
|||
{ |
|||
"iss": "https://localhost:44305/", |
|||
"exp": 1714466127, |
|||
"iat": 1714466127, |
|||
"aud": "MyProjectName", |
|||
"scope": "MyProjectName offline_access", |
|||
"sub": "ed7f5cfd-7311-0402-245c-3a123ff787f9", |
|||
"unique_name": "admin", |
|||
"preferred_username": "admin", |
|||
"given_name": "admin", |
|||
"role": "admin", |
|||
"email": "admin@abp.io", |
|||
"email_verified": "False", |
|||
"phone_number_verified": "False", |
|||
} |
|||
``` |
|||
|
|||
### OAuth2(Google, Facebook, Twitter, Microsoft) Extenal Login Client |
|||
|
|||
The `OAuth2 handler` fetchs a JSON containing user information from the `OAuth2` server. The third-party provider issues the claim type based on their standard server and then maps/adds it to the current `ClaimsIdentity`. The ASP NET Core provides some built-in claim-type mappings for different providers as can be seen below examples: |
|||
|
|||
**Example**: The `ClaimActions` property of the `GoogleOptions` maps the Google's claim types to [`System.Security.Claims.ClaimTypes`](https://learn.microsoft.com/en-us/dotnet/api/system.security.claims.claimtypes): |
|||
|
|||
```cs |
|||
ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id"); // v2 |
|||
ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub"); // v3 |
|||
ClaimActions.MapJsonKey(ClaimTypes.Name, "name"); |
|||
ClaimActions.MapJsonKey(ClaimTypes.GivenName, "given_name"); |
|||
ClaimActions.MapJsonKey(ClaimTypes.Surname, "family_name"); |
|||
ClaimActions.MapJsonKey("urn:google:profile", "link"); |
|||
ClaimActions.MapJsonKey(ClaimTypes.Email, "email"); |
|||
``` |
|||
|
|||
**Example**: The `ClaimActions` property of the `FacebookOptions` maps the Facebook's claim types to [`System.Security.Claims.ClaimTypes`](https://learn.microsoft.com/en-us/dotnet/api/system.security.claims.claimtypes): |
|||
|
|||
```cs |
|||
ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id"); |
|||
ClaimActions.MapJsonSubKey("urn:facebook:age_range_min", "age_range", "min"); |
|||
ClaimActions.MapJsonSubKey("urn:facebook:age_range_max", "age_range", "max"); |
|||
ClaimActions.MapJsonKey(ClaimTypes.DateOfBirth, "birthday"); |
|||
ClaimActions.MapJsonKey(ClaimTypes.Email, "email"); |
|||
ClaimActions.MapJsonKey(ClaimTypes.Name, "name"); |
|||
ClaimActions.MapJsonKey(ClaimTypes.GivenName, "first_name"); |
|||
ClaimActions.MapJsonKey("urn:facebook:middle_name", "middle_name"); |
|||
ClaimActions.MapJsonKey(ClaimTypes.Surname, "last_name"); |
|||
ClaimActions.MapJsonKey(ClaimTypes.Gender, "gender"); |
|||
ClaimActions.MapJsonKey("urn:facebook:link", "link"); |
|||
ClaimActions.MapJsonSubKey("urn:facebook:location", "location", "name"); |
|||
ClaimActions.MapJsonKey(ClaimTypes.Locality, "locale"); |
|||
ClaimActions.MapJsonKey("urn:facebook:timezone", "timezone"); |
|||
``` |
|||
|
|||
### OpenIddict AuthServer |
|||
|
|||
The `OpenIddict` uses the [standard claims](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims) as the claim type of the `id_token` or `access_token` and `UserInfo` endpoint response, etc. |
|||
|
|||
* For JWT token, it also uses the [azure-activedirectory-identitymodel-extensions-for-dotnet](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) to get the claims from the `id_token` or `access_token`. |
|||
* For reference token, it gets the claims from the `database`. |
|||
|
|||
## Summary |
|||
|
|||
Once you find the claims you received do not meet your expectations, follow the instructions above to troubleshoot the problem. |
|||
|
|||
This article can help you understand the claim type in the ABP Framework and ASP NET Core. |
|||
|
|||
@ -0,0 +1,86 @@ |
|||
# Deploy Your ABP Framework MVC Project to Azure Container Apps |
|||
|
|||
 |
|||
|
|||
In this article, we will show the seamless deployment of an ABP Framework MVC project to Azure Container Apps. enabling you to deploy and run containerized applications without the hassle of managing the infrastructure underneath. It provides an uncomplicated and cost-effective method for deploying and scaling your applications. |
|||
|
|||
### Getting Started with ABP Framework MVC and Azure Container Apps |
|||
|
|||
To get started, you will need an ABP Framework MVC project that you want to deploy. If you don't have one, you can [create a new project using the ABP CLI](https://docs.abp.io/en/abp/latest/Startup-Templates/Application). You will also need [an Azure subscription](https://azure.microsoft.com) and [an Azure SQL database](https://azure.microsoft.com/en-gb/products/azure-sql). |
|||
|
|||
Before creating Azure container apps resources and deploying the ABP Framework MVC project, I show you how you can effortlessly create Docker images and push them to Docker Hub, leveraging the pre-configured Docker file and scripts that come with the ABP MVC framework. |
|||
|
|||
### Creating a Docker Image for ABP Framework MVC |
|||
|
|||
To create a Docker image for your ABP Framework MVC project, navigate to `etc/build/build-images-locally.ps1` and fix the script to match your Docker Hub username and image name. Then, run the script to build the Docker image locally. |
|||
|
|||
 |
|||
|
|||
Next, check the Docker Hub repository to confirm that the image has been pushed successfully. |
|||
|
|||
 |
|||
|
|||
### Deploying to Azure Container Apps |
|||
|
|||
Now that you have Docker images for your ABP Framework MVC project, you can proceed to deploy it to Azure Container Apps. To do this, navigate to the Azure portal and create a new Azure Container Apps resource. Ypu will not need just an Azure Container Apps resource, but also an Azure Container Apps Job resource to migrate the database schema and seed data for your ABP Framework MVC project. |
|||
|
|||
 |
|||
|
|||
#### Step 1: Deploy the Docker Image |
|||
|
|||
Firstly, create a new Azure Container Apps resource without environment variables. You will need web url so that you can set it as an environment variable in the next step. Then, check the deployment status to confirm that the deployment was successful. |
|||
|
|||
 |
|||
|
|||
#### Step 2: Migrate Database Schema and Seed Data |
|||
|
|||
Secondly, create a new Azure Container Apps Job resource to migrate the database schema and seed data. You can do this by creating a new job with the following environment variables: |
|||
|
|||
```text |
|||
ConnectionStrings__Default - Server=tcp:demoabpapp.database.windows.net,1433;Initial Catalog=mvcapppro;Persist Security Info=False;User ID=demoapppro;Password={your_password};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30; |
|||
|
|||
OpenIddict__Applications__mvcapppro_Web__RootUrl - https://mvcwebapp.victoriousgrass-8b06438d.northeurope.azurecontainerapps.io |
|||
``` |
|||
|
|||
To get ConnectionStrings of Sql Database and url of the web app, you can navigate to the Azure portal and check the properties of the Azure SQL database and Azure Container Apps resource. |
|||
|
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
Finally, check the job status to confirm that the database migration and seeding were successful. You can connect to the Azure SQL database to verify that the schema and seed data have been applied. |
|||
|
|||
 |
|||
|
|||
#### Step 3: Edit the Azure Container Apps Resource |
|||
|
|||
After completing these steps, you have to edit the Azure Container Apps resource to add the required environment variables for your ABP Framework MVC project. You can do this by adding the following environment variables: |
|||
|
|||
```text |
|||
App__SelfUrl - https://mvcwebapp.victoriousgrass-8b06438d.northeurope.azurecontainerapps.io |
|||
|
|||
ASPNETCORE_URLS - http://+:80 |
|||
|
|||
AuthServer__Authority - https://mvcwebapp.victoriousgrass-8b06438d.northeurope.azurecontainerapps.io |
|||
|
|||
ConnectionStrings__Default - Server=tcp:demoabpapp.database.windows.net,1433;Initial Catalog=mvcapppro;Persist Security Info=False;User ID=demoapppro;Password={your_password};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30; |
|||
``` |
|||
|
|||
 |
|||
|
|||
#### Step 4: Create a New Deployment |
|||
|
|||
Once you have added the environment variables, save and create a new deployment to apply the changes. You can now access your ABP Framework MVC project running on Azure Container Apps by navigating to the URL provided in the environment variables. |
|||
|
|||
 |
|||
|
|||
You can see the Azure resources created for the ABP Framework MVC project deployment that includes the Azure Container Apps resource, Azure Container Apps Job resource, and Azure SQL database. |
|||
|
|||
 |
|||
|
|||
### Conclusion |
|||
|
|||
Azure Container Apps provides a simple and cost-effective way to deploy and scale your ABP Framework MVC projects without managing the underlying infrastructure. By following the steps outlined in this article, you can seamlessly deploy your ABP Framework MVC projects to Azure Container Apps and enjoy the benefits it offers. |
|||
|
|||
I hope you found this article helpful. If you have any questions or feedback, please feel free to leave a comment below. |
|||
|
After Width: | Height: | Size: 625 KiB |
|
After Width: | Height: | Size: 294 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 203 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 470 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 214 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 48 KiB |
@ -0,0 +1,115 @@ |
|||
# Using FluentValidation with ABP Framework |
|||
|
|||
## What is Validation? Why Do We Need to Validate? |
|||
Validation is checking whether data is valid or not. We can liken validations to the cell membrane of our application. Just as the cell does not want to let in harmful substances, we do not want to add erroneous data to the database, which is critical to our application. Validations allow data that follows the rules to reach the database. Data that does not comply with the rules will not access the database at all, and the operation will fail. |
|||
|
|||
 |
|||
|
|||
Validations make your application run more efficiently as they are not directly tied to the database. They should be implemented across various layers of the application, including the UI, backend, and database, to prevent malicious users from bypassing these checks. Fluent validation is frequently employed for creating backend validations. |
|||
|
|||
## What is Fluent Validation? |
|||
Fluent validation is a library for checking whether data is valid or not. Fluent validation can be applied to your code in a fluent and understandable way. |
|||
|
|||
## Why We Should Use Fluent Validation? |
|||
Fluent Validation allows you to define your validation rules in a clear and flexible way. This means you can comfortably handle complex validation scenarios in your code. This makes your development process much more manageable. The readability that Fluent Validation offers really makes things easier. Having a clear understanding of what your validation rules do is a huge advantage when working on your code. In short, your code is cleaner and clearer. Fluent Validation is also very functional in terms of testability. By defining your validation rules in separate classes, you can easily test and maintain these rules. Fluent Validation is a widely used validation library on the .NET platform. As such, it has become a common standard among .NET developers. This provides advantages in terms of community support and compatibility. So, using Fluent Validation simplifies your development process by making your code more understandable, manageable and testable. |
|||
|
|||
In this section, I will show you how to use FluentValidation library within an ABP-based application. Therefore, I assume that you have an ABP-based application that has already been created. If you haven't created an ABP-based application yet, please follow the [Getting Started documentation](https://docs.abp.io/en/abp/latest/Getting-Started-Create-Solution?UI=MVC&DB=EF&Tiered=No). |
|||
|
|||
Using Fluent validation with Abp is quite simple. Open a command line window in the folder of the project (.csproj file) and type the following command: |
|||
|
|||
```bash |
|||
abp add-package Volo.Abp.FluentValidation |
|||
``` |
|||
|
|||
If you have not created your abp project, review the [steps to create](https://docs.abp.io/en/abp/latest/Tutorials/Todo/Overall) it now. |
|||
|
|||
Create the `Product` entity as below: |
|||
|
|||
````csharp |
|||
public class Product: FullAuditedAggregateRoot<Guid> |
|||
{ |
|||
public string Name { get; set; } = string.Empty; |
|||
public decimal Price { get; set; } |
|||
public int Stock { get; set; } |
|||
public string? LicenseKey { get; set; } |
|||
} |
|||
```` |
|||
|
|||
You must have ProductCreateDto : |
|||
|
|||
````csharp |
|||
public class ProductCreateDto |
|||
{ |
|||
public string Name { get; set; } |
|||
public decimal Price { get; set; } |
|||
public int Stock { get; set; } |
|||
public string? LicenseKey { get; set; } |
|||
} |
|||
```` |
|||
Create the `ProductCreateDtoValidator` class in the **Products** folder in the `Application.Contracts` project: |
|||
|
|||
````csharp |
|||
public class ProductCreateDtoValidator :AbstractValidator<ProductCreateDto> |
|||
{ |
|||
public ProductCreateDtoValidator() |
|||
{ |
|||
RuleFor(p=>p.Name). |
|||
NotEmpty(). |
|||
WithMessage("Product name cannot be empty"); |
|||
|
|||
RuleFor(p=>p.Name). |
|||
MinimumLength(3). |
|||
MaximumLength(100). |
|||
WithMessage("Product name must be between 3 and 100 characters"); |
|||
|
|||
RuleFor(p => p.Stock). |
|||
GreaterThanOrEqualTo(0). |
|||
WithMessage("Product stock should not be negative"); |
|||
|
|||
RuleFor(p => p.Price). |
|||
GreaterThanOrEqualTo(0). |
|||
WithMessage("Product Price should not be negative"); |
|||
} |
|||
} |
|||
```` |
|||
The validator class you create should inherit from `AbstractValidator`. You should give `AbstractValidator` the class you want to validate generically. You have to create a constructor method. You must write the code in this constructor method. Fluent validation provides you with ready-made methods that you can use to write validations very easily. |
|||
|
|||
The **RuleFor** method allows you to write a validation rule. You must specify in the parameter for which property you want to write a validation rule. |
|||
|
|||
With the **NotEmpty** method you can specify that a property cannot be null. |
|||
|
|||
With **MinimumLength** and **MaximumLength** you can specify the minimum and maximum number of characters the property can take. |
|||
|
|||
With the **GreaterThanOrEqualTo** method you can specify that the value of the property must be greater than or equal to the value entered as a parameter. |
|||
|
|||
**WithMessage** method you can specify the message to be sent when the validation fails. |
|||
|
|||
You can add a method to write your own customized validation code. For example, let's write the code that requires the license key field to contain the word “key”. |
|||
|
|||
````csharp |
|||
private bool ContainsKey(string arg) |
|||
{ |
|||
return arg.IndexOf("key", StringComparison.OrdinalIgnoreCase) >= 0; |
|||
} |
|||
```` |
|||
|
|||
Add the code to the constructor to use this method: |
|||
|
|||
````csharp |
|||
RuleFor(p => p.LicenseKey). |
|||
Must(ContainsKey). |
|||
WithMessage("Product license key must contain the word 'key'"); |
|||
|
|||
```` |
|||
Try to add data that does not meet the validation rules |
|||
|
|||
 |
|||
|
|||
If one of the validation rules does not meet the rules, then the following error will be received for the custom rule that we defined: |
|||
|
|||
 |
|||
|
|||
For more information on fluent validation with abp framework, see the [documentation](https://docs.abp.io/en/abp/latest/FluentValidation) |
|||
|
|||
For more information on fluent validation, see the [documentation](https://docs.fluentvalidation.net/en/latest/) |
|||
|
|||
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 33 KiB |
@ -0,0 +1,134 @@ |
|||
# What is Object to Object Mapping? |
|||
|
|||
Mapping the properties of one object to the properties of another object is called **Object to Object Mapping**. Most of the time, you don't want to show the data you store in your database to end users as it is. Instead, you only return users the information they need for that operation and reduce the output size. |
|||
|
|||
For example, in database tables that contain relationships, we analyze the relationships and present meaningful data to users. Suppose we have a product and a category object, we keep a property called `categoryId` in the `Product` entity. However, it would be illogical to show the `categoryId ` property to users. Therefore, we can create a DTO (data transfer object) and show the **category name** to the end users, instead of the `categoryId` directly. |
|||
|
|||
DTOs are used to transfer data of objects from one object to another one. We often need to map our entities to DTOs and DTOs to entities. For example, consider the code below: |
|||
|
|||
````csharp |
|||
public virtual async Task<CustomerDto> CreateAsync(CustomerCreateDto input) |
|||
{ |
|||
|
|||
var customer = await _customerManager.CreateAsync( |
|||
input.BirthDay, input.MembershipDate, input.FirstName, input.LastName |
|||
); |
|||
CustomerDto customerDto = new CustomerDto |
|||
{ |
|||
Id = customer.Id, |
|||
FirstName = input.FirstName, |
|||
LastName = input.LastName, |
|||
// ...other |
|||
}; |
|||
|
|||
return customerDto; |
|||
} |
|||
|
|||
```` |
|||
As can be seen here, it's repetitive and tedious to manually map an object to another similar object. Also, it violates the [DRY principle](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself), makes your code more complicated and reduces readability. Instead of manually mapping objects, you can use the [AutoMapper](https://automapper.org/) library to automatically map two objects' properties: |
|||
|
|||
|
|||
````csharp |
|||
public virtual async Task<CustomerDto> CreateAsync(CustomerCreateDto input) |
|||
{ |
|||
|
|||
var customer = await _customerManager.CreateAsync( |
|||
input.BirthDay, input.MembershipDate, input.FirstName, input.LastName |
|||
); |
|||
|
|||
return ObjectMapper.Map<Customer, CustomerDto>(customer); |
|||
} |
|||
```` |
|||
The **ObjectMapper.Map** method allows you to convert your `Customer` entity to `CustomerDto`. `IObjectMapper` interface is a service, that comes from the **AutoMapper** library, so let's learn more about **AutoMapper** in the next section. |
|||
|
|||
# What is AutoMapper? |
|||
|
|||
Automapper is a .NET library that automates object-to-object mapping. ABP provides abstractions for object-to-object mapping and has an integration package to use [AutoMapper](http://automapper.org/) as the object mapper. |
|||
|
|||
Automapper is a library that transforms similar objects into each other. We can imagine Automapper as a machine that transforms an apple with a hat into an apple without a hat: |
|||
|
|||
 |
|||
|
|||
In this chapter, I will show you how to use the AutoMapper library in an ABP-based application. For this reason, I assume that you already have an ABP-based application created. If you have not yet created an ABP-based application, please follow the [Getting Started documentation](https://docs.abp.io/en/abp/latest/Getting-Started-Create-Solution?UI=MVC&DB=EF&Tiered=No). |
|||
|
|||
Create a domain entity similar to this one: |
|||
|
|||
````csharp |
|||
public class Customer : FullAuditedAggregateRoot<Guid> |
|||
{ |
|||
public string? FirstName { get; set; } |
|||
public string? LastName { get; set; } |
|||
public DateTime BirthDay { get; set; } |
|||
public DateTime MembershipDate { get; set; } |
|||
} |
|||
```` |
|||
`Customer` entity contains some properties (such as `FirstName`, `LastName`, ... and other audited properties coming from the base class - `DeleterId`, `IsDeleted`, `CreationTime` etc. -). Typically, you would not want to show/return all of these properties to end users, at that point, you can create a DTO class and only define the properties that you want to return to the end users. |
|||
|
|||
Let's create the `CustomerGetDto` class in the `*.Application.Contracts` project as follows: |
|||
|
|||
````csharp |
|||
public class CustomerGetDto |
|||
{ |
|||
public string? FirstName { get; set; } |
|||
public string? LastName { get; set; } |
|||
public DateTime BirthDay { get; set; } |
|||
} |
|||
```` |
|||
|
|||
After creating our entity and output DTO classes, now in the application service implementation, we can return the `CustomerGetDto` class, as a result of listing the customers. For that reason, we can write a code as follows: |
|||
|
|||
````csharp |
|||
public virtual async Task<PagedResultDto<CustomerGetDto>> GetListAsync(GetCustomersInput input) |
|||
{ |
|||
var totalCount = await _customerRepository.GetCountAsync(input.FilterText, input.FirstName, input.LastName, input.BirthDayMin, input.BirthDayMax, input.MembershipDateMin, input.MembershipDateMax); |
|||
var items = await _customerRepository.GetListAsync(input.FilterText, input.FirstName, input.LastName, input.BirthDayMin, input.BirthDayMax, input.MembershipDateMin, input.MembershipDateMax, input.Sorting, input.MaxResultCount, input.SkipCount); |
|||
|
|||
return new PagedResultDto<CustomerGetDto> |
|||
{ |
|||
TotalCount = totalCount, |
|||
Items = ObjectMapper.Map<List<Customer>, List<CustomerGetDto>>(items) |
|||
}; |
|||
} |
|||
```` |
|||
In this code, we first get the total number of our customers and all customers according to the specified filters, then we map `List<Customer>` to `List<CustomerGetDto>` using the `ObjectMapper.Map` method from the **ApplicationService** base class. This way we have full control over which properties are returned to the end users. |
|||
|
|||
After mapping the `Customer` entity to the `CustomerGetDto` class. Before running our application, we should define the mappings in the `*AutoMapperProfile` class in the `*.Application` project as follows: |
|||
|
|||
````csharp |
|||
public class YourApplicationAutoMapperProfile : Profile |
|||
{ |
|||
public YourApplicationAutoMapperProfile() |
|||
{ |
|||
CreateMap<Customer, CustomerGetDto>(); |
|||
} |
|||
} |
|||
```` |
|||
Finally, we can run our application and navigate to the **/swagger** endpoint to try our endpoint. When we send a request, we should get the result as follows: |
|||
|
|||
 |
|||
|
|||
## Advanced: Mapping Configurations |
|||
|
|||
In some scenarios, you may want to make some customizations when mapping two objects. For example, let's assume that you want to create the `CustomerGetDto` class as follows: |
|||
|
|||
````csharp |
|||
public class CustomerGetDto |
|||
{ |
|||
public string? FullName { get; set; } |
|||
public int Age { get; set; } |
|||
} |
|||
```` |
|||
AutoMapper can't map these properties automatically, because they do not exist in the source object, which is the `Customer` entity. Therefore, you need to specify the exception and update your `YourApplicationAutoMapperProfile` class as follows: |
|||
|
|||
````csharp |
|||
|
|||
CreateMap<Customer, CustomerGetDto>().ForMember(c=>c.FullName,opt=> opt.MapFrom(src => src.FirstName + " " + src.LastName)) |
|||
.ForMember(c=>c.Age, opt=> opt.MapFrom(src=> DateTime.UtcNow.Year -src.BirthDay.Year)); |
|||
|
|||
```` |
|||
This configuration concatenates and matches **FirstName** and **LastName** properties into the **FullName** property and subtracts **BirthDate** from today's year and assigns it to the customer's **Age**. |
|||
After these configurations, if you make a request to the relevant endpoint, the output will look like: |
|||
|
|||
 |
|||
|
|||
For more information on object-to-object mapping with [ABP Framework](https://abp.io/), see the [documentation](https://docs.abp.io/en/abp/latest/Object-To-Object-Mapping). |
|||
@ -0,0 +1,340 @@ |
|||
# Sentiment Analysis Within ABP-Based Application |
|||
|
|||
In this article, first I will briefly explain what sentiment analysis is and then show you how you can apply sentiment analysis in an ABP-Based application (or any kind of .NET application). |
|||
|
|||
We will use ML.NET Framework, which is an open-source machine learning framework created by the dotnet team and also we will create a layered ABP Solution by using the application template and finally we will use CMS Kit's Comment Feature and extend its behavior by adding spam detection while creating or updating a comment, at that point we will make sentiment analysis. |
|||
|
|||
## Sentiment Analysis |
|||
|
|||
[Sentiment Analysis (or opinion mining)](https://en.wikipedia.org/wiki/Sentiment_analysis) refers to determining the emotion from the given input. The primary goal of sentiment analysis is to identify, extract, and categorize (positive, negative, or neutral) the sentiments expressed in textual data. |
|||
|
|||
To understand it better, let's check the following figure and examine the comments: |
|||
|
|||
 |
|||
|
|||
* If you look at these comments, you will notice that comments have ratings and it's easy to understand the emotion or thoughts of the users who commented about the related product. |
|||
* But even if there was not any rating specified for the given comments we still can get the emotion of the users. Because, as you can see, the comments specified some obvious words that express emotions, for example, in the first comment, the user says **he/she liked the product**, **it's easy to use** and **its battery is good**, and therefore this is obviously a positive comment. |
|||
* On the other hand, if we look at the second comment, we will notice some negative statements such as **useless phone**, **cannot maintain any data connection** and the user suggests **do not buy this phone**. Actually, this is what sentiment analysis is all about, abstracting the emotion from a given input, it's comment in that case but it can be any kind of input or input-group. |
|||
|
|||
## Demo: Spam Detection (Applying Sentiment Analysis) |
|||
|
|||
> You can get the source code of the demo from [https://github.com/EngincanV/SentimentAnalysisDemo](https://github.com/EngincanV/SentimentAnalysisDemo). |
|||
|
|||
In this demo application, we will create an [ABP-based application](https://docs.abp.io/en/abp/8.1/Startup-Templates/Application) and integrate the [ABP's CMS Kit Module's Comment Feature](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit/Comments), which provides a comment system to add a comment to any kind of resource, such as blog posts or products. |
|||
|
|||
By default, CMS Kit's Comment Feature does not provide spam detection and therefore in this sample application, we will add [spam detection](https://github.com/EngincanV/SentimentAnalysisDemo/blob/master/src/SentimentAnalysisDemo.Application/ML/SpamDetector.cs) while creating or updating a comment. Thus, whenever a comment is being added or updated, the spam detection service will validate the comment and reject it if it contains spam content otherwise it will make the other validations and save the comment: |
|||
|
|||
 |
|||
|
|||
To get started, we will first create an application, and add the CMS Kit Module to the solution and then we will enable the Comment Feature of the CMS Kit Module, and finally, we will add the Comment Component to the homepage and add spam detection by extending the behavior. Let's start with creating the application! |
|||
|
|||
### Creating an ABP-Based Application |
|||
|
|||
You can use the following command to create a layered ABP solution (with MongoDB as the database option): |
|||
|
|||
```bash |
|||
abp new SentimentAnalysisDemo -t app -d mongodb --version 8.1.1 |
|||
``` |
|||
|
|||
### Installing the CMS Kit Module |
|||
|
|||
After creating the project, we can add the CMS Kit module to our project. [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) provides the `add-module` command to install a specific module to a solution. |
|||
|
|||
You can use the following command to install the CMS Kit module into your application (run this command in the solution directory): |
|||
|
|||
```bash |
|||
abp add-module Volo.CmsKit --skip-db-migrations |
|||
``` |
|||
|
|||
After this command is executed, all related CMS Kit packages will be added to the correct layers and then you can enable any CMS Kit feature you want. |
|||
|
|||
### Enabling the Comment Feature |
|||
|
|||
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can either enable all of them or enable them one by one. In our demo application, we only need the **Comment Feature**, therefore we can only enable it. |
|||
|
|||
To enable the Comment Feature, you can open the `SentimentAnalysisDemoGlobalFeatureConfigurator` class (under the `*.Domain.Shared` project) and update it as follows: |
|||
|
|||
```csharp |
|||
using Volo.Abp.GlobalFeatures; |
|||
using Volo.Abp.Threading; |
|||
|
|||
namespace SentimentAnalysisDemo; |
|||
|
|||
public static class SentimentAnalysisDemoGlobalFeatureConfigurator |
|||
{ |
|||
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); |
|||
|
|||
public static void Configure() |
|||
{ |
|||
OneTimeRunner.Run(() => |
|||
{ |
|||
GlobalFeatureManager.Instance.Modules.CmsKit(cmsKit => |
|||
{ |
|||
cmsKit.Comments.Enable(); |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
After enabling the feature, now we can make the final configurations and directly use it in our application. |
|||
|
|||
### Configurations for Comment Feature |
|||
|
|||
Open the `SentimentAnalysisDemoDomainModule` class and add the following code-block into the `ConfigureServices` method: |
|||
|
|||
```csharp |
|||
Configure<CmsKitCommentOptions>(options => |
|||
{ |
|||
options.EntityTypes.Add(new CommentEntityTypeDefinition("Comment")); |
|||
options.IsRecaptchaEnabled = true; |
|||
}); |
|||
``` |
|||
|
|||
Here, we simply defining what should be the entity-type name of our comment and also enable the reCaptcha for the comment system. After this configuration, now we can open the `Index.cshtml` file in the `*.Web` project and invoke the `CommentingViewComponent` as below: |
|||
|
|||
```html |
|||
@page |
|||
@using Microsoft.AspNetCore.Mvc.Localization |
|||
@using SentimentAnalysisDemo.Localization |
|||
@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Commenting |
|||
@model SentimentAnalysisDemo.Web.Pages.IndexModel |
|||
|
|||
<div class="container"> |
|||
<h5 class="display-5">Comments:</h5> |
|||
|
|||
@await Component.InvokeAsync(typeof(CommentingViewComponent), new |
|||
{ |
|||
entityType = "Comment", |
|||
entityId = "SentimentAnalysisDemo", |
|||
isReadOnly = false |
|||
}) |
|||
</div> |
|||
|
|||
``` |
|||
|
|||
After adding the related component, now you can run the web project and see the comment component if you want. |
|||
|
|||
### Applying Sentiment Analysis (Creating the Spam Detection Service) |
|||
|
|||
By default, CMS Kit's Comment Feature does not provide a spam detection system. In this demo application, we will override the `CommentPublicAppService`'s `CreateAsync` and `UpdateAsync` methods and then will add the spam detection control whenever a new comment has been submitted or an existing one is being updated. |
|||
|
|||
To override the `CommentPublicAppService` and extend its use-case implementations, create a `MyCommentAppService` class and update its content as below: |
|||
|
|||
```csharp |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Options; |
|||
using SentimentAnalysisDemo.ML; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
using Volo.CmsKit.Comments; |
|||
using Volo.CmsKit.Public.Comments; |
|||
using Volo.CmsKit.Users; |
|||
|
|||
namespace SentimentAnalysisDemo.Volo.CmsKit.Public.Comments; |
|||
|
|||
[Dependency(ReplaceServices = true)] |
|||
[ExposeServices(typeof(ICommentPublicAppService), typeof(CommentPublicAppService), typeof(MyCommentAppService))] |
|||
public class MyCommentAppService : CommentPublicAppService |
|||
{ |
|||
protected ISpamDetector SpamDetector { get; } |
|||
|
|||
public MyCommentAppService( |
|||
ICommentRepository commentRepository, |
|||
ICmsUserLookupService cmsUserLookupService, |
|||
IDistributedEventBus distributedEventBus, |
|||
CommentManager commentManager, |
|||
IOptionsSnapshot<CmsKitCommentOptions> cmsCommentOptions, |
|||
ISpamDetector spamDetector |
|||
) |
|||
: base(commentRepository, cmsUserLookupService, distributedEventBus, commentManager, cmsCommentOptions) |
|||
{ |
|||
SpamDetector = spamDetector; |
|||
} |
|||
|
|||
public override async Task<CommentDto> CreateAsync(string entityType, string entityId, CreateCommentInput input) |
|||
{ |
|||
//Check message: spam or ham. |
|||
await SpamDetector.CheckAsync(input.Text); |
|||
|
|||
return await base.CreateAsync(entityType, entityId, input); |
|||
} |
|||
|
|||
public override async Task<CommentDto> UpdateAsync(Guid id, UpdateCommentInput input) |
|||
{ |
|||
//Check message: spam or ham. |
|||
await SpamDetector.CheckAsync(input.Text); |
|||
|
|||
return await base.UpdateAsync(id, input); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
Here, we simply just inject the `ISpamDetector` service, which we will create in a minute, and use its `CheckAsync` method to make a spam check before the comment is created or updated. |
|||
|
|||
Now, we can create the `ISpamDetector` service in the `*.Application.Contracts` project as follows: |
|||
|
|||
```csharp |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace SentimentAnalysisDemo.ML; |
|||
|
|||
public interface ISpamDetector |
|||
{ |
|||
Task CheckAsync(string text); |
|||
} |
|||
``` |
|||
|
|||
Then, we can create the `SpamDetector` and implement the `ISpamDetector` interface (in the `*.Application` project): |
|||
|
|||
```csharp |
|||
using System; |
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.ML; |
|||
using SentimentAnalysisDemo.ML.Model; |
|||
using Volo.Abp; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace SentimentAnalysisDemo.ML; |
|||
|
|||
public class SpamDetector : ISpamDetector, ITransientDependency |
|||
{ |
|||
public async Task CheckAsync(string text) |
|||
{ |
|||
//check if the text contains a spam content or not... |
|||
|
|||
} |
|||
} |
|||
``` |
|||
|
|||
The `CheckAsync` method is where we need to make the sentiment analysis and detect if the comment contains spam content or not. If it's spam, then we should throw a [UserFriendlyException](https://docs.abp.io/en/abp/latest/Exception-Handling#user-friendly-exception) and notify the user that the comment should be updated and should not contain any spam content. |
|||
|
|||
#### Spam Detection |
|||
|
|||
Before, making the spam check, we should have a dataset to train a machine-learning model and add `Microsoft.ML` package into our project. For that purpose, I searched in [Kaggle](https://www.kaggle.com/) for spam datasets, found the **Spam-Mail-Detection-Dataset** from Kaggle, and downloaded the csv file to use in my application. Therefore, [you should also download the dataset from the link and put it under the **/ML/Data/spam_data.csv** directory of the `*.Web` project](https://github.com/EngincanV/SentimentAnalysisDemo/blob/master/src/SentimentAnalysisDemo.Web/ML/Data/spam_data.csv). |
|||
|
|||
Here is what our dataset looks like (**0 -> not spam / 1 -> spam**): |
|||
|
|||
| Category | Message | |
|||
|----------|---------| |
|||
| 0 | Is that seriously how you spell his name? | |
|||
| 1 | Free entry in 2 a wkly comp to win FA Cup final tkts 21st May 2005. Text FA to 87121 to receive entry question(std txt rate)T&C's apply 08452810075over18's | |
|||
| . | . | |
|||
| . | . | |
|||
| . | . | |
|||
|
|||
> **Note:** This dataset is not ready-to use in a real-world solution. It's for mail spam detection but for the simplicity of the sample, it's not important and can be used for development purposes. |
|||
|
|||
After, downloading the dataset and putting it in the directory of **/ML/Data**, now we can add the `Microsoft.ML` package into our `*.Application` project: |
|||
|
|||
```bash |
|||
dotnet add package Microsoft.ML |
|||
``` |
|||
|
|||
Finally, we can implement the `CheckAsync` method and use sentiment analysis to make spam checks as follows: |
|||
|
|||
```csharp |
|||
|
|||
public async Task CheckAsync(string text) |
|||
{ |
|||
var dataPath = Path.Combine(Environment.CurrentDirectory, "ML", "Data", "spam_data.csv"); |
|||
|
|||
var mlContext = new MLContext(); |
|||
|
|||
//Step 1: Load Data 👇 |
|||
IDataView dataView = mlContext.Data.LoadFromTextFile<SentimentAnalyzeInput>(dataPath, hasHeader: true, separatorChar: ','); |
|||
|
|||
//Step 2: Split data to train-test data 👇 |
|||
DataOperationsCatalog.TrainTestData trainTestSplit = mlContext.Data.TrainTestSplit(dataView, testFraction: 0.2); |
|||
IDataView trainingData = trainTestSplit.TrainSet; //80% of the data. |
|||
IDataView testData = trainTestSplit.TestSet; //20% of the data. |
|||
|
|||
//Step 3: Common data process configuration with pipeline data transformations + choose and set the training algorithm 👇 |
|||
var estimator = mlContext.Transforms.Text.FeaturizeText(outputColumnName: "Features", inputColumnName: nameof(SentimentAnalyzeInput.Message)) |
|||
.Append(mlContext.BinaryClassification.Trainers.SdcaLogisticRegression(labelColumnName: "Label", featureColumnName: "Features")); |
|||
|
|||
//Step 4: Train the model 👇 |
|||
ITransformer model = estimator.Fit(trainingData); |
|||
|
|||
//Step 5: Predict 👇 |
|||
var sentimentAnalyzeInput = new SentimentAnalyzeInput |
|||
{ |
|||
Message = text |
|||
}; |
|||
|
|||
var predictionEngine = mlContext.Model.CreatePredictionEngine<SentimentAnalyzeInput, SentimentAnalyzeResult>(model); |
|||
var result = predictionEngine.Predict(sentimentAnalyzeInput); |
|||
if (IsSpam(result)) |
|||
{ |
|||
throw new UserFriendlyException("Spam detected! Please update the message!"); |
|||
} |
|||
} |
|||
|
|||
private static bool IsSpam(SentimentAnalyzeResult result) |
|||
{ |
|||
//1 -> spam / 0 -> ham (for 'Prediction' column) |
|||
return result is { Prediction: true, Probability: >= 0.5f }; |
|||
} |
|||
|
|||
``` |
|||
|
|||
Here, we have done the following things: |
|||
|
|||
1. **First, we loaded the data**: For that reason, we created a `MLContext` object, which is a main class for all ML.NET operations. Then, we used its `LoadFromTextFile` method and specified the dataset path in our application. Also, we mapped the dataset columns to the `SentimentAnalyzeInput` class, which we will create later on. |
|||
2. **For the second step, we split the data as training and testing data**: To be able to train a machine learning model and then evaluate its accuracy, we should not use all the data for training purposes, instead, we should split the data as training and testing data and after training the model, compare the training data accuracy with the testing data. |
|||
3. **For the third step, we should make data transformation, convert the text-based data into numeric vectors and then choose a training algorithm**: After splitting the data for training and testing purposes, now we can apply some data transformations for the *Message* column in our dataset. Because, as you would see, messages are text-based inputs and machine-learning algorithms work best with the numeric vectors. So, we are making data transformations and representing the data as numeric values. Then, we can apply `BinaryClassification` with the **SdcaLogicticRegression** algorithm to our training data. |
|||
4. **Train the model**: Since we make the data transformations and chose the correct algorithm for our model, now we can train the model. |
|||
5. **Predict the sample data**: Finally, we can pass a comment to this method and make spam check and either approve our reject the comment according to the predicted result. (To make predictions, we need to create a **PredictionEngine** and get the final results in the output class that we specified, `SentimentAnalyzeResult` in our example) |
|||
|
|||
Let's create the `SentimentAnalyzeInput` and `SentimentAnalyzeResult` classes as follows. |
|||
|
|||
**SentimentAnalyzeInput.cs:** |
|||
|
|||
```csharp |
|||
using Microsoft.ML.Data; |
|||
|
|||
namespace SentimentAnalysisDemo.ML.Model; |
|||
|
|||
public class SentimentAnalyzeInput |
|||
{ |
|||
[LoadColumn(0), ColumnName("Label")] |
|||
public bool Category { get; set; } |
|||
|
|||
[LoadColumn(1), ColumnName("Message")] |
|||
public string Message { get; set; } |
|||
} |
|||
``` |
|||
|
|||
**SentimentAnalyzeResult.cs:** |
|||
|
|||
```csharp |
|||
using Microsoft.ML.Data; |
|||
|
|||
namespace SentimentAnalysisDemo.ML.Model; |
|||
|
|||
public class SentimentAnalyzeResult |
|||
{ |
|||
[ColumnName("PredictedLabel")] |
|||
public bool Prediction { get; set; } |
|||
|
|||
public float Probability { get; set; } |
|||
|
|||
public float Score { get; set; } |
|||
} |
|||
``` |
|||
|
|||
Then, finally, we can run the application to see the final results: |
|||
|
|||
 |
|||
|
|||
## Advanced: Reusing And Optimizing Machine Learning Models |
|||
|
|||
Once the model is trained and evaluated, we can save the trained model and use it directly for further use. In this way, you don’t have to retrain the model every time when you want to make predictions. It’s essential to save the trained model for future use and a must for the production-ready code. I created a separate article dedicated to that topic, and if you are interested, you can read it from [here](https://engincanv.github.io/machine-learning/sentiment-analysis/best-practises/2024/05/16/reusing-and-optimizing-machine-learning-models-in-dotnet.html). |
|||
|
|||
## Conclusion |
|||
|
|||
In this article, I briefly explain what sentiment analysis is, created a sample ABP-based application, integrated the CMS Kit Module and finally, applied sentiment analysis to make spam checks whenever a new comment has been submitted or updated. You can get the source code of the demo from [https://github.com/EngincanV/SentimentAnalysisDemo](https://github.com/EngincanV/SentimentAnalysisDemo) |
|||
|
|||
Thanks for reading :) |
|||
|
After Width: | Height: | Size: 348 KiB |
|
After Width: | Height: | Size: 774 KiB |