Improving our application framework

Another area that you can continue to work on is customizing our application framework further and adapting it to your individual requirements. With this in mind, you could continue to build up a complete collection of customized controls with a particular look and feel in an external resource file to use in all of your applications.

There are also many other examples provided throughout this book that could be easily extended. For example, you could update our DependencyManager class to enable multiple concrete classes to be registered for each interface.

Instead of using a Dictionary<Type, Type> object to store our registrations, you could define new custom objects. You could declare a ConcreteImplementation struct that has a Type property and an object array to hold any constructor input parameters that may be required for its initialization:

public ConcreteImplementation(Type type, 
  params object[] constructorParameters) 
{ 
  Type = type; 
  ConstructorParameters = constructorParameters; 
} 

You could then declare a DependencyRegistration class that you could use to pair the interface type with the collection of concrete implementations:

public DependencyRegistration(Type interfaceType,  
  IEnumerable<ConcreteImplementation> concreteImplementations) 
{ 
  if (!concreteImplementations.All(c =>
    interfaceType.IsAssignableFrom(c.Type)))
    throw new ArgumentException("The System.Type object specified by the 
    ConcreteImplementation.Type property must implement the interface type 
    specified by the interfaceType input parameter.", 
    nameof(interfaceType));
  ConcreteImplementations = concreteImplementations; 
  InterfaceType = interfaceType; 
} 

In our DependencyManager class, you could change the type of the registeredDependencies field to a collection of this new DependencyRegistration type. The current Register and Resolve methods could then also be updated to use this new collection type.

Alternatively, you could include other common functionality that is contained within popular Dependency Injection and Inversion of Control containers, such as the automatic registering of concrete classes to interfaces at the assembly level. For this, you could use some basic reflection:

using System.Reflection;

...

public void RegisterAllInterfacesInAssemblyOf<T>() where T : class 
{ 
  Assembly assembly = typeof(T).Assembly; 
  IEnumerable<Type> interfaces = 
    assembly.GetTypes().Where(p => p.IsInterface); 
  foreach (Type interfaceType in interfaces) 
  { 
    IEnumerable<Type> implementingTypes = assembly.GetTypes().
      Where(p => interfaceType.IsAssignableFrom(p) && !p.IsInterface); 
    ConcreteImplementation[] concreteImplementations = implementingTypes.
      Select(t => new ConcreteImplementation(t, null)).ToArray();       
    if (concreteImplementations != null && concreteImplementations.Any()) 
      registeredDependencies.Add(interfaceType, concreteImplementations); 
  } 
} 

This method first accesses the assembly that contains the generic type parameter and then gets a collection of the interfaces in that assembly. It then iterates through the interface collection and finds a collection of classes that implements each interface, instantiating a ConcreteImplementation element with each. Each match is added into the registeredDependencies collection with its relating interface type.

In this way, you could pass any interface type from our Models, Managers, and ViewModels projects to automatically register all of the interfaces and concrete classes found inside their assemblies. There is a clear benefit to doing this in larger applications, as it will mean that you don't have to manually register each type:

private void RegisterDependencies() 
{ 
  DependencyManager.Instance.ClearRegistrations(); 
  DependencyManagerAdvanced.Instance. 
    RegisterAllInterfacesInAssemblyOf<IDataProvider>(); 
  DependencyManagerAdvanced.Instance. 
    RegisterAllInterfacesInAssemblyOf<IUiThreadManager>(); 
  DependencyManagerAdvanced.Instance. 
    RegisterAllInterfacesInAssemblyOf<IUserViewModel>(); 
} 

Additionally, you could declare another method that registers all types found in the assembly of the type specified by the generic type parameter T, where matches of implemented interfaces are found. This could be used during testing, so that you could just pass any type from the mock projects during testing, again saving time and effort:

DependencyManager.Instance.
  RegisterAllConcreteImplementationsInAssemblyOf<MockUiThreadManager>(); 

As with all serious development projects, there is a need to test the code that makes up the code base. Doing so obviously helps to reduce the number of bugs in the application, but also alerts us when existing functionality has been broken, while adding new code. They also provide a safety net for refactoring, allowing us to continually improve our designs, while ensuring that existing functionality is not broken.

Therefore, one area that you could improve in theĀ application would be to implement a full test suite. This book has explained a number of ways for us to swap out code during testing and this pattern can be easily extended. If a manager class uses some sort of resource that cannot be used during testing, then you can create an interface for it, add a mock class, and use the DependencyManager class to instantiate the relevant concrete implementation during runtime and testing.

Another area from the book that could be extended relates to our AnimatedStackPanel class. You could extract the reusable properties and animation code from this class to an AnimatedPanel base class so that it could service several different types of animated panels.

As suggested in Chapter 7, Mastering Practical Animations, you could then further extend the base class by exposing additional animation properties so that users of your panel could have more control over the animations that it provides. For example, you could add alignment, direction, duration, and/or animation type properties to enable users of your framework to use a wide variety of animation options.

These properties could be divided between the entry and exit animations, to enable independent control over them. By providing a wide variety of these additional properties in a base class, you can vastly simplify the process of adding new animated panels.

For example, you could add a new AnimatedWrapPanel, or perhaps anĀ AnimatedColumnPanel, by simply extending the base class, and only have to implement the two MeasureOverride and ArrangeOverride methods in the new panel.