abp-infrastructure-patterns
π―Skillfrom thapaliyabikendra/ai-artifacts
abp-infrastructure-patterns skill from thapaliyabikendra/ai-artifacts
Installation
npx skills add https://github.com/thapaliyabikendra/ai-artifacts --skill abp-infrastructure-patternsSkill Details
"ABP Framework cross-cutting patterns including authorization, background jobs, distributed events, multi-tenancy, and module configuration. Use when: (1) defining permissions, (2) creating background jobs, (3) publishing/handling distributed events, (4) configuring modules."
Overview
# ABP Infrastructure Patterns
Cross-cutting concerns and infrastructure patterns for ABP Framework.
Authorization & Permissions
Define Permissions
```csharp
// Domain.Shared/Permissions/ClinicPermissions.cs
public static class ClinicPermissions
{
public const string GroupName = "Clinic";
public static class Patients
{
public const string Default = GroupName + ".Patients";
public const string Create = Default + ".Create";
public const string Edit = Default + ".Edit";
public const string Delete = Default + ".Delete";
}
public static class Appointments
{
public const string Default = GroupName + ".Appointments";
public const string Create = Default + ".Create";
public const string Edit = Default + ".Edit";
public const string Delete = Default + ".Delete";
public const string ViewAll = Default + ".ViewAll"; // Admins only
}
}
```
Register Permissions
```csharp
// Application.Contracts/Permissions/ClinicPermissionDefinitionProvider.cs
public class ClinicPermissionDefinitionProvider : PermissionDefinitionProvider
{
public override void Define(IPermissionDefinitionContext context)
{
var clinicGroup = context.AddGroup(ClinicPermissions.GroupName);
var patients = clinicGroup.AddPermission(
ClinicPermissions.Patients.Default,
L("Permission:Patients"));
patients.AddChild(ClinicPermissions.Patients.Create, L("Permission:Patients.Create"));
patients.AddChild(ClinicPermissions.Patients.Edit, L("Permission:Patients.Edit"));
patients.AddChild(ClinicPermissions.Patients.Delete, L("Permission:Patients.Delete"));
var appointments = clinicGroup.AddPermission(
ClinicPermissions.Appointments.Default,
L("Permission:Appointments"));
appointments.AddChild(ClinicPermissions.Appointments.Create, L("Permission:Appointments.Create"));
appointments.AddChild(ClinicPermissions.Appointments.ViewAll, L("Permission:Appointments.ViewAll"));
}
private static LocalizableString L(string name)
=> LocalizableString.Create
}
```
Use Permissions
```csharp
// Declarative
[Authorize(ClinicPermissions.Patients.Create)]
public async Task
// Imperative
public async Task
{
var appointment = await _appointmentRepository.GetAsync(id);
if (appointment.DoctorId != CurrentUser.Id)
{
await AuthorizationService.CheckAsync(ClinicPermissions.Appointments.ViewAll);
}
return _mapper.AppointmentToDto(appointment);
}
// Check without throwing
public async Task
=> await AuthorizationService.IsGrantedAsync(ClinicPermissions.Patients.Create);
```
Background Jobs
Define Job
```csharp
public class AppointmentReminderJob : AsyncBackgroundJob
{
private readonly IRepository
private readonly IEmailSender _emailSender;
public AppointmentReminderJob(
IRepository
IEmailSender emailSender)
{
_appointmentRepository = appointmentRepository;
_emailSender = emailSender;
}
public override async Task ExecuteAsync(AppointmentReminderArgs args)
{
var appointment = await _appointmentRepository.GetAsync(args.AppointmentId);
await _emailSender.SendAsync(
appointment.Patient.Email,
"Appointment Reminder",
$"You have an appointment on {appointment.AppointmentDate}");
}
}
public class AppointmentReminderArgs
{
public Guid AppointmentId { get; set; }
}
```
Enqueue Job
```csharp
public async Task
{
var appointment = await _appointmentManager.CreateAsync(/.../);
// Schedule reminder 24 hours before
var reminderTime = appointment.AppointmentDate.AddHours(-24);
await _backgroundJobManager.EnqueueAsync(
new AppointmentReminderArgs { AppointmentId = appointment.Id },
delay: reminderTime - DateTime.Now);
return _mapper.AppointmentToDto(appointment);
}
```
Distributed Events
Publish Event
```csharp
// From entity (recommended for domain events)
public class Patient : AggregateRoot
{
public void Activate()
{
IsActive = true;
AddDistributedEvent(new PatientActivatedEto
{
Id = Id,
Name = Name,
Email = Email
});
}
}
// From application service
public async Task ActivateAsync(Guid id)
{
var patient = await _patientRepository.GetAsync(id);
patient.Activate();
// Or manually publish:
await _distributedEventBus.PublishAsync(new PatientActivatedEto
{
Id = patient.Id,
Name = patient.Name,
Email = patient.Email
});
}
```
Handle Event
```csharp
public class PatientActivatedEventHandler :
IDistributedEventHandler
ITransientDependency
{
private readonly IEmailSender _emailSender;
private readonly ILogger
public PatientActivatedEventHandler(
IEmailSender emailSender,
ILogger
{
_emailSender = emailSender;
_logger = logger;
}
public async Task HandleEventAsync(PatientActivatedEto eventData)
{
_logger.LogInformation("Patient activated: {Name}", eventData.Name);
await _emailSender.SendAsync(
eventData.Email,
"Welcome",
"Your patient account has been activated");
}
}
```
Robust Event Handler (Idempotent + Multi-Tenant)
```csharp
public class EntitySyncEventHandler :
IDistributedEventHandler
ITransientDependency
{
private readonly IRepository
private readonly IDataFilter _dataFilter;
private readonly ILogger
public async Task HandleEventAsync(EntityUpdatedEto eto)
{
// Disable tenant filter for cross-tenant sync
using (_dataFilter.Disable
{
try
{
_logger.LogInformation("Processing entity sync: {Id}", eto.Id);
// Idempotency check
var existing = await _repository.FirstOrDefaultAsync(
x => x.ExternalId == eto.ExternalId);
if (existing != null)
{
ObjectMapper.Map(eto, existing);
await _repository.UpdateAsync(existing);
}
else
{
var entity = ObjectMapper.Map
await _repository.InsertAsync(entity);
}
_logger.LogInformation("Entity sync completed: {Id}", eto.Id);
}
catch (Exception ex)
{
_logger.LogError(ex, "Entity sync failed: {Id}", eto.Id);
throw new UserFriendlyException($"Failed to sync entity: {ex.Message}");
}
}
}
}
```
Multi-Tenancy
Cross-Tenant Operations
```csharp
public class CrossTenantService : ApplicationService
{
private readonly IDataFilter _dataFilter;
private readonly ICurrentTenant _currentTenant;
public async Task> GetAllTenantsPatients()
{
using (_dataFilter.Disable
{
return await _patientRepository.GetListAsync();
}
}
public async Task OperateOnTenant(Guid tenantId)
{
using (_currentTenant.Change(tenantId))
{
await DoTenantSpecificOperation();
}
}
}
```
Tenant-Specific Seeding
```csharp
public async Task SeedAsync(DataSeedContext context)
{
if (context.TenantId.HasValue)
await SeedTenantDataAsync(context.TenantId.Value);
else
await SeedHostDataAsync();
}
```
Module Configuration
```csharp
[DependsOn(
typeof(ClinicDomainModule),
typeof(AbpIdentityDomainModule),
typeof(AbpPermissionManagementDomainModule))]
public class ClinicApplicationModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure
{
options.ExternalLoginProviders.Add
});
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure
{
options.KeyPrefix = "Clinic:";
});
context.Services.AddTransient
context.Services.AddSingleton
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
}
}
```
Object Extension
```csharp
public static class ClinicModuleExtensionConfigurator
{
public static void Configure()
{
ObjectExtensionManager.Instance.Modules()
.ConfigureIdentity(identity =>
{
identity.ConfigureUser(user =>
{
user.AddOrUpdateProperty
"Title",
property =>
{
property.Attributes.Add(new StringLengthAttribute(64));
});
});
});
}
}
```
Best Practices
- Permissions - Define hierarchically (Parent.Child pattern)
- Background jobs - Use for long-running or delayed tasks
- Distributed events - Use for loose coupling between modules
- Idempotency - Check for existing before insert in event handlers
- Multi-tenancy - Use
IDataFilter.Disablesparingly() - Module deps - Declare all dependencies explicitly
Related Skills
abp-entity-patterns- Domain layer patternsabp-service-patterns- Application layer patternsopeniddict-authorization- OAuth implementationdistributed-events-advanced- Advanced event patterns
More from this repository10
Provides clean code guidelines and refactoring techniques for C#/.NET, focusing on improving code readability, maintainability, and adherence to SOLID principles.
Implements domain layer patterns for ABP Framework, providing robust entity, aggregate, repository, and domain service implementations following DDD principles.
Validates input DTOs in ABP Framework using FluentValidation with async checks, conditional rules, custom validators, and localized error messages.
Configures and optimizes Entity Framework Core patterns for ABP Framework, focusing on entity configuration, migrations, and relationship design with PostgreSQL.
Implements comprehensive REST APIs in ABP Framework with robust AppServices, DTOs, pagination, filtering, and authorization for .NET applications.
Generates Claude Code artifacts like skills, agents, and commands with best practices and quality standards.
content-retrieval skill from thapaliyabikendra/ai-artifacts
Designs scalable, reliable distributed systems by applying proven architectural patterns and evaluating trade-offs across performance, consistency, and availability.
Generates ABP Application.Contracts layer scaffolding, enabling parallel development by creating standardized interfaces, DTOs, and permissions for .NET microservices.
Implements permission-based OAuth 2.0 authorization for ABP Framework using OpenIddict, enabling fine-grained access control and multi-tenant security.