|
After Width: | Height: | Size: 4.8 MiB |
|
After Width: | Height: | Size: 377 KiB |
|
After Width: | Height: | Size: 190 KiB |
|
After Width: | Height: | Size: 153 KiB |
|
After Width: | Height: | Size: 1.7 MiB |
@ -0,0 +1,83 @@ |
|||
# 🚀 Best Practices for Azure DevOps CI/CD Pipelines |
|||
|
|||
**CI/CD (Continuous Integration / Continuous Delivery)** is not just fancy tech talk - it's now a must-have for modern software teams. |
|||
Microsoft's **Azure DevOps** helps make these processes easier to manage. |
|||
But how do you create pipelines that work well for your team? Let's look at some practical tips that will make your life easier. |
|||
|
|||
--- |
|||
|
|||
## 1. 📜 Define Your Pipeline as Code |
|||
|
|||
Don't use the manual setup method that's hard to track. Azure DevOps lets you use **YAML files** for your pipelines, which gives you: |
|||
|
|||
- A record of all changes - who made them and when |
|||
- The same setup across all environments |
|||
- The ability to undo changes when something goes wrong |
|||
|
|||
This stops the common problem where something works on one computer but not another. |
|||
|
|||
 |
|||
|
|||
--- |
|||
|
|||
## 2. 🔑 Store Sensitive Information Safely |
|||
|
|||
Never put passwords directly in your code, even temporarily. |
|||
Each environment should have its own settings, and keep sensitive information in **Azure Key Vault** or **Library Variable Groups**. |
|||
|
|||
You'll avoid security problems later. |
|||
|
|||
<!--  --> |
|||
--- |
|||
|
|||
## 3. 🏗️ Keep Building and Releasing Separate |
|||
|
|||
Think of **Building** like cooking a meal - you prepare everything and package it up. |
|||
**Releasing** is like delivering that meal to different people. |
|||
|
|||
Keeping these as separate steps means: |
|||
|
|||
- You create your package once, then send it to multiple places |
|||
- You save time and resources by not rebuilding the same thing over and over |
|||
|
|||
 |
|||
|
|||
--- |
|||
|
|||
## 4. 🧪 Add Automatic Testing |
|||
|
|||
Don't waste time testing the same things manually over and over. |
|||
Set up **different types of tests** to run automatically. When tests run every time you make changes: |
|||
|
|||
- You catch problems before your customers do |
|||
- Your software quality stays high without extra manual work |
|||
|
|||
Azure DevOps has tools to help you see test results easily without searching through technical logs. |
|||
|
|||
--- |
|||
|
|||
## 5. 🛡️ Add Safety Checks |
|||
|
|||
Automatic doesn't mean pushing everything to your live system right away. |
|||
For important environments, add **human approval steps** or **automatic checks** like security scans. |
|||
|
|||
This helps you avoid emergency problems in the middle of the night. |
|||
|
|||
 |
|||
|
|||
|
|||
--- |
|||
|
|||
## ✅ Conclusion |
|||
|
|||
Good Azure DevOps pipelines aren't just about automation - they help you feel confident in your process. |
|||
Remember these main points: |
|||
|
|||
✔ Use YAML files to keep everything visible and trackable |
|||
✔ Keep passwords and sensitive data in secure storage (not in your code) |
|||
✔ Build once, deploy to many places |
|||
✔ Let automatic tests find problems before users do |
|||
✔ Add safety checks for important systems |
|||
|
|||
 |
