| name | abp-module |
| license | UNLICENSED |
| description | Use when building or reviewing reusable ABP modules that will be distributed or consumed by other solutions. Covers EF Core + MongoDB dual support, virtual methods for extensibility, DbTablePrefix, module options pattern, entity extension, and separate connection string. |
ABP Module Solution Template
Docs: https://abp.io/docs/latest/solution-templates/application-module
This template is for developing reusable ABP modules. Key requirement: extensibility - consumers must be able to override and customize module behavior.
Solution Structure
MyModule/
โโโ src/
โ โโโ MyModule.Domain.Shared/ # Constants, enums, localization
โ โโโ MyModule.Domain/ # Entities, repository interfaces, domain services
โ โโโ MyModule.Application.Contracts/ # DTOs, service interfaces
โ โโโ MyModule.Application/ # Service implementations
โ โโโ MyModule.EntityFrameworkCore/ # EF Core implementation
โ โโโ MyModule.MongoDB/ # MongoDB implementation
โ โโโ MyModule.HttpApi/ # REST controllers
โ โโโ MyModule.HttpApi.Client/ # Client proxies
โ โโโ MyModule.Web/ # MVC/Razor Pages UI
โ โโโ MyModule.Blazor/ # Blazor UI
โโโ test/
โ โโโ MyModule.Tests/
โโโ host/
โโโ MyModule.HttpApi.Host/ # Test host application
Database Independence
Support both EF Core and MongoDB:
Repository Interface (Domain)
public interface IBookRepository : IRepository<Book, Guid>
{
Task<Book> FindByNameAsync(string name);
Task<List<Book>> GetListByAuthorAsync(Guid authorId);
}
EF Core Implementation
public class BookRepository : EfCoreRepository<MyModuleDbContext, Book, Guid>, IBookRepository
{
public async Task<Book> FindByNameAsync(string name)
{
var dbSet = await GetDbSetAsync();
return await dbSet.FirstOrDefaultAsync(b => b.Name == name);
}
}
MongoDB Implementation
public class BookRepository : MongoDbRepository<MyModuleMongoDbContext, Book, Guid>, IBookRepository
{
public async Task<Book> FindByNameAsync(string name)
{
var queryable = await GetQueryableAsync();
return await queryable.FirstOrDefaultAsync(b => b.Name == name);
}
}
Table/Collection Prefix
Allow customization to avoid naming conflicts:
public static class MyModuleDbProperties
{
public static string DbTablePrefix { get; set; } = "MyModule";
public static string DbSchema { get; set; } = null;
public const string ConnectionStringName = "MyModule";
}
Usage:
builder.Entity<Book>(b =>
{
b.ToTable(MyModuleDbProperties.DbTablePrefix + "Books", MyModuleDbProperties.DbSchema);
});
Module Options
Provide configuration options:
public class MyModuleOptions
{
public bool EnableFeatureX { get; set; } = true;
public int MaxItemCount { get; set; } = 100;
}
Usage in module:
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<MyModuleOptions>(options =>
{
options.EnableFeatureX = true;
});
}
Usage in service:
public class MyService : ITransientDependency
{
private readonly MyModuleOptions _options;
public MyService(IOptions<MyModuleOptions> options)
{
_options = options.Value;
}
}
Extensibility Points
Virtual Methods (Critical for Modules!)
When developing a reusable module, all public and protected methods must be virtual to allow consumers to override behavior:
public class BookAppService : ApplicationService, IBookAppService
{
public virtual async Task<BookDto> CreateAsync(CreateBookDto input)
{
var book = await CreateBookEntityAsync(input);
await _bookRepository.InsertAsync(book);
return _bookMapper.MapToDto(book);
}
protected virtual Task<Book> CreateBookEntityAsync(CreateBookDto input)
{
return Task.FromResult(new Book(
GuidGenerator.Create(),
input.Name,
input.Price
));
}
}
This allows module consumers to:
- Override specific methods without copying entire class
- Extend functionality while preserving base behavior
- Customize module behavior for their needs
Entity Extension
Support object extension system:
public class MyModuleModuleExtensionConfigurator
{
public static void Configure()
{
OneTimeRunner.Run(() =>
{
ObjectExtensionManager.Instance.Modules()
.ConfigureMyModule(module =>
{
module.ConfigureBook(book =>
{
book.AddOrUpdateProperty<string>("CustomProperty");
});
});
});
}
}
Localization
[LocalizationResourceName("MyModule")]
public class MyModuleResource
{
}
Configure<AbpLocalizationOptions>(options =>
{
options.Resources
.Add<MyModuleResource>("en")
.AddVirtualJson("/Localization/MyModule");
});
Permission Definition
public class MyModulePermissionDefinitionProvider : PermissionDefinitionProvider
{
public override void Define(IPermissionDefinitionContext context)
{
var myGroup = context.AddGroup(
MyModulePermissions.GroupName,
L("Permission:MyModule"));
myGroup.AddPermission(
MyModulePermissions.Books.Default,
L("Permission:Books"));
}
}
Best Practices
- Virtual methods - All public/protected methods must be
virtual for extensibility
- Protected virtual helpers - Use
protected virtual instead of private for helper methods
- Database agnostic - Support both EF Core and MongoDB
- Configurable - Use options pattern for customization
- Localizable - Use localization for all user-facing text
- Table prefix - Allow customization to avoid conflicts
- Separate connection string - Support dedicated database
- No dependencies on host - Module should be self-contained
- Test with host app - Include a host application for testing