Skip to content
Gennady Verdel edited this page Jul 17, 2016 · 4 revisions

Purpose

The Solid library addresses different aspects in applications development. The most prominent are: Inversion-Of-Control, Modularity, Extensibility via Middleware and Composition.

The Inversion-Of-Control aspect is addressed by defining an abstraction of an IoC container which can be implemented using any existing or even new container. It's important to stress that the user is not limited to this abstract API. The abstraction is created to support different bootstrapping and modular functionality.

The Modularity aspect addresses the cases where there's need to add functionality during app initialization without having to include it into the initialization root thus preserving the encapsulation. There are various sorts of functionality that can be added using this approach: registering dependencies into the IoC container, initializing third-party engines, etc.

The Extensibility aspect is addressed by defining an abstraction for reusable piece of functionality that is attached to a certain object.This abstraction is represented by the following interface IMiddleware which defines the Apply method in a fluent fashion.

The Composition aspect is the only one that has concrete implementation. In essense, it provides a unified way of composing application blocks, including assemblies and composition modules, for further consumption.

Additionally, the Solid library contains various interfaces and implementation for some of the Design Patterns, including IAcceptor for the Visitor pattern, IMemento for the Memento pattern and so on. This saves a lot of duplicate code when such a pattern is used in the app development.

Inversion-Of-Control

The Inversion-of-Control component is a vital part of all mid and large-scale projects. When implemented correctly it provides great value to the developers and lays foundation for extensibility and testability. Originally it included three explicit stages: Register-Resolve-Release. Nowadays the Release stage is rarely invoked explicitly, the Resolve stage should not be invoked at all in the app code as it ultimately leads to the ServiceLocator anti-pattern (with few exceptions). The Register stage is the one where all dependencies are wired up.

There are two main interfaces in the IoC component of the Solid framework

IIocContainerRegistrator

and

IIocContainerResolver

The IIocContainerRegistrator contains various options for registering dependencies, both in generic and type-explicit way. It is not meant to include all possible lifetime styles and registration options because that would result in a very large God-class like interface. Instead it focuses on the most common cases that are used in the application development, both at the client and the server.

The IIocContainerResolver contains methods for resolving dependencies. I would like to stress again that this functionality should be used only after a careful thought. The main usage patterns for this use case are the application root and the dynamically created objects which require external dependencies (in this case a factory pattern is used).

There are also a couple of marker interfaces which are used for the adapters to the existing IoC containers.

Extensibility

There are cases when a certain object's functionality needs to be extended. Preferably this should be achieved without modifying its current implementation and thus preserving the Open-Close principle. There is an existing extension mechanism in .NET - using static classes and static methods. The Solid adds a solution for the case where the functionality needs to be added and invoked somewhat later in the object's lifetime. So, in a sense, it's an implementation of the Builder pattern without the Build method.

The basic unit of encapsulation in this case is the middleware which is modeled by the IMiddleware<T>. Naturally, there's also an interface for containing the middlewares which also marks the object as an extensible one. It's called IExtensible<T>.

Consider the following example where we have an existing bootstrapper that exposes certain interface:

public class BootstrapperBase : IBootstrapper, IExtensible<IBootstrapper>
{
///some existing implementation
}

We could, of course, write an extension method to attach new functionality. But in this case we would like to defer the invocation of the functionality and store it inside the class to be invoked later. In such case we add a new middleware:

    /// <summary>
    /// Initializes view locator.
    /// </summary>    
    public class InitializeViewLocatorMiddleware : IMiddleware<IBootstrapper>
    {
        /// <summary>
        /// Applies the middleware on the specified object.
        /// </summary>
        /// <param name="object">The object.</param>
        /// <returns></returns>
        public IBootstrapper Apply(IBootstrapper @object)
        {
            InitializeViewLocator(@object);
            return @object;
        }

        private readonly Dictionary<string, Type> _typedic = new Dictionary<string, Type>();

        private void InitializeViewLocator(IBootstrapper bootstrapper)
        {
            //overriden for performance reasons (Assembly caching)
            ViewLocator.LocateTypeForModelType = (modelType, displayLocation, context) =>
            {
                var viewTypeName = modelType.FullName.Substring(0, modelType.FullName.IndexOf("`") < 0
                    ? modelType.FullName.Length
                    : modelType.FullName.IndexOf("`")
                    ).Replace("Model", string.Empty);

                if (context != null)
                {
                    viewTypeName = viewTypeName.Remove(viewTypeName.Length - 4, 4);
                    viewTypeName = viewTypeName + "." + context;
                }

                Type viewType;
                if (!_typedic.TryGetValue(viewTypeName, out viewType))
                {
                    _typedic[viewTypeName] = viewType = (from assembly in bootstrapper.Assemblies
                                                         from type in assembly
#if NET45
                                                         .GetExportedTypes()
#endif
#if WINDOWS_UWP || NETFX_CORE || WIN81
                                                         .DefinedTypes
#endif
                                                         where type.FullName == viewTypeName
                                                         select type
#if WINDOWS_UWP || NETFX_CORE || WIN81
                                                         .AsType()
#endif
                                                         ).FirstOrDefault();
                }

                return viewType;
            };
            ViewLocator.LocateForModelType = (modelType, displayLocation, context) =>
            {
                var viewType = ViewLocator.LocateTypeForModelType(modelType, displayLocation, context);

                return viewType == null
                    ? new TextBlock
                    {
                        Text = $"Cannot find view for\nModel: {modelType}\nContext: {context} ."
                    }
                    : ViewLocator.GetOrCreateViewType(viewType);
            };
        }
    }    

Here we use the exposed property Assemblies to attach new functionality and store it inside the existing class. Then we can invoke it when we want. Remember the missing Build method?

Modularity

As the applications evolve they start to include more different concerns and third-party components. Frequently you would like to decouple the domain and application interfaces from their actual implementation. This means you will have to encapsulate all component-related activities in one assembly and this will eventually include the third-party engine initialization and/or dependency registrations. The modularity comes to your rescue. There are three kinds of modules: ICompositionModule<TIocContainer> which allows registering dependencies into the passed ioc container abstraction. IPlainCompositionModule which allows parameterless registrations; primarily used for third-party engine initialization. IHierarchicalCompositionModule - allows injection collection of other modules; rarely used, mostly in the presentation layer.

Clone this wiki locally