--- description: "ABP Module solution template specific patterns" alwaysApply: false --- # 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) ```csharp public interface IBookRepository : IRepository { Task FindByNameAsync(string name); Task> GetListByAuthorAsync(Guid authorId); } ``` ### EF Core Implementation ```csharp public class BookRepository : EfCoreRepository, IBookRepository { public async Task FindByNameAsync(string name) { var dbSet = await GetDbSetAsync(); return await dbSet.FirstOrDefaultAsync(b => b.Name == name); } } ``` ### MongoDB Implementation ```csharp public class BookRepository : MongoDbRepository, IBookRepository { public async Task FindByNameAsync(string name) { var queryable = await GetQueryableAsync(); return await queryable.FirstOrDefaultAsync(b => b.Name == name); } } ``` ## Table/Collection Prefix Allow customization to avoid naming conflicts: ```csharp // Domain.Shared public static class MyModuleDbProperties { public static string DbTablePrefix { get; set; } = "MyModule"; public static string DbSchema { get; set; } = null; public const string ConnectionStringName = "MyModule"; } ``` Usage: ```csharp builder.Entity(b => { b.ToTable(MyModuleDbProperties.DbTablePrefix + "Books", MyModuleDbProperties.DbSchema); }); ``` ## Module Options Provide configuration options: ```csharp // Domain public class MyModuleOptions { public bool EnableFeatureX { get; set; } = true; public int MaxItemCount { get; set; } = 100; } ``` Usage in module: ```csharp public override void ConfigureServices(ServiceConfigurationContext context) { Configure(options => { options.EnableFeatureX = true; }); } ``` Usage in service: ```csharp public class MyService : ITransientDependency { private readonly MyModuleOptions _options; public MyService(IOptions 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: ```csharp public class BookAppService : ApplicationService, IBookAppService { // ✅ Public methods MUST be virtual public virtual async Task CreateAsync(CreateBookDto input) { var book = await CreateBookEntityAsync(input); await _bookRepository.InsertAsync(book); return _bookMapper.MapToDto(book); } // ✅ Use protected virtual for helper methods (not private) protected virtual Task CreateBookEntityAsync(CreateBookDto input) { return Task.FromResult(new Book( GuidGenerator.Create(), input.Name, input.Price )); } // ❌ WRONG for modules - private methods cannot be overridden // private Book CreateBook(CreateBookDto input) { ... } } ``` 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: ```csharp public class MyModuleModuleExtensionConfigurator { public static void Configure() { OneTimeRunner.Run(() => { ObjectExtensionManager.Instance.Modules() .ConfigureMyModule(module => { module.ConfigureBook(book => { book.AddOrUpdateProperty("CustomProperty"); }); }); }); } } ``` ## Localization ```csharp // Domain.Shared [LocalizationResourceName("MyModule")] public class MyModuleResource { } // Module configuration Configure(options => { options.Resources .Add("en") .AddVirtualJson("/Localization/MyModule"); }); ``` ## Permission Definition ```csharp 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 1. **Virtual methods** - All public/protected methods must be `virtual` for extensibility 2. **Protected virtual helpers** - Use `protected virtual` instead of `private` for helper methods 3. **Database agnostic** - Support both EF Core and MongoDB 4. **Configurable** - Use options pattern for customization 5. **Localizable** - Use localization for all user-facing text 6. **Table prefix** - Allow customization to avoid conflicts 7. **Separate connection string** - Support dedicated database 8. **No dependencies on host** - Module should be self-contained 9. **Test with host app** - Include a host application for testing