|||
--- |
|||
@ -0,0 +1,398 @@ |
|||
# ABP Now Supports Angular Standalone Applications |
|||
|
|||
We are excited to announce that **ABP now supports Angular’s standalone component structure** in the latest Studio update. This article walks you through how to generate a standalone application, outlines the migration steps, and highlights the benefits of this shift over traditional module-based architecture. |
|||
|
|||
--- |
|||
|
|||
## Why Standalone? |
|||
|
|||
Angular's standalone component architecture, which is introduced in version 14 and made default in version 19, is a major leap forward for Angular development. Here is why it matters: |
|||
|
|||
### 🔧 Simplified Project Structure |
|||
|
|||
Standalone components eliminate the need for `NgModule` wrappers. This leads to: |
|||
|
|||
- Fewer files to manage |
|||
- Cleaner folder organization |
|||
- Reduced boilerplate |
|||
|
|||
Navigating and understanding your codebase becomes easier for everyone on your team. |
|||
|
|||
### 🚀 Faster Bootstrapping |
|||
|
|||
Standalone apps simplify app initialization: |
|||
|
|||
```ts |
|||
bootstrapApplication(AppComponent, appConfig); |
|||
``` |
|||
|
|||
This avoids the need for `AppModule` and speeds up startup times. |
|||
|
|||
### 📦 Smaller Bundle Sizes |
|||
|
|||
Since components declare their own dependencies, Angular can more effectively tree-shake unused code. Result? Smaller bundle sizes and faster load times. |
|||
|
|||
### 🧪 Easier Testing & Reusability |
|||
|
|||
Standalone components are self-contained. They declare their dependencies within the `imports` array, making them: |
|||
|
|||
- Easier to test in isolation |
|||
- Easier to reuse in different contexts |
|||
|
|||
### 🧠 Clearer Dependency Management |
|||
|
|||
Standalone components explicitly define what they need. No more hidden dependencies buried in shared modules. |
|||
|
|||
### 🔄 Gradual Adoption |
|||
|
|||
You can mix and match standalone and module-based components. This allows for **incremental migration**, reducing risk in larger codebases. Here is the related document for the [standalone migration](https://angular.dev/reference/migrations/standalone). |
|||
|
|||
--- |
|||
|
|||
## Getting Started: Creating a Standalone Angular App |
|||
|
|||
Angular CLI makes it easy to start: |
|||
|
|||
```bash |
|||
ng new my-app |
|||
``` |
|||
|
|||
With Angular 19, new apps follow this bootstrapping model: |
|||
|
|||
```ts |
|||
// main.ts |
|||
import { bootstrapApplication } from "@angular/platform-browser"; |
|||
import { appConfig } from "./app/app.config"; |
|||
import { AppComponent } from "./app/app.component"; |
|||
|
|||
bootstrapApplication(AppComponent, appConfig).catch((err) => |
|||
console.error(err) |
|||
); |
|||
``` |
|||
|
|||
The `app.config.ts` file replaces `AppModule`: |
|||
|
|||
```ts |
|||
// app.config.ts |
|||
import { ApplicationConfig, provideZoneChangeDetection } from "@angular/core"; |
|||
import { provideRouter } from "@angular/router"; |
|||
import { routes } from "./app.routes"; |
|||
|
|||
export const appConfig: ApplicationConfig = { |
|||
providers: [ |
|||
provideZoneChangeDetection({ eventCoalescing: true }), |
|||
provideRouter(routes), |
|||
], |
|||
}; |
|||
``` |
|||
|
|||
Routing is defined in a simple `Routes` array: |
|||
|
|||
```ts |
|||
// app.routes.ts |
|||
import { Routes } from "@angular/router"; |
|||
|
|||
export const routes: Routes = []; |
|||
``` |
|||
|
|||
--- |
|||
|
|||
## ABP Studio Support for Standalone Structure |
|||
|
|||
Starting with the latest release (insert version number here), ABP Studio fully supports Angular's standalone structure. While the new format is encouraged, module-based structure will continue to be supported for backwards compatibility. |
|||
|
|||
To try it out, simply update your ABP Studio to create apps with the latest version. |
|||
|
|||
--- |
|||
|
|||
## What’s New in ABP Studio Templates? |
|||
|
|||
When you generate an app using the latest ABP Studio, the project structure aligns with Angular's standalone architecture. |
|||
|
|||
This migration is split into four parts: |
|||
|
|||
1. **Package updates** |
|||
2. **Schematics updates** |
|||
3. **Suite code generation updates** |
|||
4. **Template refactors** |
|||
|
|||
--- |
|||
|
|||
## Package Migration Details |
|||
|
|||
Migration has been applied to packages in the [ABP GitHub repository](https://github.com/abpframework/abp/tree/dev/npm/ng-packs/packages). Here is an example from the Identity package. |
|||
|
|||
### 🧩 Migrating Components |
|||
|
|||
Components are made standalone, using: |
|||
|
|||
```bash |
|||
ng g @angular/core:standalone |
|||
``` |
|||
|
|||
Example: |
|||
|
|||
```ts |
|||
@Component({ |
|||
selector: 'abp-roles', |
|||
templateUrl: './roles.component.html', |
|||
providers: [...], |
|||
imports: [ |
|||
ReactiveFormsModule, |
|||
LocalizationPipe, |
|||
... |
|||
], |
|||
}) |
|||
export class RolesComponent implements OnInit { ... } |
|||
``` |
|||
|
|||
### 🛣 Updating Routing |
|||
|
|||
Old lazy-loaded routes using `forLazy()`: |
|||
|
|||
```ts |
|||
{ |
|||
path: 'identity', |
|||
loadChildren: () => import('@abp/ng.identity').then(m => m.IdentityModule.forLazy({...})) |
|||
} |
|||
``` |
|||
|
|||
Now replaced with: |
|||
|
|||
```ts |
|||
{ |
|||
path: 'identity', |
|||
loadChildren: () => import('@abp/ng.identity').then(c => c.createRoutes({...})) |
|||
} |
|||
``` |
|||
|
|||
### 🧱 Replacing Module Declarations |
|||
|
|||
The old setup: |
|||
|
|||
```ts |
|||
// identity.module.ts |
|||
@NgModule({ |
|||
imports: [IdentityRoutingModule, RolesComponent, UsersComponent], |
|||
}) |
|||
export class IdentityModule {...} |
|||
``` |
|||
|
|||
```ts |
|||
//identity-routing.module |
|||
const routes: Routes = [...]; |
|||
@NgModule({ |
|||
imports: [RouterModule.forChild(routes)], |
|||
exports: [RouterModule], |
|||
}) |
|||
export class IdentityRoutingModule {} |
|||
``` |
|||
|
|||
New setup: |
|||
|
|||
```ts |
|||
// identity-routes.ts |
|||
export function provideIdentity(options: IdentityConfigOptions = {}): Provider[] { |
|||
return [...]; |
|||
} |
|||
export const createRoutes = (options: IdentityConfigOptions = {}): Routes => [ |
|||
{ |
|||
path: '', |
|||
component: RouterOutletComponent, |
|||
providers: provideIdentity(options), |
|||
children: [ |
|||
{ |
|||
path: 'roles', |
|||
component: ReplaceableRouteContainerComponent, |
|||
data: { |
|||
requiredPolicy: 'AbpIdentity.Roles', |
|||
replaceableComponent: { |
|||
key: eIdentityComponents.Roles, |
|||
defaultComponent: RolesComponent, |
|||
}, |
|||
}, |
|||
title: 'AbpIdentity::Roles', |
|||
}, |
|||
... |
|||
], |
|||
}, |
|||
]; |
|||
``` |
|||
|
|||
--- |
|||
|
|||
## ABP Schematics Migration Details |
|||
|
|||
You can reach details by checking [ABP Schematics codebase](https://github.com/abpframework/abp/tree/dev/npm/ng-packs/packages/schematics). |
|||
|
|||
### 📚 Library creation |
|||
|
|||
When you run the `abp create-lib` command, the prompter will ask you the `templateType`. It supports both module and standalone templates. |
|||
|
|||
```ts |
|||
"templateType": { |
|||
"type": "string", |
|||
"description": "Type of the template", |
|||
"enum": ["module", "standalone"], |
|||
"x-prompt": { |
|||
"message": "Select the type of template to generate:", |
|||
"type": "list", |
|||
"items": [ |
|||
{ "value": "module", "label": "Module Template" }, |
|||
{ "value": "standalone", "label": "Standalone Template" } |
|||
] |
|||
} |
|||
}, |
|||
``` |
|||
|
|||
--- |
|||
|
|||
## ABP Suite Code Generation Migration Details |
|||
|
|||
ABP Suite will also be supporting both structures. If you have a project that is generated with the previous versions, the Suite will detect the structure in that way and generate the related code accordingly. Conversely, here is what is changed for the standalone migration: |
|||
|
|||
**❌ Discarded module files** |
|||
|
|||
```ts |
|||
// entity-one.module.ts |
|||
@NgModule({ |
|||
declarations: [], |
|||
imports: [EntityOneComponent, EntityOneRoutingModule], |
|||
}) |
|||
export class EntityOneModule {} |
|||
``` |
|||
|
|||
```ts |
|||
// entity-one-routing.module.ts |
|||
export const routes: Routes = [ |
|||
{ |
|||
path: "", |
|||
component: EntityOneComponent, |
|||
canActivate: [authGuard, permissionGuard], |
|||
}, |
|||
]; |
|||
|
|||
@NgModule({ |
|||
imports: [RouterModule.forChild(routes)], |
|||
exports: [RouterModule], |
|||
}) |
|||
export class EntityOneRoutingModule {} |
|||
``` |
|||
|
|||
```ts |
|||
// app-routing.module.ts |
|||
{ |
|||
path: 'entity-ones', |
|||
loadChildren: () => |
|||
import('./entity-ones/entity-one/entity-one.module').then(m => m.EntityOneModule), |
|||
}, |
|||
``` |
|||
|
|||
**✅ Added routes configuration** |
|||
|
|||
```ts |
|||
// entity-one.routes.ts |
|||
export const ENTITY_ONE_ROUTES: Routes = [ |
|||
{ |
|||
path: "", |
|||
loadComponent: () => { |
|||
return import("./components/entity-one.component").then( |
|||
(c) => c.EntityOneComponent |
|||
); |
|||
}, |
|||
canActivate: [authGuard, permissionGuard], |
|||
}, |
|||
]; |
|||
``` |
|||
|
|||
```ts |
|||
// app.routes.ts |
|||
{ path: 'entity-ones', children: ENTITY_ONE_ROUTES }, |
|||
``` |
|||
|
|||
--- |
|||
|
|||
## Template Migration Details |
|||
|
|||
### 🧭 Routing: `app.routes.ts` |
|||
|
|||
```ts |
|||
// app.routes.ts |
|||
import { Routes } from '@angular/router'; |
|||
|
|||
export const APP_ROUTES: Routes = [ |
|||
{ |
|||
path: '', |
|||
pathMatch: 'full', |
|||
loadComponent: () => import('./home/home.component').then(m => m.HomeComponent), |
|||
}, |
|||
{ |
|||
path: 'account', |
|||
loadChildren: () => import('@abp/ng.account').then(m => m.createRoutes()), |
|||
}, |
|||
... |
|||
]; |
|||
``` |
|||
|
|||
### ⚙ Configuration: `app.config.ts` |
|||
|
|||
```ts |
|||
// app.config.ts |
|||
export const appConfig: ApplicationConfig = { |
|||
providers: [ |
|||
provideRouter(APP_ROUTES), |
|||
APP_ROUTE_PROVIDER, |
|||
provideAbpCore( |
|||
withOptions({ |
|||
environment, |
|||
registerLocaleFn: registerLocale(), |
|||
... |
|||
}) |
|||
), |
|||
provideAbpOAuth(), |
|||
provideAbpThemeShared(), |
|||
... |
|||
], |
|||
}; |
|||
|
|||
``` |
|||
|
|||
### 🧼 Removed: `shared.module.ts` |
|||
|
|||
This file has been removed to reduce unnecessary shared imports. Components now explicitly import what they need—leading to better encapsulation and less coupling. |
|||
|
|||
--- |
|||
|
|||
## Common Problems |
|||
|
|||
You may encounter these common problems that you would need to manage. |
|||
|
|||
### 1. Missing Imports |
|||
|
|||
In standalone structure, components must declare all their dependencies in `imports`. Forgetting this often causes template errors. |
|||
|
|||
### 2. Mixed Structures |
|||
|
|||
Combining modules and standalone in the same feature leads to confusion. Migrate features fully or keep them module-based. |
|||
|
|||
### 3. Routing Errors |
|||
|
|||
Incorrect migration from `forLazy()` to `createRoutes()` or `loadComponent` can break navigation. Double-check route configs. |
|||
|
|||
### 4. Service Injection |
|||
|
|||
Services provided in old modules may be missing. Add them in the component’s `providers` or `app.config.ts`. |
|||
|
|||
### 5. Shared Module Habit |
|||
|
|||
Reintroducing a shared module reduces the benefits of standalone. Import dependencies directly where needed. |
|||
|
|||
--- |
|||
|
|||
## Conclusion |
|||
|
|||
Angular’s standalone component architecture is a significant improvement for scalability, simplicity, and performance. With latest version of ABP Studio, you can adopt this modern approach with ease—without losing support for existing module-based projects. |
|||
|
|||
**Ready to modernize your Angular development?** |
|||
|
|||
Update your ABP Studio today and start building with standalone power! |
|||
@ -0,0 +1,213 @@ |
|||
# App Services vs Domain Services: Deep Dive into Two Core Service Types in ABP Framework |
|||
|
|||
In ABP's layered architecture, we frequently encounter two types of services that appear similar but serve distinctly different purposes: Application Services and Domain Services. Understanding the differences between them is crucial for building clear and maintainable enterprise applications. |
|||
|
|||
## Architectural Positioning |
|||
|
|||
In ABP's layered architecture: |
|||
|
|||
- **Application Services** reside in the application layer and are responsible for coordinating use case execution |
|||
- **Domain Services** reside in the domain layer and are responsible for implementing core business logic |
|||
|
|||
This layered design follows Domain-Driven Design (DDD) principles, ensuring clear separation of business logic and system maintainability. |
|||
|
|||
## Application Services: Use Case Orchestrators |
|||
|
|||
### Core Responsibilities |
|||
|
|||
Application Services are stateless services primarily used to implement application use cases. They act as a bridge between the presentation layer and domain layer, responsible for: |
|||
|
|||
- **Parameter Validation**: Input validation is automatically handled by ABP using data annotations |
|||
- **Authorization**: Checking user permissions and access control using `[Authorize]` attribute or manual authorization checks via `IAuthorizationService` |
|||
- **Transaction Management**: Methods automatically run as Unit of Work (transactional by default) |
|||
- **Use Case Orchestration**: Organizing and coordinating multiple domain objects to complete specific business use cases |
|||
- **Data Transformation**: Handling conversion between DTOs and domain objects using ObjectMapper |
|||
|
|||
### Design Principles |
|||
|
|||
1. **DTO Boundaries**: Application service methods should only accept and return DTOs, never directly expose domain entities |
|||
2. **Use Case Oriented**: Each method should correspond to a clear user use case |
|||
3. **Thin Layer Design**: Avoid implementing complex business logic in application services |
|||
|
|||
### Typical Execution Flow |
|||
|
|||
A standard application service method typically follows this pattern: |
|||
|
|||
```csharp |
|||
[Authorize(BookPermissions.Create)] // Declarative authorization |
|||
public virtual async Task<BookDto> CreateBookAsync(CreateBookDto input) // input is automatically validated |
|||
{ |
|||
// Get related data |
|||
var author = await _authorRepository.GetAsync(input.AuthorId); |
|||
|
|||
// Call domain service to execute business logic (if needed) |
|||
// You can also use the entity constructor directly if no complex business logic is required |
|||
var book = await _bookManager.CreateAsync(input.Title, author, input.Price); |
|||
|
|||
// Persist changes |
|||
await _bookRepository.InsertAsync(book); |
|||
|
|||
// Return DTO |
|||
return ObjectMapper.Map<Book, BookDto>(book); |
|||
} |
|||
``` |
|||
|
|||
### Integration Services: Special kind of Application Service |
|||
|
|||
It's worth mentioning that ABP also provides a special type of application service—Integration Services. They are application services marked with the `[IntegrationService]` attribute, designed for inter-module or inter-microservice communication. |
|||
|
|||
We have a community article dedicated to integration services: [Integration Services Explained — What they are, when to use them, and how they behave](https://abp.io/community/articles/integration-services-explained-what-they-are-when-to-use-lienmsy8) |
|||
|
|||
## Domain Services: Guardians of Business Logic |
|||
|
|||
### Core Responsibilities |
|||
|
|||
Domain Services implement core business logic and are particularly needed when: |
|||
|
|||
- **Core domain logic depends on services**: You need to implement logic that requires repositories or other external services |
|||
- **Logic spans multiple aggregates**: The business logic is related to more than one aggregate/entity and doesn't properly fit in any single aggregate |
|||
- **Complex business rules**: Complex domain rules that don't naturally belong in a single entity |
|||
|
|||
### Design Principles |
|||
|
|||
1. **Domain Object Interaction**: Method parameters and return values should be domain objects (entities, value objects), never DTOs |
|||
2. **Business Logic Focus**: Focus on implementing pure business rules |
|||
3. **Stateless Design**: Maintain the stateless nature of services |
|||
4. **State-Changing Operations Only**: Domain services should only define methods that mutate data, not query methods |
|||
5. **No Authorization Logic**: Domain services should not perform authorization checks or depend on current user context |
|||
6. **Specific Method Names**: Use descriptive, business-meaningful method names (e.g., `AssignToAsync`) instead of generic names (e.g., `UpdateAsync`) |
|||
|
|||
### Implementation Example |
|||
|
|||
```csharp |
|||
public class IssueManager : DomainService |
|||
{ |
|||
private readonly IRepository<Issue, Guid> _issueRepository; |
|||
|
|||
public virtual async Task AssignToAsync(Issue issue, Guid userId) |
|||
{ |
|||
// Business rule: Check user's unfinished task count |
|||
var openIssueCount = await _issueRepository.GetCountAsync(i => i.AssignedUserId == userId && !i.IsClosed); |
|||
|
|||
if (openIssueCount >= 3) |
|||
{ |
|||
throw new BusinessException("IssueTracking:ConcurrentOpenIssueLimit"); |
|||
} |
|||
|
|||
// Execute assignment logic |
|||
issue.AssignedUserId = userId; |
|||
issue.AssignedDate = Clock.Now; |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Key Differences Comparison |
|||
|
|||
| Dimension | Application Services | Domain Services | |
|||
|-----------|---------------------|-----------------| |
|||
| **Layer Position** | Application Layer | Domain Layer | |
|||
| **Primary Responsibility** | Use Case Orchestration | Business Logic Implementation | |
|||
| **Data Interaction** | DTOs | Domain Objects | |
|||
| **Callers** | Presentation Layer/Client Applications | Application Services/Other Domain Services | |
|||
| **Authorization** | Responsible for permission checks | No authorization logic | |
|||
| **Transaction Management** | Manages transaction boundaries (Unit of Work) | Participates in transactions but doesn't manage | |
|||
| **Current User Context** | Can access current user information | Should not depend on current user context | |
|||
| **Return Types** | Returns DTOs | Returns domain objects only | |
|||
| **Query Operations** | Can perform query operations | Should not define GET/query methods | |
|||
| **Naming Convention** | `*AppService` | `*Manager` or `*Service` | |
|||
|
|||
## Collaboration Patterns in Practice |
|||
|
|||
In real-world development, these two types of services typically work together: |
|||
|
|||
```csharp |
|||
// Application Service |
|||
public class BookAppService : ApplicationService |
|||
{ |
|||
private readonly BookManager _bookManager; |
|||
private readonly IRepository<Book> _bookRepository; |
|||
|
|||
[Authorize(BookPermissions.Update)] |
|||
public virtual async Task<BookDto> UpdatePriceAsync(Guid id, decimal newPrice) |
|||
{ |
|||
var book = await _bookRepository.GetAsync(id); |
|||
|
|||
await _bookManager.ChangePriceAsync(book, newPrice); |
|||
|
|||
await _bookRepository.UpdateAsync(book); |
|||
|
|||
return ObjectMapper.Map<Book, BookDto>(book); |
|||
} |
|||
} |
|||
|
|||
// Domain Service |
|||
public class BookManager : DomainService |
|||
{ |
|||
public virtual async Task ChangePriceAsync(Book book, decimal newPrice) |
|||
{ |
|||
// Domain service focuses on business rules |
|||
if (newPrice <= 0) |
|||
{ |
|||
throw new BusinessException("Book:InvalidPrice"); |
|||
} |
|||
|
|||
if (book.IsDiscounted && newPrice > book.OriginalPrice) |
|||
{ |
|||
throw new BusinessException("Book:DiscountedPriceCannotExceedOriginal"); |
|||
} |
|||
|
|||
if (book.Price == newPrice) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// Additional business logic: Check if price change requires approval |
|||
if (await RequiresApprovalAsync(book, newPrice)) |
|||
{ |
|||
throw new BusinessException("Book:PriceChangeRequiresApproval"); |
|||
} |
|||
|
|||
book.ChangePrice(newPrice); |
|||
} |
|||
|
|||
private Task<bool> RequiresApprovalAsync(Book book, decimal newPrice) |
|||
{ |
|||
// Example business rule: Large price increases require approval |
|||
var increasePercentage = ((newPrice - book.Price) / book.Price) * 100; |
|||
return Task.FromResult(increasePercentage > 50); // 50% increase threshold |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Best Practice Recommendations |
|||
|
|||
### Application Services |
|||
- Create a corresponding application service for each aggregate root |
|||
- Use clear naming conventions (e.g., `IBookAppService`) |
|||
- Implement standard CRUD operation methods (`GetAsync`, `CreateAsync`, `UpdateAsync`, `DeleteAsync`) |
|||
- Avoid inter-application service calls within the same module/application |
|||
- Always return DTOs, never expose domain entities directly |
|||
- Use the `[Authorize]` attribute for declarative authorization or manual checks via `IAuthorizationService` |
|||
- Methods automatically run as Unit of Work (transactional) |
|||
- Input validation is handled automatically by ABP |
|||
|
|||
### Domain Services |
|||
- Use the `Manager` suffix for naming (e.g., `BookManager`) |
|||
- Only define state-changing methods, avoid query methods (use repositories directly in Application Services for queries) |
|||
- Throw `BusinessException` with clear, unique error codes for domain validation failures |
|||
- Keep methods pure, avoid involving user context or authorization logic |
|||
- Accept and return domain objects only, never DTOs |
|||
- Use descriptive, business-meaningful method names (e.g., `AssignToAsync`, `ChangePriceAsync`) |
|||
- Do not implement interfaces unless there's a specific need for multiple implementations |
|||
|
|||
## Summary |
|||
|
|||
Application Services and Domain Services each have their distinct roles in the ABP framework: Application Services serve as use case orchestrators, handling authorization, validation, transaction management, and DTO transformations; Domain Services focus purely on business logic implementation without any infrastructure concerns. Integration Services are a special type of Application Service designed for inter-service communication. |
|||
|
|||
Correctly understanding and applying these service patterns is key to building high-quality ABP applications. Through clear separation of responsibilities, we can not only build more maintainable code but also flexibly switch between monolithic and microservice architectures—this is precisely the elegance of ABP framework design. |
|||
|
|||
## References |
|||
|
|||
- [Application Services](https://abp.io/docs/latest/framework/architecture/domain-driven-design/application-services) |
|||
- [Integration Services](https://abp.io/docs/latest/framework/api-development/integration-services) |
|||
- [Domain Services](https://abp.io/docs/latest/framework/architecture/domain-driven-design/domain-services) |
|||
|
After Width: | Height: | Size: 638 KiB |
@ -0,0 +1,317 @@ |
|||
# Best Free Alternatives to AutoMapper in .NET — Why We Moved to Mapperly |
|||
|
|||
--- |
|||
|
|||
## Introduction |
|||
|
|||
[AutoMapper](https://automapper.io/) has been one of the most popular mapping library for .NET apps. It has been free and [open-source](https://github.com/LuckyPennySoftware/AutoMapper) since 2009. On 16 April 2025, Jimmy Bogard (the owner of the project) decided to make it commercial for his own reasons. You can read [this announcement](https://www.jimmybogard.com/automapper-and-mediatr-licensing-update/) about what happened to AutoMapper. |
|||
|
|||
|
|||
|
|||
### Why AutoMapper’s licensing change matters |
|||
|
|||
In ABP Framework we have been also using AutoMapper for object mappings. After its commercial transition, we also needed to replace it. Because ABP Framework is open-source and under [LGPL-3.0 license](https://github.com/abpframework/abp#LGPL-3.0-1-ov-file). |
|||
|
|||
**TL;DR** |
|||
|
|||
> That's why, **we decided to replace AutoMapper with Mapperly**. |
|||
|
|||
In this article, we'll discuss the alternatives of AutoMapper so that you can cut down on costs and maximize performance while retaining control over your codebase. Also I'll explain why we chose Mapperly. |
|||
|
|||
Also AutoMapper uses heavily reflection. And reflection comes with a performance cost if used indiscriminately, and compile-time safety is limited. Let's see how we can overcome these... |
|||
|
|||
|
|||
|
|||
## Cost-Free Alternatives to AutoMapper |
|||
|
|||
Check out the comparison table for key features vs. AutoMapper. |
|||
|
|||
| | **AutoMapper (Paid)** | **Mapster (Free)** | **Mapperly (Free)** | **AgileMapper (Free)** | **Manual Mapping** | |
|||
| ------------------- | ----------------------------------------------- | ----------------------------------------- | -------------------------------------------- | ------------------------------------------- | ------------------------------------------------ | |
|||
| **License & Cost** | Paid/commercial | Free, MIT License | Free, MIT License | Free, Apache 2.0 | Free (no library) | |
|||
| **Performance** | Slower due to reflection & conventions | Very fast (runtime & compile-time modes) | Very fast (compile-time code generation) | Good, faster than AutoMapper | Fastest (direct assignment) | |
|||
| **Ease of Setup** | Easy, but configuration-heavy | Easy, minimal config | Easy, but different approach from AutoMapper | Simple, flexible configuration | Manual coding required | |
|||
| **Features** | Rich features, conventions, nested mappings | Strong typed mappings, projection support | Strong typed, compile-time safe mappings | Dynamic & conditional mapping | Whatever you code | |
|||
| **Maintainability** | Hidden mappings can be hard to debug | Explicit & predictable | Very explicit, compiler-verified mappings | Readable, good balance | Very explicit, most maintainable | |
|||
| **Best For** | Large teams used to AutoMapper & willing to pay | Teams wanting performance + free tool | Teams prioritizing type-safety & performance | Developers needing flexibility & simplicity | Small/medium projects, performance-critical apps | |
|||
|
|||
There are other libraries such as [**ExpressMapper**](https://github.com/fluentsprings/ExpressMapper) **(308K GitHub stars)**, [**ValueInjecter**](https://github.com/omuleanu/ValueInjecter) **(258K GitHub stars)**, [**AgileMapper**](https://github.com/agileobjects/AgileMapper) **(463K GitHub stars)**. These are not very popular but also free and offer a different balance of simplicity and features. |
|||
|
|||
|
|||
|
|||
## Why We Chose Mapperly |
|||
|
|||
We filtered down all the alternatives into 2: **Mapster** and **Mapperly**. |
|||
|
|||
The crucial factor was maintainability! As you see from the screenshots below, Mapster is already stopped development. Mapster’s development appears stalled, and its future maintenance is uncertain. On the other hand, Mapperly regularly gets commits. The community support is valuable. |
|||
|
|||
We looked up different alternatives of AutoMapper also, here's the initial issue of AutoMapper replacement [github.com/abpframework/abp/issues/23243](https://github.com/abpframework/abp/issues/23243). |
|||
|
|||
The ABP team started Mapperly integration with this initial commit [github.com/abpframework/abp/commit/178d3f56d42b4e5acb7e349470f4a644d4c5214e](https://github.com/abpframework/abp/commit/178d3f56d42b4e5acb7e349470f4a644d4c5214e). And this is our Mapperly integration package : [github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.Mapperly.](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.Mapperly.) |
|||
|
|||
 |
|||
|
|||
Here are some considerations for developers who are used to ABP and AutoMapper. |
|||
|
|||
### [Mapster](https://github.com/MapsterMapper/Mapster): |
|||
|
|||
* ✔ It is similar to AutoMapper, configuring mappings through code. |
|||
* ✔ Support for dependency injection and complex runtime configuration. |
|||
* ❌ It is looking additional Mapster maintainers ([Call for additional Mapster maintainers MapsterMapper/Mapster#752](https://github.com/MapsterMapper/Mapster/discussions/752)) |
|||
|
|||
### [Mapperly](https://github.com/riok/Mapperly): |
|||
|
|||
- ✔ It generates mapping code(` source generator`) during the build process. |
|||
- ✔ It is actively being developed and maintained. |
|||
- ❌ It is a static `map` method, which is not friendly to dependency injection. |
|||
- ❌ The configuration method is completely different from AutoMapper, and there is a learning curve. |
|||
|
|||
|
|||
|
|||
**Mapperly** → generates mapping code at **compile time** using source generators. |
|||
|
|||
**Mapster** → has two modes: |
|||
|
|||
- By default, it uses **runtime code generation** (via expression trees and compilation). |
|||
|
|||
- But with **Mapster.Tool** (source generator), it can also generate mappings at **compile time**. |
|||
|
|||
|
|||
|
|||
This is important because it guarantees the mappings are working well. Also they provide type safety and improved performance. Another advantages of these libraries, they eliminate runtime surprises and offer better IDE support. |
|||
|
|||
--- |
|||
|
|||
## When Mapperly Will Come To ABP |
|||
|
|||
Mapperly integration will be delivered with ABP v10. If you have already defined AutoMapper configurations, you can still keep and use them. But the framework will use Mapperly. So there'll be 2 mapping integrations in your app. You can also remove AutoMapper from your final application and use one mapping library: Mapperly. It's up to you! Check [AutoMapper pricing table](https://automapper.io/#pricing). |
|||
|
|||
|
|||
|
|||
## Migrating from AutoMapper to Mapperly |
|||
|
|||
In ABP v10, we will be migrating from AutoMapper to Mapperly. The document about the migration is not delivered by the time I wrote this article, but you can reach the document in our dev docs branch |
|||
|
|||
* [github.com/abpframework/abp/blob/dev/docs/en/release-info/migration-guides/AutoMapper-To-Mapperly.md](https://github.com/abpframework/abp/blob/dev/docs/en/release-info/migration-guides/AutoMapper-To-Mapperly.md). |
|||
|
|||
Also for ABP, you can check out how you will define DTO mappings based on Mapperly at this document |
|||
|
|||
* [github.com/abpframework/abp/blob/dev/docs/en/framework/infrastructure/object-to-object-mapping.md](https://github.com/abpframework/abp/blob/dev/docs/en/framework/infrastructure/object-to-object-mapping.md) |
|||
|
|||
|
|||
|
|||
## Mapping Code Examples for AutoMapper, Mapster, AgileMapper |
|||
|
|||
### AutoMapper vs Mapster vs Mapperly Performance |
|||
|
|||
Here are concise, drop-in **side-by-side C# snippets** that map the same model with AutoMapper, Mapster, AgileMapper, and manual mapping. |
|||
|
|||
Models used in all examples |
|||
|
|||
We'll use these models to show the mapping examples for AutoMapper, Mapster, AgileMapper. |
|||
|
|||
```csharp |
|||
public sealed class Order |
|||
{ |
|||
public int Id { get; init; } |
|||
public Customer Customer { get; init; } = default!; |
|||
public List<OrderLine> Lines { get; init; } = new(); |
|||
public DateTime CreatedAt { get; init; } |
|||
} |
|||
|
|||
public sealed class Customer |
|||
{ |
|||
public int Id { get; init; } |
|||
public string Name { get; init; } = ""; |
|||
public string? Email { get; init; } |
|||
} |
|||
|
|||
public sealed class OrderLine |
|||
{ |
|||
public int ProductId { get; init; } |
|||
public int Quantity { get; init; } |
|||
public decimal UnitPrice { get; init; } |
|||
} |
|||
|
|||
public sealed class OrderDto |
|||
{ |
|||
public int Id { get; init; } |
|||
public string CustomerName { get; init; } = ""; |
|||
public int ItemCount { get; init; } |
|||
public decimal Total { get; init; } |
|||
public string CreatedAtIso { get; init; } = ""; |
|||
} |
|||
``` |
|||
|
|||
|
|||
|
|||
#### AutoMapper Example (Paid) |
|||
|
|||
```csharp |
|||
public sealed class OrderProfile : Profile |
|||
{ |
|||
public OrderProfile() |
|||
{ |
|||
CreateMap<Order, OrderDto>() |
|||
.ForMember(d => d.CustomerName, m => m.MapFrom(s => s.Customer.Name)) |
|||
.ForMember(d => d.ItemCount, m => m.MapFrom(s => s.Lines.Sum(l => l.Quantity))) |
|||
.ForMember(d => d.Total, m => m.MapFrom(s => s.Lines.Sum(l => l.Quantity * l.UnitPrice))) |
|||
.ForMember(d => d.CreatedAtIso,m => m.MapFrom(s => s.CreatedAt.ToString("O"))); |
|||
} |
|||
} |
|||
|
|||
// registration |
|||
services.AddAutoMapper(typeof(OrderProfile)); |
|||
|
|||
// mapping |
|||
var dto = mapper.Map<OrderDto>(order); |
|||
|
|||
// EF Core projection (common pattern) |
|||
var list = dbContext.Orders |
|||
.ProjectTo<OrderDto>(mapper.ConfigurationProvider) |
|||
.ToList(); |
|||
``` |
|||
|
|||
**NuGet Packages:** |
|||
|
|||
- https://www.nuget.org/packages/AutoMapper |
|||
- https://www.nuget.org/packages/AutoMapper.Extensions.Microsoft.DependencyInjection |
|||
|
|||
--- |
|||
|
|||
#### Mapperly (Free, Apache-2.0) |
|||
|
|||
This is compile-time generated mapping. |
|||
|
|||
```csharp |
|||
[Mapper] // generates the implementation at build time |
|||
public partial class OrderMapper |
|||
{ |
|||
// Simple property mapping: Customer.Name -> CustomerName |
|||
[MapProperty(nameof(Order.Customer) + "." + nameof(Customer.Name), nameof(OrderDto.CustomerName))] |
|||
public partial OrderDto ToDto(Order s); |
|||
|
|||
// Update an existing target (like MapToExisting) |
|||
[MapProperty(nameof(Order.Customer) + "." + nameof(Customer.Name), nameof(OrderDto.CustomerName))] |
|||
public partial void UpdateDto(Order s, OrderDto target); |
|||
|
|||
// Post-process calculated fields (ItemCount, Total, CreatedAtIso) |
|||
// https://mapperly.riok.app/docs/configuration/before-after-map/ |
|||
[UserMapping(Default = true)] |
|||
private static void After(Order s, ref OrderDto d) |
|||
{ |
|||
d = d with |
|||
{ |
|||
ItemCount = s.Lines.Sum(l => l.Quantity), |
|||
Total = s.Lines.Sum(l => l.Quantity * l.UnitPrice), |
|||
CreatedAtIso = s.CreatedAt.ToString("O") |
|||
}; |
|||
} |
|||
} |
|||
|
|||
//USAGE |
|||
var mapper = new OrderMapper(); |
|||
var dto = mapper.ToDto(order); |
|||
|
|||
var target = new OrderDto(); |
|||
mapper.UpdateDto(order, target); |
|||
``` |
|||
|
|||
**NuGet Packages:** |
|||
|
|||
* https://www.nuget.org/packages/Riok.Mapperly/ |
|||
|
|||
--- |
|||
|
|||
#### Mapster Example (Free, MIT) |
|||
|
|||
```csharp |
|||
TypeAdapterConfig<Order, OrderDto>.NewConfig() |
|||
.Map(d => d.CustomerName, s => s.Customer.Name) |
|||
.Map(d => d.ItemCount, s => s.Lines.Sum(l => l.Quantity)) |
|||
.Map(d => d.Total, s => s.Lines.Sum(l => l.Quantity * l.UnitPrice)) |
|||
.Map(d => d.CreatedAtIso, s => s.CreatedAt.ToString("O")); |
|||
|
|||
// one-off |
|||
var dto = order.Adapt<OrderDto>(); |
|||
|
|||
// DI-friendly registration |
|||
services.AddSingleton(TypeAdapterConfig.GlobalSettings); |
|||
services.AddScoped<IMapper, ServiceMapper>(); |
|||
|
|||
// EF Core projection (strong suit) |
|||
var mappedList = dbContext.Orders |
|||
.ProjectToType<OrderDto>() // Mapster projection |
|||
.ToList(); |
|||
``` |
|||
|
|||
**NuGet Packages:** |
|||
|
|||
- https://www.nuget.org/packages/Mapster |
|||
- https://www.nuget.org/packages/Mapster.DependencyInjection |
|||
- https://www.nuget.org/packages/Mapster.SourceGenerator (for performance improvement) |
|||
|
|||
--- |
|||
|
|||
#### AgileMapper Example (Free, Apache-2.0) |
|||
|
|||
```csharp |
|||
var mapper = Mapper.CreateNew(cfg => |
|||
{ |
|||
cfg.WhenMapping |
|||
.From<Order>() |
|||
.To<OrderDto>() |
|||
.Map(ctx => ctx.Source.Customer.Name).To(dto => dto.CustomerName) |
|||
.Map(ctx => ctx.Source.Lines.Sum(l => l.Quantity)).To(dto => dto.ItemCount) |
|||
.Map(ctx => ctx.Source.Lines.Sum(l => l.Quantity * l.UnitPrice)).To(dto => dto.Total) |
|||
.Map(ctx => ctx.Source.CreatedAt.ToString("O")).To(dto => dto.CreatedAtIso); |
|||
}); |
|||
|
|||
var mappedDto = mapper.Map(order).ToANew<OrderDto>(); |
|||
``` |
|||
|
|||
**NuGet Packages:** |
|||
|
|||
* https://www.nuget.org/packages/AgileObjects.AgileMapper |
|||
|
|||
|
|||
--- |
|||
|
|||
#### Manual (Pure) Mapping (no library) |
|||
|
|||
Straightforward, fastest, and most explicit. Good for simple applications which doesn't need long term maintenance. Hand-written mapping is faster, safer, and more maintainable. And for tiny mappings, you can still use manual mapping. |
|||
|
|||
* Examples of when manual mapping is better than libraries. |
|||
|
|||
```csharp |
|||
public static class OrderMapping |
|||
{ |
|||
public static OrderDto ToDto(this Order s) => new() |
|||
{ |
|||
Id = s.Id, |
|||
CustomerName = s.Customer.Name, |
|||
ItemCount = s.Lines.Sum(l => l.Quantity), |
|||
Total = s.Lines.Sum(l => l.Quantity * l.UnitPrice), |
|||
CreatedAtIso = s.CreatedAt.ToString("O") |
|||
}; |
|||
} |
|||
|
|||
// usage |
|||
var dto = order.ToDto(); |
|||
|
|||
// EF Core projection (best for perf + SQL translation) |
|||
var mappedList = dbContext.Orders.Select(s => new OrderDto |
|||
{ |
|||
Id = s.Id, |
|||
CustomerName = s.Customer.Name, |
|||
ItemCount = s.Lines.Sum(l => l.Quantity), |
|||
Total = s.Lines.Sum(l => l.Quantity * l.UnitPrice), |
|||
CreatedAtIso = s.CreatedAt.ToString("O") |
|||
}).ToList(); |
|||
``` |
|||
|
|||
|
|||
|
|||
### Conclusion |
|||
|
|||
If you rely on AutoMapper today, it’s time to evaluate alternatives. For ABP Framework, we chose **Mapperly** due to active development, strong community, and compile-time performance. But your team may prefer **Mapster** for flexibility or even manual mapping for small apps. Your requirements might be different, your project is not a framework so you decide the best one for you. |
|||
|
After Width: | Height: | Size: 477 KiB |
|
After Width: | Height: | Size: 163 KiB |
@ -0,0 +1,174 @@ |
|||
# Building a Permission-Based Authorization System for ASP.NET Core |
|||
|
|||
In this article, we'll explore different authorization approaches in ASP.NET Core and examine how ABP's permission-based authorization system works. |
|||
|
|||
First, we'll look at some of the core authorization types that come with ASP.NET Core, such as role-based, claims-based, policy-based, and resource-based authorization. We'll briefly review the pros and cons of each approach. |
|||
|
|||
Then, we'll dive into [ABP's Permission-Based Authorization System](https://abp.io/docs/latest/framework/fundamentals/authorization#permission-system). This is a more advanced approach that gives you fine-grained control over what users can do in your application. We'll also explore ABP's Permission Management Module, which makes managing permissions through the UI easily. |
|||
|
|||
## Understanding ASP.NET Core Authorization Types |
|||
|
|||
Before diving into permission-based authorization, let's examine some of the core authorization types available in ASP.NET Core: |
|||
|
|||
- **[Role-Based Authorization](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles?view=aspnetcore-9.0)** checks if the current user belongs to specific roles (like **"Admin"** or **"User"**) and grants access based on these roles. (For example, only users in the **"Manager"** role can access the employee salary management page.) |
|||
|
|||
- **[Claims-Based Authorization](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/claims?view=aspnetcore-9.0)** uses key-value pairs (claims) that describe user attributes, such as age, department, or security clearance. (For example, only users with a **"Department=Finance"** claim can view financial reports.) This provides more granular control but requires careful claim management (such as grouping claims under policies). |
|||
|
|||
- **[Policy-Based Authorization](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-9.0)** combines multiple requirements (roles, claims, custom logic) into reusable policies. It offers flexibility and centralized management, and **this is exactly why ABP's permission system is built on top of it!** (We'll discuss this in more detail later.) |
|||
|
|||
- **[Resource-Based Authorization](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/resourcebased?view=aspnetcore-9.0)** determines access by examining both the user and the specific item they want to access. (For example, a user can edit only their own blog posts, not others' posts.) Unlike policy-based authorization which applies the same rules everywhere, resource-based authorization makes decisions based on the actual data being accessed, requiring more complex implementation. |
|||
|
|||
Here's a quick comparison of these approaches: |
|||
|
|||
| Authorization Type | Pros | Cons | |
|||
|-------------------|------|------| |
|||
| **Role-Based** | Simple implementation, easy to understand | Becomes inflexible with complex role hierarchies | |
|||
| **Claims-Based** | Granular control, flexible user attributes | Complex claim management, potential for claim explosion | |
|||
| **Policy-Based** | Centralized logic, combines multiple requirements | Can become complex with numerous policies | |
|||
| **Resource-Based** | Fine-grained per-resource control | Implementation complexity, resource-specific code | |
|||
|
|||
## What is Permission-Based Authorization? |
|||
|
|||
Permission-based authorization takes a different approach from other authorization types by defining specific permissions (like **"CreateUser"**, **"DeleteOrder"**, **"ViewReports"**) that represent granular actions within your application. These permissions can be assigned to users directly or through roles, providing both flexibility and clear action-based access control. |
|||
|
|||
ABP Framework's permission system is built on top of this approach and extends ASP.NET Core's policy-based authorization system, working seamlessly with it. |
|||
|
|||
## ABP Framework's Permission System |
|||
|
|||
ABP extends [ASP.NET Core Authorization](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/introduction?view=aspnetcore-9.0) by adding **permissions** as automatic [policies](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-9.0) and allows the authorization system to be used in application services as well. |
|||
|
|||
This system provides a clean abstraction while maintaining full compatibility with ASP.NET Core's authorization infrastructure. |
|||
|
|||
ABP also provides a [Permission Management Module](https://abp.io/docs/latest/modules/permission-management) that offers a complete UI and API for managing permissions. This allows you to easily manage permissions in the UI, assign permissions to roles or users, and much more. (We'll see how to use it in the following sections.) |
|||
|
|||
### Defining Permissions in ABP |
|||
|
|||
In ABP, permissions are defined in classes (typically under the `*.Application.Contracts` project) that inherit from the `PermissionDefinitionProvider` class. Here's how you can define permissions for a book management system: |
|||
|
|||
```csharp |
|||
public class BookStorePermissionDefinitionProvider : PermissionDefinitionProvider |
|||
{ |
|||
public override void Define(IPermissionDefinitionContext context) |
|||
{ |
|||
var bookStoreGroup = context.AddGroup("BookStore"); |
|||
|
|||
var booksPermission = bookStoreGroup.AddPermission("BookStore.Books", L("Permission:Books")); |
|||
booksPermission.AddChild("BookStore.Books.Create", L("Permission:Books.Create")); |
|||
booksPermission.AddChild("BookStore.Books.Edit", L("Permission:Books.Edit")); |
|||
booksPermission.AddChild("BookStore.Books.Delete", L("Permission:Books.Delete")); |
|||
} |
|||
|
|||
private static LocalizableString L(string name) |
|||
{ |
|||
return LocalizableString.Create<BookStoreResource>(name); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
ABP automatically discovers this class and registers the permissions/policies in the system. You can then assign these permissions/policies to users/roles. There are two ways to do this: |
|||
|
|||
* Using the [Permission Management Module](https://abp.io/docs/latest/modules/permission-management) |
|||
* Using the `IPermissionManager` service (via code) |
|||
|
|||
#### Setting Permissions to Roles and Users via Permission Management Module |
|||
|
|||
When you define a permission, it also becomes usable in the ASP.NET Core authorization system as a **policy name**. If you are using the [Permission Management Module](https://abp.io/docs/latest/modules/permission-management), you can manage the permissions through the UI: |
|||
|
|||
 |
|||
|
|||
In the permission management UI, you can grant permissions to roles and users through the **Role Management** and **User Management** pages within the "permissions" modals. You can then easily check these permissions in your code. In the screenshot above, you can see the permission modal for the user's page, clearly showing the permissions granted to the user by their role. (**(R)** in the UI indicates that the permission is granted by one of the current user's roles.) |
|||
|
|||
#### Setting Permissions to Roles and Users via Code |
|||
|
|||
You can also set permissions for roles and users programmatically. You just need to inject the `IPermissionManager` service and use its `SetForRoleAsync` and `SetForUserAsync` methods (or similar methods): |
|||
|
|||
```csharp |
|||
public class MyService : ITransientDependency |
|||
{ |
|||
private readonly IPermissionManager _permissionManager; |
|||
|
|||
public MyService(IPermissionManager permissionManager) |
|||
{ |
|||
_permissionManager = permissionManager; |
|||
} |
|||
|
|||
public async Task GrantPermissionForUserAsync(Guid userId, string permissionName) |
|||
{ |
|||
await _permissionManager.SetForUserAsync(userId, permissionName, true); |
|||
} |
|||
|
|||
public async Task ProhibitPermissionForUserAsync(Guid userId, string permissionName) |
|||
{ |
|||
await _permissionManager.SetForUserAsync(userId, permissionName, false); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
### Checking Permissions in AppServices and Controllers |
|||
|
|||
ABP provides multiple ways to check permissions. The most common approach is using the `[Authorize]` attribute and passing the permission/policy name. |
|||
|
|||
Here is an example of how to check permissions in an application service: |
|||
|
|||
```csharp |
|||
[Authorize("BookStore.Books")] |
|||
public class BookAppService : ApplicationService, IBookAppService |
|||
{ |
|||
[Authorize("BookStore.Books.Create")] |
|||
public async Task<BookDto> CreateAsync(CreateBookDto input) |
|||
{ |
|||
//logic here |
|||
} |
|||
} |
|||
``` |
|||
|
|||
> Notice that you can use the `[Authorize]` attribute at both class and method levels. In the example above, the `CreateAsync` method is marked with the `[Authorize]` attribute, so it will check the user's permission before executing the method. Since the application service class also has a permission requirement, both permissions must be granted to the user to execute the method! |
|||
|
|||
And here is an example of how to check permissions in a controller: |
|||
|
|||
```csharp |
|||
[Authorize("BookStore.Books")] |
|||
public class CreateBookController : AbpController |
|||
{ |
|||
//omitted for brevity... |
|||
} |
|||
``` |
|||
|
|||
### Programmatic Permission Checking |
|||
|
|||
To conditionally control authorization in your code, you can use the `IAuthorizationService` service: |
|||
|
|||
```csharp |
|||
public class BookAppService : ApplicationService, IBookAppService |
|||
{ |
|||
public async Task<BookDto> CreateAsync(CreateBookDto input) |
|||
{ |
|||
// Checks the permission and throws an exception if the user does not have the permission |
|||
await AuthorizationService.CheckAsync(BookStorePermissions.Books.Create); |
|||
|
|||
// Your logic here |
|||
} |
|||
|
|||
public async Task<bool> CanUserCreateBooksAsync() |
|||
{ |
|||
// Checks if the permission is granted for the current user |
|||
return await AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Create); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
You can use the `IAuthorizationService`'s helpful methods for authorization checking, as shown in the example above: |
|||
|
|||
- `IsGrantedAsync` checks if the current user has the given permission. |
|||
- `CheckAsync` throws an exception if the current user does not have the given permission. |
|||
- `AuthorizeAsync` checks if the current user has the given permission and returns an `AuthorizationResult`, which has a `Succeeded` property that you can use to verify if the user has the permission. |
|||
|
|||
Also notice that we did not inject the `IAuthorizationService` in the constructor, because we are using the `ApplicationService` base class, which already provides property injection for it. This means we can directly use it in our application services, just like other helpful base services (such as `ICurrentUser` and `ICurrentTenant`). |
|||
|
|||
## Conclusion |
|||
|
|||
Permission-based authorization in ABP Framework provides a powerful and flexible approach to securing your applications. By building on ASP.NET Core's policy-based authorization, ABP offers a clean abstraction that simplifies permission management while maintaining the full power of the underlying system. |
|||
|
|||
The ability to check permissions in both application services and controllers makes ABP Framework's authorization system very flexible and powerful, yet easy to use. |
|||
|
|||
Additionally, the Permission Management Module makes it very easy to manage permissions and roles through the UI. You can learn more about how it works in the [documentation](https://abp.io/docs/latest/modules/permission-management). |
|||
|
After Width: | Height: | Size: 324 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 62 KiB |