CSI IDO Extension Classes / Custom Assemblies
Detailed Primer on Creating Custom Assemblies in Infor CSI (SyteLine)
Welcome to the world of Infor CSI custom assemblies! The trend is moving away from SQL Server stored procedures towards C# assemblies for extending CSI's functionality. This shift brings the power and flexibility of the .NET framework directly into your CSI environment. At its core, a "custom assembly" in Infor CSI (also known as an IDO Extension Class or Mongoose Extension Class) is a library of C# (or VB.NET) code that you write. This code can then be "hooked into" CSI's existing business objects (called IDOs) to add new behaviors, validate data, integrate with external systems, or perform complex calculations that aren't possible through standard CSI configurations.
Let's break down everything you need to know as a beginner.
Tools Required To begin creating custom assemblies, you'll need to set up your development environment with the right software:
Visual Studio (Integrated Development Environment - IDE): This is your primary workspace. Think of it as a sophisticated text editor specifically designed for writing, organizing, and debugging code. Recommendation: Visual Studio Community Edition is free for individuals and small teams and provides all the necessary features. Installation: When installing Visual Studio, ensure you select the ".NET desktop development" workload. This includes the C# compiler and other essential components. Why it's essential: Visual Studio compiles your C# code into a .dll file (your custom assembly), which CSI can then understand and execute.
Targeted .NET Framework SDK: CSI is built on the Microsoft .NET Framework (a software development platform). Your custom assemblies must target the same version of the .NET Framework that your CSI installation uses. How to find your CSI's .NET version: This is usually documented for your specific CSI release. For newer CSI versions (e.g., CSI 10.x and beyond), it's commonly .NET Framework 4.8. You can also often find this by inspecting the properties of core CSI DLLs (like IDOCore.dll) in your CSI installation folder. Installation: You'll need to download and install the corresponding .NET Framework Developer Pack (SDK) from Microsoft's website.
Infor CSI (SyteLine) Installation/Client: You need a working CSI environment to deploy and test your custom assemblies. This could be: A local installation on your development machine (common for initial learning). Access to a development/test environment on a server. Why it's essential: You can't run or test your assembly in isolation; it needs the CSI application to interact with.
CSI Core Assemblies (DLLs): These are dynamic-link library files that are part of the CSI application itself. Your custom assembly needs to "know about" these CSI components to communicate with them. Location: You'll find these in your CSI installation directory, typically under C:\Program Files\Infor\CSI or C:\Program Files (x86)\Infor\CSI.
Key DLLs you'll always reference:
IDOCore.dll: Contains fundamental classes for IDO interactions.
IDOBase.dll: Provides base classes for IDOs and their methods.
IDOProtocol.dll: Defines the communication protocols for IDO methods.
MGShared.dll: Contains shared utilities and common CSI components.
ApplicationDB.dll: Crucial for interacting with the CSI database.
WSEnums.dll: Contains various enumeration types used by CSI.
Why they're essential: When you write code like using Mongoose.IDO; or ApplicationDB.SQLAppDB, your Visual Studio project needs to know where to find these definitions, which are provided by these CSI DLLs.
Source Control System (Highly Recommended): Tools like Git (often used with platforms like GitHub, GitLab, or Azure DevOps) help you manage changes to your code, revert to previous versions, and collaborate with other developers. Why it's essential: Prevents accidental loss of code, simplifies debugging by seeing what changed, and is indispensable for teamwork.
2. Simple Requirement Example Let's imagine a common scenario that previously might have been handled by a SQL Server stored procedure: automatically setting a default value or performing a simple calculation when a record is created or updated. Requirement: "When a new Item record (representing a product) is created in CSI, if the Weight field is left blank, automatically set its default value to 1.0 and log a message indicating that the default weight was applied." Why this is a good example:
It involves a specific CSI business object (Item).
It demonstrates conditional logic (checking if Weight is blank).
It shows setting a property (Weight).
It includes a simple logging mechanism (writing a message).
CREATE TRIGGER trg_InsertItemWeightDefault ON Item AFTER INSERT
AS BEGIN
UPDATE I SET Weight = 1.0 FROM Item I
INNER JOIN INSERTED AS INS ON I.Item = INS.Item
WHERE INS.Weight IS NULL OR INS.Weight = 0; -- Assuming 0 means blank/unentered for numeric
END;
It's a common need that avoids relying solely on database defaults. Conceptual SQL Server Stored Procedure Equivalent (if one were used to handle this logic on an insert trigger): -- This is just a conceptual example, not how you'd typically manage triggers -- directly in SyteLine, but it illustrates the logic being replaced.
3. Sample Code (C#) Now, let's create the C# custom assembly code for our "default item weight" requirement. Step-by-Step in Visual Studio:
Create New Project: Open Visual Studio. Select File > New > Project... Search for "Class Library (.NET Framework)" and select it.
Click "Next". Project Name: CSI.ItemExtensions (or similar, choose a descriptive name). Location: Choose a sensible folder for your projects. Framework: Crucially, select the .NET Framework version that matches your CSI environment (e.g., .NET Framework 4.8). Click "Create".
Add References: In the Solution Explorer (usually on the right side of Visual Studio), right-click on "References" under your new project (CSI.ItemExtensions). Select "Add Reference...". In the "Reference Manager" dialog, click on the "Browse" tab. Click the "Browse..." button and navigate to your CSI installation directory (e.g., C:\Program Files\Infor\CSI). Select the core CSI DLLs mentioned earlier (IDOCore.dll, IDOBase.dll, IDOProtocol.dll, MGShared.dll, ApplicationDB.dll, WSEnums.dll). Click "Add" and then "OK".
Write the C# Code: A default Class1.cs file will be created. Rename it (right-click on Class1.cs in Solution Explorer, select "Rename") to something like ItemExtensionClass.cs. Replace the contents of ItemExtensionClass.cs with the following code:
· using Mongoose.IDO; // For IDO and IDOExtensionClass
· using Mongoose.IDO.Protocol; // For MethodResponseType, PropertyType etc.
· using Mongoose.IDO.Security; // For security-related items (less common for simple cases)
· using System; // Standard .NET types
· using System.Linq; // For LINQ queries (e.g., .Where(), .Take() if needed)
·
· // This 'namespace' helps organize your code and prevents naming conflicts.
· // It should typically align with your project name.
· namespace CSI.ItemExtensions
· {
· // The IDOExtensionClassAssembly attribute is ESSENTIAL.
· // It tells CSI that this DLL contains IDO extension classes and assigns a unique name
· // that CSI will use to identify this particular assembly.
· // This name (e.g., "CSIItemExtensionAssembly") must be unique in your CSI system.
· [IDOExtensionClassAssembly("CSIItemExtensionAssembly")]
· public class ItemExtensionClass : IDOExtensionClass // Your class MUST inherit from IDOExtensionClass
· {
· // This method will be called by CSI after an Item record is inserted.
· // IDO methods typically follow a pattern where the first parameter is an
· // IDO method request, and the second is the response.
· // We'll use MethodFlags.CustomUpdate even for an insert-time modification
· // because we are changing data.
· [IDOMethod(MethodFlags.CustomUpdate)]
· public MethodResponseType IDO_Item_PostIns(
· LoadCollectionRequestData request,
· LoadCollectionResponseData response)
· {
· // Always initialize a MethodResponseType object to return.
· // This is how you tell CSI about the success or failure of your method,
· // and optionally return data or messages.
· MethodResponseType methodResponse = new MethodResponseType
· {
· AppliesTo = MessageType.Info // Default to informational message
· };
·
· try
· {
· // We're expecting only one row since this is a PostIns event for a single item.
· // It's good practice to check if there are any items in the response.
· if (response.Items != null && response.Items.Count > 0)
· {
· // Access the first (and likely only) item that was just inserted.
· IDOItem item = response.Items[0];
·
· // Check if the 'Weight' property exists and if its value is null or empty.
· // CSI properties are accessed via item.Properties["PropertyName"].Value.
· // Numeric fields might come as null, empty string, or 0.
· // We use double.TryParse to safely convert and check.
· if (item.Properties.Contains("Weight") &&
· (string.IsNullOrWhiteSpace(item.Properties["Weight"].Value) ||
· (double.TryParse(item.Properties["Weight"].Value, out double currentWeight) && currentWeight == 0.0)))
· {
· // Set the 'Weight' property to our default value.
· // Ensure the value is set as a string, as IDO properties are string-based.
· item.Properties["Weight"].Value = "1.0";
·
· // You can add messages that will appear in the CSI message log.
· // This is excellent for debugging and tracing.
· methodResponse.Message = $"Item '{item.Properties["Item"].Value}' had no weight specified. Defaulted to 1.0.";
· methodResponse.AppliesTo = MessageType.Info; // Could also be Warning or Error
· }
· else
· {
· // If weight was provided, just log that it was used.
· methodResponse.Message = $"Item '{item.Properties["Item"].Value}' created with weight: {item.Properties["Weight"].Value}.";
· methodResponse.AppliesTo = MessageType.Info;
· }
· }
· else
· {
· methodResponse.Message = "IDO_Item_PostIns called, but no items found in response.";
· methodResponse.AppliesTo = MessageType.Warning;
· }
· }
· catch (Exception ex)
· {
· // Always include robust error handling.
· // If an unhandled exception occurs, CSI will often show a generic error.
· // By catching it and setting the response, you provide a clear message.
· methodResponse.AppliesTo = MessageType.Error;
· methodResponse.Message = $"An error occurred in IDO_Item_PostIns: {ex.Message}";
· // You might also want to log the full exception details to a file for deeper analysis.
· }
·
· // Return the response object.
· return methodResponse;
· }
· }
· }
·
Explanation of the Code:
using statements: These bring in the necessary namespaces, allowing you to use classes like IDOExtensionClass, MethodResponseType, etc., without typing their full names.
namespace CSI.ItemExtensions: This defines a logical grouping for your code. It helps organize your projects, especially when you have multiple assemblies.
[IDOExtensionClassAssembly("CSIItemExtensionAssembly")]: This is a critical attribute. It tells CSI that this .dll file contains IDO extension classes and assigns a unique name (CSIItemExtensionAssembly in this case) by which CSI will refer to this assembly. Make sure this name is unique across your CSI system.
public class ItemExtensionClass : IDOExtensionClass: public class ItemExtensionClass: Defines your class. The name is descriptive of its purpose. : IDOExtensionClass: Your class must inherit from Mongoose.IDO.IDOExtensionClass. This is the base class that provides the framework for CSI to interact with your code.
[IDOMethod(MethodFlags.CustomUpdate)]: This is another critical attribute. It marks a method as an IDO method that CSI can call. MethodFlags.CustomUpdate: This flag indicates that the method will potentially modify data. For an "after insert" or "after update" trigger, CustomUpdate is generally appropriate even if you're just setting defaults. Other common flags include CustomLoad (for retrieving data) and CustomDelete (for actions after deletion).
public MethodResponseType IDO_Item_PostIns(...): Method Name: CSI often expects specific naming conventions for its "hooks." IDO_Item_PostIns means: IDO_: Standard prefix for IDO methods. Item: The IDO name (the Mongoose name for the Item business object). PostIns: The specific "hook" point – this method will be called after an Insert operation on the Item IDO. Other common hooks include PreIns (before insert), PostUpdate (after update), PreUpdate (before update), PreSave (before any save), PostSave (after any save), etc. Parameters: LoadCollectionRequestData request: Contains information about the IDO operation that triggered the method (e.g., filters, properties requested). LoadCollectionResponseData response: Contains the data that was just inserted/updated. This is where you'll find the IDOItem objects to modify. Return Type: All IDO methods must return a MethodResponseType. This object communicates the outcome of your method back to CSI.
response.Items[0]: Accesses the first item in the response collection. Since PostIns is triggered for a single item insertion, there will usually be only one item.
item.Properties["Weight"].Value: This is how you access the properties (fields) of an IDO item within your C# code. Note that the .Value property is always a string, regardless of the underlying data type in the database. You'll need to parse it (e.g., double.TryParse) if you need to perform numeric operations.
methodResponse.Message: This property allows you to send a message back to the CSI client. This message will typically appear in the CSI message log (bottom of the CSI client) and is invaluable for debugging and informing users.
methodResponse.AppliesTo: This sets the type of message (e.g., MessageType.Info, MessageType.Warning, MessageType.Error). This affects how the message is displayed to the user. If you set it to Error, the transaction will often be rolled back.
try...catch block: Always wrap your core logic in a try...catch block. If your code throws an unhandled exception, CSI might crash or show a generic error. By catching exceptions, you can log meaningful error messages and return a clean MethodResponseType with MessageType.Error.
4. Development Methodology/Process/Typical Workflow Here's a step-by-step workflow for developing and integrating your custom assemblies into Infor CSI: Phase 1: Setup and Development (In Visual Studio)
Define the Requirement (Crucial First Step): Before writing any code, clearly understand what you want the assembly to do, when it should happen (e.g., before/after save, on a button click), and which CSI business object (IDO) it relates to.
Design the IDO Method: Identify the IDO: Which CSI object does this relate to (e.g., SLItems, SLCustomers, SLAps etc.)? Choose the Hook Point: PreIns, PostIns, PreUpdate, PostUpdate, PreSave, PostSave, or a CustomLoad method for fetching data, or CustomUpdate for a specific action initiated from a form. For our example, PostIns is suitable for defaulting. Determine Method Signature: What parameters will your C# method take? What will it return (MethodResponseType is standard)?
Create Visual Studio Project: Launch Visual Studio. File > New > Project... > Class Library (.NET Framework). Select the correct .NET Framework version. Name your project (e.g., CSI.ItemExtensions).
Add References: Right-click References in your project. Add Reference... > Browse. Navigate to your CSI installation folder (e.g., C:\Program Files\Infor\CSI). Select IDOCore.dll, IDOBase.dll, IDOProtocol.dll, MGShared.dll, ApplicationDB.dll, WSEnums.dll. Click Add, then OK.
Write C# Code: Rename Class1.cs to a more descriptive name (e.g., ItemExtensionClass.cs). Implement your logic in the C# file, ensuring you inherit from IDOExtensionClass and use the [IDOExtensionClassAssembly] and [IDOMethod] attributes correctly. Best Practices for Code: Keep it clean: Use meaningful variable names. Error Handling: Use try...catch blocks to gracefully handle exceptions and provide informative messages. Logging: Use methodResponse.Message to send messages to the CSI client's message log. For more advanced logging, consider writing to a file on the server. Database Interactions: Always use ApplicationDB.SQLAppDB for database access to ensure you're within the CSI transaction context.
Build the Project: In Visual Studio, go to Build > Build Solution (or Ctrl+Shift+B). If there are errors, Visual Studio will highlight them in the Error List. Fix them and rebuild. A successful build will create your .dll file (e.g., CSI.ItemExtensions.dll) and a corresponding .pdb file (Program Database, for debugging) in your project's bin\Debug (or bin\Release) folder. Phase 2: Deployment and Integration (In Infor CSI Client)
Log into CSI: Open your Infor CSI client with a user account that has administrative privileges (typically, the sa user or an equivalent).
Go to IDO Extension Class Assemblies Form: Navigate to System > Agency Personalization > IDO Extension Class Assemblies. This form is where you register your compiled C# assemblies with CSI.
Add Your Assembly: Click "New" to create a new record. Assembly Name: Enter the exact name you used in the [IDOExtensionClassAssembly("YourAssemblyName")] attribute in your C# code (e.g., CSIItemExtensionAssembly). This must match exactly. Import Assembly: Click the "Import Assembly" button (or browse to the .dll field) and select your compiled .dll file from your Visual Studio project's bin\Debug (or bin\Release) folder (e.g., CSI.ItemExtensions.dll). Import Symbols: Crucially, click the "Import Symbols" button and select the corresponding .pdb file (e.g., CSI.ItemExtensions.pdb). This enables debugging. Save the record.
Associate Assembly with an IDO: Navigate to the IDOs form: System > Agency Personalization > IDOs. Find the IDO your assembly extends (e.g., search for Item). Custom Assembly Name: On the IDO's general tab, find the "Custom Assembly Name" field and select your registered assembly name from the dropdown (e.g., CSIItemExtensionAssembly). Ext Class Name: Enter the fully qualified name of your class (including its namespace) as defined in your C# code (e.g., CSI.ItemExtensions.ItemExtensionClass). Save the IDO record.
Define the Custom Method on the IDO: Still on the IDOs form, go to the "Methods" tab for your selected IDO (Item). Click "New" to add a new method record. Method Name: This must exactly match the C# method name in your code (e.g., IDO_Item_PostIns). Method Type: Select "Custom Method". Parameters: If your custom method takes parameters (like our example's LoadCollectionRequestData and LoadCollectionResponseData), they are usually handled automatically for standard hooks. For custom methods you call manually, you'd define them here. Save the method record.
Refresh CSI Metadata Cache: This is absolutely vital! CSI caches its definitions. If you don't refresh the cache, your changes won't be recognized. Go to View > User Preferences and ensure "Unload IDO Metadata With Forms" is checked. Then, go to Form > Definition > Unload All Global Form Objects. This clears CSI's internal memory of IDO definitions and forces it to reload them, including your new assembly. Restarting the CSI client (and potentially the IDO Runtime service on the server) is often the safest bet for ensuring all caches are cleared. Phase 3: Testing and Debugging
Functional Testing: Open the relevant form in CSI (e.g., the Items form). Perform the action that should trigger your custom assembly (e.g., create a new item, leaving the weight blank). Check the results: Is the weight defaulted? Is there a message in the CSI message log?
Debugging (On-Premise Only): This is where the .pdb file shines! Stop IDO Runtime Service: On the CSI application server (or your local machine if running a full CSI client and server stack locally), stop the "Infor CSI IDO Runtime Service" (sometimes called "SyteLine Application Server" service). This frees up the DLLs. Run IDO Runtime Development Server: Navigate to your CSI installation directory (e.g., C:\Program Files\Infor\CSI\IDORuntime) and manually launch IDORuntimeHost.exe. This starts a special debugging instance of the IDO runtime. Attach to Process in Visual Studio: In Visual Studio, go to Debug > Attach to Process.... In the "Attach to Process" dialog: Ensure "Attach to:" is set to "Managed (v4.x) code" (or "Managed (v4.6, v4.5, v4.0)" or similar, depending on your .NET Framework version). Find IDORuntimeHost.exe in the list of available processes and select it. Click "Attach". Visual Studio is now "listening" to your custom assembly when CSI calls it. Set Breakpoints: In your C# code in Visual Studio, click in the gray margin next to a line of code to set a red circle breakpoint. Trigger from CSI: Go back to your CSI client and perform the action that triggers your custom method (e.g., save a new item). Debug! Execution should pause at your breakpoint in Visual Studio. You can now: Step through your code line by line (F10 or F11). Hover over variables to see their values. Inspect the Call Stack. Use the Immediate Window or Watch Window to evaluate expressions. Detach and Restart: Once you're done debugging, stop execution in Visual Studio, detach from the process (Debug > Detach All), close IDORuntimeHost.exe, and then restart the "Infor CSI IDO Runtime Service" on the server to restore normal operations. Cloud Debugging: Direct process attachment is generally not possible in Infor CSI Cloud environments. You'll rely heavily on logging (e.g., methodResponse.Message to the CSI client log, or writing to a dedicated log file on the server) and error messages for troubleshooting.
Version Control: After successful testing, commit your changes to your source control system (e.g., Git). This creates a snapshot of your working code. Phase 4: Maintenance and Updates
Modify Code: If you need to make changes, open your Visual Studio project, modify the C# code.
Rebuild: Compile the project again (Ctrl+Shift+B).
Re-deploy: Go back to the IDO Extension Class Assemblies form in CSI. Select your existing assembly record and use the "Import Assembly" and "Import Symbols" buttons to upload the new .dll and .pdb files, overwriting the old ones.
Refresh Metadata: Perform the Unload All Global Form Objects and restart CSI client as before.
By following this detailed methodology, even as a beginner, you'll be well-equipped to create, deploy, and troubleshoot custom assemblies in Infor CSI. Good luck!