Component model

General information

ELMA component model allows you to extend any system functions. For example, add menu items or buttons and process data.

Component model includes:

  • Extendable module –ELMA module with a set of extension points;
  • Extension point – any interface with properties/methods that is marked with ELMA.ComponentModel.ExtensionPointAttribute attribute;
  • Component – an instance of the class that implements the extension point (interface) and is marked with the ComponentAttribute attribute.

 Component model

To initialize and work with ELMA core, there is EleWise.ELMA.ComponentModel.ComponentManager class. It allows you to initialize the component model, register and receive components. When we start the ELMA server, the component manager loads the assemblies marked with the EleWise.ELMA.ComponentModel.ComponentAssemblyAttribute attribute and checks if they contain any components.

Base classes for working with the component model are located in the EleWise.ELMA.SDK assembly in the EleWise.ELMA.ComponentModel namespace.

 What types does a component model use?

  • ExtensionPointAttribute – we use this attribute to declare extension points;
  • ComponentManager – we use this manager to access the loaded components;
  • ComponentAssemblyAttribute – we use this attribute to declare assemblies with components;
  • ComponentAttribute – we use this attribute to declare components;
  • IIniteHnadler – we use this extension point to process component’s loading.

How to create extension points?

To create an extension point, you need to create an interface and mark it with the EleWise.ELMA.ComponentModel.ExtensionPointAttribute attribute:

namespace EleWise.ELMA.ComponentModel
{
 
/// <summary>
/// Attribute of the extension point’s interface
/// </summary>
[AttributeUsage(AttributeTargets.Interface)]
public class ExtensionPointAttribute : Attribute
{
 
/// <summary>
/// Extension point with the lifecycle type = Application and with registration of component instances
/// </summary>
public ExtensionPointAttribute();
 
/// <summary>
/// Extension point with the specified type of components’ registration (either registration of component types or registration of component instances)
/// </summary>
/// <param name="createInstance">If set to false, then only types of components that implement this extension point will be registered</param>
public ExtensionPointAttribute(bool createInstance);
 
/// <summary>
/// Extension point with specified lifecycle type
/// </summary>
/// <param name="serviceScope">Lifecycle type of components that implement this extension point</param>
public ExtensionPointAttribute(ServiceScope serviceScope);
 
/// <summary>
/// Whether to create component instances
/// </summary>
/// <remarks>
/// True, if we need to create instances (you can access them using IComponentManager.GetExtensionPoints method)
/// False, if we only need to load their types (you can access them using IComponentManager.GetExtensionPointTypes method)
/// </remarks>
public bool CreateInstance { get; }
 
/// <summary>
/// Lifecycle type. Components that implement this extension point will be created and registered with this lifecycle type
/// Applcation – registration at the application level (before initialization of IInitHandler.Init) - one instance per application
/// Shell – registration at the container level (container is recreated after enabling/disabling extensions) - one instance per container
/// Transient – registration at the container level - one instance per each request from the container
/// UnitOfWork - registration at the container level – one instance per each UnitOfWork (in Web - one instance per each HTTP request)
/// </summary>
public ServiceScope ServiceScope { get; }
}
 
}

Below is an example:

/// <summary>
/// Extension point for menu
/// </summary>
[ExtensionPoint]
public interface IMenuExtension
{
 
/// <summary>
/// Get menu items
/// </summary>
MenuItem[] GetMenuItems(); 
 
}

How to create components?

To create a component, you must check if the assembly of this component has the EleWise.ELMA.ComponentModel.ComponentAssemblyAttribute attribute. If there is no such attribute, you must add it:

[assembly: EleWise.ELMA.ComponentModel.ComponentAssembly]

Then you need to create a class, implement the extension point’s interface and mark it with EleWise.ELMA.ComponentModel.ComponentAttribute attribute:

