Showing posts from Latest Update category

How to Add a Module in the ABP.io Application?

How to Add a Module in the ABP.io Application?

If you’re building your application with ABP.io, you’re already on the right path to creating scalable and professional software. One of the best things about ABP.io is how it supports modular architecture. But what does that really mean? In simple words, a **Module** in ABP.io is like a complete feature package. It comes with its own logic, database setup, APIs, and even user interface parts. Think of it as a plug-and-play part of your application that keeps your code clean, reusable, and easy to manage. ABP.io provides many useful built-in modules like Identity, Tenant Management, and Audit Logging. These help you quickly add common features without writing everything from scratch. But sometimes, you need to go beyond the default options. Maybe your project has unique requirements. That’s when creating a **custom module** becomes important. In this guide, I’ll show you how to add a custom module, like **Vineforce.ProjectManagement**, into your existing ABP.io application. You’ll learn how to do it step by step on both the backend using .NET and the frontend using Angular. Whether you’re just starting with ABP.io or already have experience, this blog is made to help you integrate modules smoothly and with confidence. What You’ll Learn in This Guide * How to prepare your system for module integration * How to add the module to your ABP.io backend using NuGet * How to configure the database context and migrations * How to link the Angular frontend with the module using npm * Common pitfalls to avoid during integration Step 1: Prerequisites & Setup Before you begin, make sure the following tools and environments are set up properly. 1.1 Required Software * .NET SDK (compatible with your ABP.io version) * Node.js and npm * Angular CLI (installed globally) * ABP CLI (installed via dotnet tool) * Visual Studio or Rider for .NET * Git for version control 1.2 ABP.io Application Ready Ensure you have an existing ABP.io application up and running. The application should be in either Modular Monolith or Microservice architecture. Step 2: Backend Integration Using NuGet (C# / .NET) We’ll start by connecting your backend application to the custom module using NuGet packages. 2.1 Add Required Packages "Vineforce.Admin.HttpApi.Host" ![Add Required Packages](./images/posts/how-to-add-a-module-in-the-abp-io-application/p1.png) ![Add Required Packages](./images/posts/how-to-add-a-module-in-the-abp-io-application/p2.png) - [Vineforce.ProjectManagement.Application](https://www.nuget.org/packages/Vineforce.ProjectManagement.Application) - [Vineforce.ProjectManagement.HttpApi](https://www.nuget.org/packages/Vineforce.ProjectManagement.HttpApi) ![Add Required Packages](./images/posts/how-to-add-a-module-in-the-abp-io-application/p3.png) 2.2 Register Module Dependencies 1. Open the file `AdminHttpApiHostModule.cs`. 2. Add the module dependencies inside the `[DependsOn]` attribute: ```csharp [DependsOn( typeof(ProjectManagementHttpApiModule), typeof(ProjectManagementApplicationModule), typeof(ProjectManagementEntityFrameworkCoreModule), typeof(ProjectManagementDomainSharedModule) )] ``` ![Add Required Packages](./images/posts/how-to-add-a-module-in-the-abp-io-application/p4.png) 3. Also, add the using statements at the top: ```csharp using Vineforce.ProjectManagement; using Vineforce.ProjectManagement.EntityFrameworkCore; ``` 2.3 Add Required Packages "Vineforce.Admin.EntityFrameworkCore" ![Add Required Packages](./images/posts/how-to-add-a-module-in-the-abp-io-application/p5.png) ![Add Required Packages](./images/posts/how-to-add-a-module-in-the-abp-io-application/p6.png) 1. Also, add the using statements at the top: ```csharp using Vineforce.ProjectManagement.EntityFrameworkCore; ``` 2.4 Update Your DbContext 1. Open `AdminDbContext.cs` ![Add Required Packages](./images/posts/how-to-add-a-module-in-the-abp-io-application/p7.png) 2. Inside the `OnModelCreating` method, add: ![Add Required Packages](./images/posts/how-to-add-a-module-in-the-abp-io-application/p8.png) ```csharp builder.ConfigureProjectManagement(); ``` This step tells your database context to load and apply table configurations from the ProjectManagement module. 2.5 Apply Migrations Once you update the DbContext, run EF Core migrations to apply the updated schema to your database. Option A: Using Terminal / PowerShell ```bash cd src/Vineforce.Admin.EntityFrameworkCore dotnet ef migrations add Add_ProjectManagement_Module dotnet ef database update ``` Option B: Using Visual Studio (Recommended for Beginners) 1. Open Package Manager Console. 2. Set Default Project to `Vineforce.Admin.EntityFrameworkCore`. 2. Run the Following Commands: ```powershell Add-Migration Add_ProjectManagement_Module Update-Database ``` At this point, the new tables from the module will be added to your main database. ![Add Required Packages](./images/posts/how-to-add-a-module-in-the-abp-io-application/p9.png) Step 3: Frontend Integration Using npm (Angular) Once the backend is configured, let’s integrate the Angular frontend with the module. 3.1 Install the Angular Module 1. Open your Angular project root (usually `/angular`). 2. Run this command in the terminal: ```bash npm i @vineforce-modules/project-management ``` 4. You can view this link to copy the command: '[@vineforce-modules/project-management](https://www.npmjs.com/package/@vineforce-modules/project-management)' 3. This will also add the following entry in `package.json`: ```json "@vineforce-modules/project-management": "^9.0.0" ``` ![Add Required Packages](./images/posts/how-to-add-a-module-in-the-abp-io-application/p10.png) 3.2 Update the app-routing.module.ts File ![Add Required Packages](./images/posts/how-to-add-a-module-in-the-abp-io-application/p11.png) ```typescript { path: 'project-management', loadChildren: () => import('@vineforce-modules/project-management').then( m => m.ProjectManagementModule.forLazy() ), }, ``` This step makes the ProjectManagement module visible in the frontend UI. Step 4: Test the Integration Now that everything is configured, it’s time to test. 4.1 Backend Checklist * Run the application using Visual Studio or `dotnet run` * Check if there are any build errors * Confirm that new database tables were created 4.2 Frontend Checklist * Run `npm start` or `ng serve` * Log in to the app * Navigate through the new module in the side menu * Test create/edit/delete operations Frequently Asked Questions Q: What are the prerequisites for adding a custom ABP.io module? A: You'll need .NET SDK compatible with your ABP version, Node.js/npm, Angular CLI, ABP CLI, and an existing ABP.io application in either Modular Monolith or Microservice architecture. Q: How do I integrate the module on the backend? A: Add the required NuGet packages (`Vineforce.ProjectManagement.Application`, `Vineforce.ProjectManagement.HttpApi`) and register dependencies in `AdminHttpApiHostModule.cs`. Q: What's the best approach for database integration? A: Update your `DbContext` to call `builder.ConfigureProjectManagement()` and apply migrations using `dotnet ef migrations add` and `dotnet ef database update`. Q: How do I integrate with the Angular frontend? A: Install the module via `npm i @vineforce-modules/project-management` and add it to your Angular routes in `app-routing.module.ts`. Q: What common issues should I watch for? A: Watch for version mismatches between backend and frontend packages, database migration errors, and Angular module import issues. Common Issues & Troubleshooting | Issue | Solution | | ------------------------------- | ----------------------------------------------------------- | | Build fails after adding module | Double-check all using imports and NuGet versions | | EF migration error | Make sure DbContext is updated and compiled | | Module not showing in UI | Confirm that routing is updated correctly | | Package not found | Check internet connection and package version compatibility | Final Summary You have now successfully: * Added a module to your ABP.io backend * Linked it with your database and ran migrations * Integrated the Angular module using npm * Updated routing and tested the entire module This setup allows you to build and scale your application using modular principles without repeating core logic again and again. ![Add Required Packages](./images/posts/how-to-add-a-module-in-the-abp-io-application/p12.png) ![Add Required Packages](./images/posts/how-to-add-a-module-in-the-abp-io-application/p13.png) Conclusion Adding a custom module to your ABP.io application is not just a technical task, it is a smart way to keep your project scalable and organized. By following this step-by-step guide, you have successfully connected your backend using NuGet, updated your database context, applied migrations, and linked the frontend through npm and Angular routing. This process helps you avoid repeating code, improves maintainability, and prepares your application for future growth. Whether you are adding one module or planning to build a fully modular system, this method will save time and reduce errors. If you face any issues or want expert assistance, the Vineforce team is always ready to help with ABP.io development, custom modules, and performance optimization. You can reach us at **[[email protected]](mailto:[email protected])** for support or consultation.

What's New in .NET 9:Faster, Safer, Smarter Features

What's New in .NET 9:Faster, Safer, Smarter Features

.NET 9 brings a bunch of exciting updates that make it faster, safer, and easier to use. It improves performance, handles memory better, and makes working with collections simpler. You'll find helpful features like tools for trimming code, better ways to manage collections, and new LINQ methods. Plus, it adds safer ways to handle cryptography, makes working with time more convenient, and processes data more efficiently. Let's dive in to see how these new features make .NET 9 a great choice for developers! Key Features 1. **Feature Switches with Trimming Support** : FeatureSwitchDefinitionAttribute and FeatureGuardAttribute help reduce app size when trimming by excluding dead code based on feature flags. 2. **UnsafeAccessorAttribute for Generics**: Now supports generic parameters, enabling unsafe access to private members of generic types. 3. **Garbage Collection (DATAS)**: Dynamic Adaptation to Application Sizes automatically adjusts heap size to optimize memory usage and is enabled by default. 4. **Performance Enhancements**: Includes loop optimizations, inlining improvements, PGO enhancements, ARM64 vectorization, and faster exception handling. New Libraries 1. **Base64Url Encoding**: New Base64Url class simplifies working with URL-safe Base64 encoding. 2. **Removal of BinaryFormatter**: Removed to encourage safer serialization practices. 3. **Improved Collections**: New OrderedDictionary<TKey, TValue>, ReadOnlySet<T>, and updated PriorityQueue improve performance and flexibility. 4. **Cryptography**: New KMAC algorithm, improved key management, and OpenSSL provider support. 5. **TimeSpan Enhancements**: New integer-based overloads provide more precise time span creation and avoid floating-point precision issues. 6. **LINQ Enhancements**: New CountBy, AggregateBy, and Index methods streamline collection operations and improve efficiency. 1. Attribute Model for Feature Switches with Trimming Support The Attribute model for feature switches with trimming support in .NET allows you to define feature switches that can enable or disable functionality, helping reduce app size when trimming or compiling with Native AOT (Ahead-of-Time compilation). Key Attributes 1. **FeatureSwitchDefinitionAttribute**: Used to treat a feature-switch property as a constant during trimming, allowing dead code guarded by the switch to be removed. For example, if a feature is disabled in the project settings, related code will be removed: ```csharp if (Feature.IsSupported) Feature.Implementation(); ``` 2. **FeatureGuardAttribute**: Used for guarding code that requires attributes like RequiresUnreferencedCode, RequiresAssemblyFiles, or RequiresDynamicCode. This ensures that feature-related code is properly handled when trimming: ```csharp [FeatureGuard(typeof(RequiresDynamicCodeAttribute))] internal static bool IsSupported => RuntimeFeature.IsDynamicCodeSupported; ``` Example If the feature Feature.IsSupported is set to false in the project file, the corresponding implementation is removed when trimming, optimizing the app size: ```xml <ItemGroup> <RuntimeHostConfigurationOption Include="Feature.IsSupported" Value="false" Trim="true" /> </ItemGroup> ``` 2. UnsafeAccessorAttribute Supports Generic Parameters The UnsafeAccessorAttribute in .NET allows unsafe access to private members (fields or methods) of a class. Initially introduced in .NET 8, this feature lacked support for generic parameters. .NET 9 adds support for generic parameters in both CoreCLR and Native AOT scenarios, enabling more flexibility in accessing generic type members. Example In the following code, the UnsafeAccessorAttribute is used to access private fields and methods of a generic class: ```csharp public class Class<T> { private T? _field; private void M<U>(T t, U u) { } } class Accessors<V> { [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_field")] public extern static ref V GetSetPrivateField(Class<V> c); [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M")] public extern static void CallM<W>(Class<V> c, V v, W w); } internal class UnsafeAccessorExample { public void AccessGenericType(Class<int> c) { ref int f = ref Accessors<int>.GetSetPrivateField(c); Accessors<int>.CallM<string>(c, 1, string.Empty); } } ``` In this example, GetSetPrivateField and CallM are used to access and modify private members in a Class<int> object, with support for generic type parameters. 3. Garbage Collection Dynamic Adaptation to Application Sizes (DATAS) is a feature in .NET that automatically adjusts the application's heap size based on its memory requirements, ensuring that the heap size is roughly proportional to the long-lived data size. This improves memory efficiency by preventing excessive heap growth while maintaining performance. DATAS was introduced as an opt-in feature in .NET 8 and has been enhanced in .NET 9, enabling automatic heap size management by default. Example In .NET 9, DATAS is enabled by default, meaning applications will dynamically adjust memory usage based on the amount of long-lived data they manage. This results in more efficient memory usage without requiring manual tuning by developers. For more details, you can refer to the Dynamic Adaptation to Application Sizes (DATAS) documentation. 4. Performance Improvement The following performance improvements have been made for .NET 9: - Loop optimizations - Inlining improvements - PGO improvements: Type checks and casts - Arm64 vectorization in .NET libraries - Arm64 code generation - Faster exceptions - Code layout - Reduced address exposure - AVX10v1 support - Hardware intrinsic code generation - Constant folding for floating point and SIMD operations - Arm64 SVE support - Object stack allocation for boxes What's New in .NET 9 Libraries 1. Base64Url Base64 is an encoding scheme that converts binary data into a text format, using a set of 64 characters. This encoding is commonly used for transferring data, as it's supported by many methods like Convert.ToBase64String. However, Base64 includes characters like '+' and '/', which can conflict with URL encoding because they have special meanings in URLs. To address this issue, the Base64Url scheme was created, which uses an alternate set of characters that are URL-safe. In .NET 9, a new Base64Url class was introduced, making it easier to work with Base64Url encoding and decoding. **Example Code:** ```csharp ReadOnlySpan<byte> bytes = ...; string encoded = Base64Url.EncodeToString(bytes); ``` This example shows how to use the Base64Url.EncodeToString method to encode a byte array into a Base64Url string, suitable for use in URLs. 2. BinaryFormatter In .NET 9, the BinaryFormatter has been removed from the runtime. Although the API still exists, its implementation now always throws an exception, making it unusable. This change encourages developers to migrate to safer and more efficient serialization methods. A migration guide is available to help users transition from BinaryFormatter to alternative approaches. **Example:** If you try using BinaryFormatter in .NET 9, it will throw an exception: ```csharp var formatter = new BinaryFormatter(); formatter.Serialize(stream, obj); // This will throw an exception in .NET 9 ``` Developers are advised to use alternatives like System.Text.Json or Newtonsoft.Json for serialization tasks. 3. Collections The collection types in .NET gain the following updates for .NET 9: - Collection lookups with spans - OrderedDictionary<TKey, TValue> - PriorityQueue.Remove() method lets you update the priority of an item in the queue. - ReadOnlySet<T> Collection Lookups with Spans In high-performance scenarios, spans are used to avoid unnecessary string allocations. In .NET 9, with the new allows ref struct feature in C# 13, it's now possible to perform lookups on collection types like Dictionary<TKey, TValue> using spans. This helps optimize memory usage and performance in lookup-intensive code. **Example:** ```csharp private static Dictionary<string, int> CountWords(ReadOnlySpan<char> input) { Dictionary<string, int> wordCounts = new(StringComparer.OrdinalIgnoreCase); Dictionary<string, int>.AlternateLookup<ReadOnlySpan<char>> spanLookup = wordCounts.GetAlternateLookup<ReadOnlySpan<char>>(); foreach (Range wordRange in Regex.EnumerateSplits(input, @"\b\w+\b")) { ReadOnlySpan<char> word = input[wordRange]; spanLookup[word] = spanLookup.TryGetValue(word, out int count) ? count + 1 : 1; } return wordCounts; } ``` OrderedDictionary<TKey, TValue> The OrderedDictionary<TKey, TValue> allows both ordered storage of key-value pairs and efficient key-based lookups. In .NET 9, a generic version of OrderedDictionary is introduced, improving type safety and efficiency. **Example:** ```csharp OrderedDictionary<string, int> d = new() { ["a"] = 1, ["b"] = 2, ["c"] = 3, }; d.Add("d", 4); d.RemoveAt(0); d.RemoveAt(2); d.Insert(0, "e", 5); foreach (KeyValuePair<string, int> entry in d) { Console.WriteLine(entry); } // Output: // [e, 5] // [b, 2] // [c, 3] ``` PriorityQueue.Remove() Method .NET 6 introduced PriorityQueue<TElement, TPriority>, but it lacked efficient priority updates. The new PriorityQueue<TElement, TPriority>.Remove() method in .NET 9 allows emulating priority updates, making it suitable for algorithms like Dijkstra's algorithm in certain contexts. **Example:** ```csharp public static void UpdatePriority<TElement, TPriority>( this PriorityQueue<TElement, TPriority> queue, TElement element, TPriority priority ) { queue.Remove(element, out _, out _); // Remove the element queue.Enqueue(element, priority); // Re-enqueue with new priority } ``` ReadOnlySet<T> .NET 9 introduces ReadOnlySet<T>, a read-only wrapper for mutable sets (ISet<T>), complementing ReadOnlyCollection<T> and ReadOnlyDictionary<TKey, TValue> for other collections. **Example:** ```csharp private readonly HashSet<int> _set = new(); private ReadOnlySet<int>? _setWrapper; public ReadOnlySet<int> Set => _setWrapper ??= new ReadOnlySet<int>(_set); ``` 4. Cryptography The advancement of cryptography in .NET 9 strengthens data security by enhancing encryption algorithms and improving key management processes. CryptographicOperations.HashData() Method The CryptographicOperations.HashData() method in .NET 9 allows for one-shot hashing or HMAC operations using a specified HashAlgorithmName. This simplifies hashing operations, as it eliminates the need for conditional logic based on the algorithm, improving performance and reducing allocations. **Example:** ```csharp static void HashAndProcessData(HashAlgorithmName hashAlgorithmName, byte[] data) { byte[] hash = CryptographicOperations.HashData(hashAlgorithmName, data); ProcessHash(hash); } ``` KMAC Algorithm .NET 9 introduces the KMAC (KECCAK Message Authentication Code) algorithm, as specified by NIST SP-800-185. KMAC is a pseudorandom function based on KECCAK, supporting both one-shot and accumulated MAC generation. It's available on Linux (OpenSSL 3.0 or later) and Windows 11 (Build 26016 or later). **Example:** ```csharp if (Kmac128.IsSupported) { byte[] key = GetKmacKey(); byte[] input = GetInputToMac(); byte[] mac = Kmac128.HashData(key, input, outputLength: 32); } else { // Handle scenario where KMAC isn't available. } ``` X.509 Certificate Loading .NET 9 introduces the X509CertificateLoader class to replace older, less secure certificate loading methods. This class provides a more secure, one-method-one-purpose design for certificate loading, supporting two widely compatible formats. OpenSSL Providers Support .NET 9 enhances support for OpenSSL providers, including the ability to use SafeEvpPKeyHandle.OpenKeyFromProvider for interacting with providers such as TPM2 or PKCS11. This allows for secure key management with hardware security modules (HSM). **Example:** ```csharp byte[] data = [ /* example data */ ]; // Using a TPM provider using (SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider("tpm2", "handle:0x81000007")) using (ECDsa ecdsaPri = new ECDsaOpenSsl(priKeyHandle)) { byte[] signature = ecdsaPri.SignData(data, HashAlgorithmName.SHA256); // Use signature created by TPM. } ``` This method improves performance during the TLS handshake and interaction with RSA private keys using ENGINE components. 5. TimeSpan Class Enhancements in .NET 9 The TimeSpan class in .NET 9 introduces new overloads for creating TimeSpan objects from integers. This is particularly useful to avoid precision issues that can arise when using double values, as the floating-point format can lead to slight inaccuracies. For example, TimeSpan.FromSeconds(101.832)` might result in a time span that isn't exactly 101 seconds and 832 milliseconds but instead a slightly imprecise value. The new overloads allow you to directly specify days, hours, minutes, seconds, milliseconds, and microseconds using integer values, providing a more accurate and efficient way to create TimeSpan objects. **Example:** ```csharp TimeSpan timeSpan1 = TimeSpan.FromSeconds(value: 101.832); Console.WriteLine($"timeSpan1 = {timeSpan1}"); // Output: timeSpan1 = 00:01:41.8319999 TimeSpan timeSpan2 = TimeSpan.FromSeconds(seconds: 101, milliseconds: 832); Console.WriteLine($"timeSpan2 = {timeSpan2}"); // Output: timeSpan2 = 00:01:41.8320000 ``` In this example, timeSpan1 shows the floating-point imprecision, whereas timeSpan2 demonstrates the new integer-based overload, providing a precise value. 6. LINQ LINQ (Language Integrated Query) has received enhancements in .NET 9, making querying collections even easier and more powerful, particularly with the introduction of new query operators and better async LINQ support. .NET 9 introduces new methods CountBy, AggregateBy, and Index to streamline collection operations and avoid unnecessary intermediate groupings, offering more efficient ways to process data. **1. CountBy:** This method allows you to quickly calculate the frequency of each key in a collection, similar to GroupBy, but without allocating intermediate groupings. **Example: Find the most frequent word in a text:** ```csharp string sourceText = """ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices amet diam. """; KeyValuePair<string, int> mostFrequentWord = sourceText .Split(new char[] { ' ', '.', ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(word => word.ToLowerInvariant()) .CountBy(word => word) .MaxBy(pair => pair.Value); Console.WriteLine(mostFrequentWord.Key); // Output: amet ``` **AggregateBy:** This method lets you perform custom aggregation by key, replacing the need for GroupBy when you need to aggregate values in a more general-purpose manner. **Example: Aggregate scores by ID:** ```csharp (string id, int score)[] data = [ ("0", 42), ("1", 5), ("2", 4), ("1", 10), ("0", 25), ]; var aggregatedData = data.AggregateBy( keySelector: entry => entry.id, seed: 0, (totalScore, curr) => totalScore + curr.score ); foreach (var item in aggregatedData) { Console.WriteLine(item); } // Output: // (0, 67) // (1, 15) // (2, 4) ``` **Index:** This method provides a quick way to obtain the index of each item in a collection. It simplifies the process of iterating through elements with their indices. **Example: Iterate through lines in a file with their line numbers:** ```csharp IEnumerable<string> lines2 = File.ReadAllLines("output.txt"); foreach ((int index, string line) in lines2.Index()) { Console.WriteLine($"Line number: {index + 1}, Line: {line}"); } ``` These methods enhance data manipulation efficiency and readability in common workflows like counting occurrences, aggregation, and indexing.