Introduction
Recently, I introduced Roxy run-time proxy code generator and (would be IoC Container). In particular, here are several articles on Roxy:
As I demonstrate in the above articles (especially the one listed first), Roxy is great for separating concerns in classes that need multiple concerns. It allows assembling implementations of multiple concerns together essentially creating a multiple plugin based inheritance-like functionality.
In this brief article, I present a new important feature that allows to assemble implementations of multiple concerns together without specifying the plugin wrappers when creating a new type. The plugin wrappers, of course, are still there but they are found automagically based on the interfaces we want to implement.
The Code
Code Location
You can download the code from the link above or from GITHUB repository DefaultWrapperTests.
The code consists of two very similar tests: NP.Roxy.DefaultWrappersTest.sln and NP.Roxy.DefaultWrappersTestWithAttrs.sln.
Compiling the Code
The code depends on NP.Roxy
nuget package and its dependencies, so you need the internet connection on during the compilation.
NP.Roxy.DefaultWrappersTest
Open and compile NP.Roxy.DefaultWrappersTest.sln console solution.
There is an interface IPersonDataVM
defined within PersonDataVM.cs file:
public interface IPersonDataVM
{
string FirstName { get; set; }
string LastName { get; set; }
string FullName { get; }
}
Its default implementation is PersonDataVM
defined next to the interface:
public class PersonDataVM : IPersonDataVM
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName =>
FirstName + " " + LastName;
}
There is also a wrapper interface defined at the very bottom of the same (PersonDataVM.cs) file:
public interface IPersonDataWrapper
{
PersonDataVM ThePersonWrapper { get; }
}
The purpose of this interface is to define the plugin (wrapper) implementation.
I am also using ISelectableItem<>
interface from NP.Utilities
library. This library is loaded via nuget, so the code for this interface and its implementation is not part of this project, but it is part of Roxy code. Here is a GITHUB link to the file containing this interface: SelectableItem.cs and its implementation.
Here is the code for the interface:
public interface ISelectableItem<T>
where T : ISelectableItem<T>
{
bool IsSelected { get; set; }
[EventThisIdx]
event Action<ISelectableItem<T>> IsSelectedChanged;
void SelectItem();
}
Here is the default (and probably the only needed) implementation of the interface:
[Implements(typeof(ISelectableItem<>))]
public class SelectableItem<T> : VMBase, ISelectableItem<T>, INotifyPropertyChanged
where T : ISelectableItem<T>
{
bool _isSelected = false;
[XmlIgnore]
public bool IsSelected
{
get
{
return _isSelected;
}
set
{
if (_isSelected == value)
return;
_isSelected = value;
IsSelectedChanged?.Invoke(this);
OnPropertyChanged(nameof(IsSelected));
}
}
public event Action<ISelectableItem<T>> IsSelectedChanged;
public void SelectItem()
{
this.IsSelected = true;
}
public void ToggleSelection()
{
this.IsSelected = !this.IsSelected;
}
}
Finally, as in the case of PersonDataVM
class, there is also a wrapper interface that defines how the plugin is implemented:
[WrapperInterface(typeof(ISelectableItem<>))]
public interface ISelectableItemWrapper<T>
where T : ISelectableItem<T>
{
SelectableItem<T> TheSelectableItem { get; }
}
At the top of the Program.cs files, the interfaces are merged together into the ISelectablePersonVM
interface:
public interface ISelectablePersonVM : IPersonDataVM, ISelectableItem<ISelectablePersonVM>
{
}
While the implementations are merged using Roxy within the Program.Main
method.
Before we create the implementation, however, we specify the mappings within NP.Roxy.Core static
class between the interfaces we want to merge and their respective Wrapper
implementation interfaces:
Core.SetWrapperType<IPersonDataVM, IPersonDataWrapper>();
Core.SetWrapperType(typeof(ISelectableItem<>), typeof(ISelectableItemWrapper<>));
Now, since the mapping is set, we can create the new type and its instance with a single NP.Roxy.Core
call:
ISelectablePersonVM personVM =
Core.CreateInstanceOfGeneratedType<ISelectablePersonVM>();
The rest of the code Program.Main
code tests that the created object correctly implements both IPersonDataVM
and ISelectableItem
interfaces:
personVM.FirstName = "Joe";
personVM.LastName = "Doe";
Console.WriteLine($"FullName is '{personVM.FullName}'");
personVM.IsSelectedChanged += PersonVM_IsSelectedChanged;
personVM.IsSelected = true;
When you run the test, it will print the following to the console:
FullName is 'Joe Doe'
Is Selected Changed
NP.Roxy.DefaultWrappersWithAttrsTest
DefaultWrappersWithAttrsTest
is almost the same as the previous sample, only mappings here are created by class attribute WrapperInterfaceAttribute
. Here is the modified code for IPersonDataWrapper
:
[WrapperInterface(typeof(IPersonDataVM))]
public interface IPersonDataWrapper
{
PersonDataVM ThePersonWrapper { get; }
}
Note the class attribute. Also, coming back to ISelectableItemWrapper
:
[WrapperInterface(typeof(ISelectableItem<>))]
public interface ISelectableItemWrapper<T>
where T : ISelectableItem<T>
{
SelectableItem<T> TheSelectableItem { get; }
}
Note the class attribute also here.
When using class attributes, we can simply call method NP.Roxy.Core.AddTypeAssemblyStatic<T>();
to create the mappings between all the Wrapper
interfaces contained in the assembly pointed by generic type argument T
and their original interfaces to implement. This is exactly what is done at the top of Program.Main
method:
static void Main(string[] args)
{
Core.AddTypeAssemblyStatic <ISelectableItem<ISelectablePersonVM>> ();
Core.AddTypeAssemblyStatic<ISelectablePersonVM>();
ISelectablePersonVM personVM =
Core.CreateInstanceOfGeneratedType<ISelectablePersonVM>();
personVM.FirstName = "Joe";
personVM.LastName = "Doe";
Console.WriteLine($"FullName is '{personVM.FullName}'");
personVM.IsSelectedChanged += PersonVM_IsSelectedChanged;
personVM.IsSelected = true;
}
private static void PersonVM_IsSelectedChanged(ISelectableItem<ISelectablePersonVM> obj)
{
Console.WriteLine("Is Selected Changed");
}
Summary
This article talked about a new feature for mapping the interfaces and their implementations while creating the class that contains multiple implementation concerns using Roxy.