namespace EleWise.ELMA.ComponentModel
{
 
/// <summary>
/// Attribute of component that implements extension points
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = false)]
public class ComponentAttribute : Attribute
{
 
/// <summary>
/// Order of components that implement the extension point
/// </summary>
[DefaultValue(0)]
public int Order { get; set; }
 
/// <summary>
/// Automatically initialize properties’ values (using Autofac container)
/// </summary>
[DefaultValue(true)]
public bool InjectProerties { get; set; }
 
/// <summary>
/// Use method interceptors for the class
/// </summary>
[DefaultValue(false)]
public bool EnableInterceptiors { get; set; }
 
}
 
}

Below is an example:

/// <summary>
/// Component that implements IMenuExtension extension point
/// </summary>
[Component]
public class CustomMenuExtension : IMenuExtension
{
 
public MenuItem[] GetMenuItems()
{
...
}
 
}

Component manager

A component manager is created when the server starts. You can access it using the static property ComponentManager.Current.

namespace EleWise.ELMA.ComponentModel
    {
 
/// <summary>
/// Component manager
/// </summary>
public class ComponentManager : IComponentManager, IDisposable
{
 
/// <summary>
/// Lifecycle stage
/// </summary>
public enum LifetimeStage
{
/// <summary>
/// before initialization
/// </summary>
BeforeInit,
 
/// <summary>
/// When IInitHandler.Init is called
/// </summary>
Initializing,
 
/// <summary>
/// When IInitHandler.InitComplete is called
/// </summary>
InitCompleting,
 
/// <summary>
/// After initialization
/// </summary>
Initialized,
 
/// <summary>
/// Disposed
/// </summary>
Disposed
}
 
 
/// <summary>
/// Receive the current manager
/// </summary>
public static ComponentManager Current
{
get;
}
 
/// <summary>
/// Whether initialized or not
/// </summary>
public static bool Initialized
{
get;
}
 
/// <summary>
/// Current IoC container (available at the beginning of initialization in the IInitHandler.Init method)
/// </summary>
public static ContainerBuilder Builder
{
get;
}
 
/// <summary>
/// Lifecycle stage
/// </summary>
public LifetimeStage Stage
{
get;
}
 
/// <summary>
/// Register an existing component. Method is available at the BeforeInit and Initializing stages
/// </summary>
/// <param name="component">Component</param>
public ComponentManager RegisterComponent(object component);
 
/// <summary>
/// Register the assembly where components will be searched for. The method is available at the BeforeInit and Initializing stages
/// </summary>
/// <param name="assembly">Assembly </param>
public ComponentManager RegisterAssembly(Assembly assembly);
 
/// <summary>
/// Returns the component of a specific type. The method is available at the Initializing, InitCompleting and Initialized stages
/// </summary>
/// <typeparam name="T">Extension type</typeparam>
/// <returns>Extension instance</returns>
public T GetExtensionPointByType<T>();
 
/// <summary>
/// Returns the component of a specific type. The method is available at Initializing, InitCompleting and Initialized stages.
/// </summary>
/// <param name="type">Extension type</param>
/// <returns>Extension instance</returns>
public object GetExtensionPointByType(Type type);
 
/// <summary>
/// Returns the components that implement extension point. The method is available at the Initializing, InitCompleting and Initialized stages
/// </summary>
/// <typeparam name="T">Type of the extension point’s interface</typeparam>
/// <returns></returns>
public IEnumerable<T> GetExtensionPoints<T>();
 
/// <summary>
/// Returns the components that implement the extension point. The method is available at the Initializing, InitCompleting and Initialized stages
/// </summary>
/// <param name="type">Type of the extension point’s interface</param>
/// <returns></returns>
public IEnumerable<object> GetExtensionPoints(Type type);
 
/// <summary>
/// Returns the types of components that implement the extension point’s interface
/// </summary>
/// <param name="type">Type of the extension point’s interface</param>
/// <returns>List of the component types. If not found, the empty list is returned</returns>
public IEnumerable<Type> GetExtensionPointTypes(Type type);
 
/// <summary>
/// Returns the types of the components that implement the extension point’s interface
/// </summary>
/// <typeparam name="T">Type of the extension point’s interface</typeparam>
/// <returns>List of components</returns>
public IEnumerable<Type> GetExtensionPointTypes<T>();
 
/// <summary>
/// Returns the list of the components that implement the extension point’s interface
/// </summary>
/// <param name="type">Type of the extension point’s interface</param>
/// <returns>List of component types. If not found, the empty list is returned.</returns>
public Type[] GetExtensionPointTypesArray(Type type);
 
/// <summary>
/// Get types that implement the IXsiType interface.
/// </summary>
/// <returns></returns>
public Type[] GetXsiTypes();
 
/// <summary>
/// Get the array of all components that are registered in the manager. The method is available at the Initializing, InitCompleting and Initialized stages
/// </summary>
/// <returns>Array of components</returns>
public object[] GetComponents();
 
}
 
}

Below is the description of the component manager initialization:

  1. Load all the assemblies (*.dll or *.exe) from the folder of the application that was started (web application or ELMA Designer);
  2. Select all the assemblies with the ComponentAssemblyAttribute attribute and also the assemblies added with the RegisterAssembly method;
  3. Select the classes marked with the ComponentAttributeилиServiceAttribute attribute from these assemblies. Also select the objects that were added using the RegisterComponent method;
  4. Register the classes from the 3rd step in the Autofac container:
    • Classes with the ComponentAttribute attribute are registered with the class type and with all types of extension points that the class implements;
    • Classes with the ServiceAttribute attribute are registered with the class type and with all interfaces that the class implement.
  5. Select the components that implement the IInitHandler extension point. For each of them the Init() method is called;
  6. Update the Autofac container with the components from the 5th step;
  7. Select the components that implement the IInitHandler extension point. For each of them the InitComplete() method is called;
  8. Initialization is completed.

IInitHandler extension point

The EleWise.ELMA.ComponentModel.IInitHandler extension point allows you to subscribe to the start (Init) and end events (InitComplete) of the component manager’s initialization.

namespace EleWise.ELMA.ComponentModel
{
 
/// <summary>
/// Interface of the component that supports initialization methods
/// </summary>
[ExtensionPoint]
public interface IInitHandler
{
 
/// <summary>
/// Starting initialization (the ComponentManager.Current and ComponentManager.Builder properties can be used)
/// </summary>
void Init();
 
/// <summary>
/// Finishing initialization (Locator is available)
/// </summary>
void InitComplete();
 
}
 
}

The Init() method allows you to work with the current component manager (the static property ComponentManager.Current) and with the builder of the Autofac container (the static property ComponentManager.Builder). For more information, please see ELMA core architecture.

The InitComplete method allows you to work with the current component manager and with the Locator class (for more information, please see ELMA core architecture).

How to get components?

There are three methods to get the components that implement a particular extension point (say, IMenuExtension):

    1. Auto-injected properties

If you need to get components from an object (e.g. MenuController) placed in the Autofac container (see ELMA core architecture), then you can simply declare the property:

public class MenuController : Controller
{
...
 
public IEnumerable<IMenuExtension> MenuExtensions { get; set; }
 
...
}

When you create the MenuController object, the MenuExtensions property will be filled automatically, if at least one component with the implementation of the extension point IMenuExtension is registered. Otherwise, the property will be Null.

     2. Using component manager

using EleWise.ELMA.Services;
 
public class SomeClass
{
 
public void SomeMethod()
{
var menuExtensions = ComponentManager.Current.GetExtensionPoints<IMenuExtension>();
if (menuExtensions != null)
{
...
}
}
 
}

      3. Using Locator class

Please read ELMA core architecture for more information about the Locator class. 

using EleWise.ELMA.Services;
 
public class SomeClass
{
 
public void SomeMethod()
{
var menuExtensions = Locator.GetService<IEnumerable<IMenuExtension>>();
if (menuExtensions != null)
{
...
}
}
 
}

 See also: