I'm trying to develop an application using the MVP pattern.
The problem is wiring all the code manually. I was hoping to reduce the code needed. I tried to copy another solution, but I couldn't get to work. I'm using Winforms and the solution I was using as source is using WPF.
It would wire things on some conventions:
View events are wired by name. For example:"Loaded" event on the view will be wired to the "OnLoaded()" method on the presenter
Button commands are wired by name. For example: MoveNext" button is wired to the "OnMoveNext()" method.
Grids double click is wired by name. For example: Double click on "Actions" will be wired to the "OnActionsChoosen(ToDoAction)"
The working code in WPF is:
private static void WireListBoxesDoubleClick(IPresenter presenter)
{
var presenterType = presenter.GetType();
var methodsAndListBoxes = from method in GetActionMethods(presenterType)
where method.Name.EndsWith("Choosen")
where method.GetParameters().Length == 1
let elementName = method.Name.Substring(2, method.Name.Length - 2 /*On*/- 7 /*Choosen*/)
let matchingListBox = LogicalTreeHelper.FindLogicalNode(presenter.View, elementName) as ListBox
where matchingListBox != null
select new {method, matchingListBox};
foreach (var methodAndEvent in methodsAndListBoxes)
{
var parameterType = methodAndEvent.method.GetParameters()[0].ParameterType;
var action = Delegate.CreateDelegate(typeof(Action<>).MakeGenericType(parameterType),
presenter, methodAndEvent.method);
methodAndEvent.matchingListBox.MouseDoubleClick += (sender, args) =>
{
var item1 = ((ListBox)sender).SelectedItem;
if(item1 == null)
return;
action.DynamicInvoke(item1);
};
}
}
private static void WireEvents(IPresenter presenter)
{
var viewType = presenter.View.GetType();
var presenterType = presenter.GetType();
var methodsAndEvents =
from method in GetParameterlessActionMethods(presenterType)
let matchingEvent = viewType.GetEvent(method.Name.Substring(2))
where matchingEvent != null
where matchingEvent.EventHandlerType == typeof(RoutedEventHandler)
select new { method, matchingEvent };
foreach (var methodAndEvent in methodsAndEvents)
{
var action = (Action)Delegate.CreateDelegate(typeof(Action),
presenter, methodAndEvent.method);
var handler = (RoutedEventHandler)((sender, args) => action());
methodAndEvent.matchingEvent.AddEventHandler(presenter.View, handler);
}
}
private static IEnumerable<MethodInfo> GetActionMethods(Type type)
{
return from method in type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance)
where method.Name.StartsWith("On")
select method;
}
private static IEnumerable<MethodInfo> GetParameterlessActionMethods(Type type)
{
return from method in GetActionMethods(type)
where method.GetParameters().Length == 0
select method;
}
Anyway, I tried to port that to a WinForm app, but I wasn't successful. I changed the RoutedEventHandlers to EventHandlers, but couldn't find what to do about the LogicalTreeHelper.
I'm kind of stuck on this. I could do manually but I found this mini-framework so ingenuous that it's almost crazy.
PS: Source is http://msdn.microsoft.com/en-us/magazine/ee819139.aspx
Edit
I just realized something. I'm not gone dumb, the code above is not very test friendly, is it?
Ok. I got it working myself. I'm just posting the answer because at lest one other person found interesting.
First, the view
public interface IBaseView
{
void Show();
C Get<C>(string controlName) where C : Control; //Needed to later wire the events
}
public interface IView : IBaseView
{
TextBox ClientId { get; set; } //Need to expose this
Button SaveClient { get; set; }
ListBox MyLittleList { get; set; }
}
public partial class View : Form, IView
{
public TextBox ClientId //since I'm exposing it, my "concrete view" the controls are camelCased
{
get { return this.clientId; }
set { this.clientId = value; }
}
public Button SaveClient
{
get { return this.saveClient; }
set { this.saveClient = value; }
}
public ListBox MyLittleList
{
get { return this.myLittleList; }
set { this.myLittleList = value; }
}
//The view must also return the control to be wired.
public C Get<C>(string ControlName) where C : Control
{
var controlName = ControlName.ToLower();
var underlyingControlName = controlName[0] + ControlName.Substring(1);
var underlyingControl = this.Controls.Find(underlyingControlName, true).FirstOrDefault();
//It is strange because is turning PascalCase to camelCase. Could've used _Control for the controls on the concrete view instead
return underlyingControl as C;
}
Now the Presenter:
public class Presenter : BasePresenter <ViewModel, View>
{
Client client;
IView view;
ViewModel viewModel;
public Presenter(int clientId, IView viewParam, ViewModel viewModelParam)
{
this.view = viewParam;
this.viewModel = viewModelParam;
client = viewModel.FindById(clientId);
BindData(client);
wireEventsTo(view); //Implement on the base class
}
public void OnSaveClient(object sender, EventArgs e)
{
viewModel.Save(client);
}
public void OnEnter(object sender, EventArgs e)
{
MessageBox.Show("It works!");
}
public void OnMyLittleListChanged(object sender, EventArgs e)
{
MessageBox.Show("Test");
}
}
The "magic" happens at the base class. In the wireEventsTo(IBaseView view)
public abstract class BasePresenter
<VM, V>
where VM : BaseViewModel
where V : IBaseView, new()
{
protected void wireEventsTo(IBaseView view)
{
Type presenterType = this.GetType();
Type viewType = view.GetType();
foreach (var method in presenterType.GetMethods())
{
var methodName = method.Name;
if (methodName.StartsWith("On"))
{
try
{
var presenterMethodName = methodName.Substring(2);
var nameOfMemberToMatch = presenterMethodName.Replace("Changed", ""); //ListBoxes wiring
var matchingMember = viewType.GetMember(nameOfMemberToMatch).FirstOrDefault();
if (matchingMember == null)
{
return;
}
if (matchingMember.MemberType == MemberTypes.Event)
{
wireMethod(view, matchingMember, method);
}
if (matchingMember.MemberType == MemberTypes.Property)
{
wireMember(view, matchingMember, method);
}
}
catch (Exception ex)
{
continue;
}
}
}
}
private void wireMember(IBaseView view, MemberInfo match, MethodInfo method)
{
var matchingMemberType = ((PropertyInfo)match).PropertyType;
if (matchingMemberType == typeof(Button))
{
var matchingButton = view.Get<Button>(match.Name);
var eventHandler = (EventHandler)EventHandler.CreateDelegate(typeof(EventHandler), this, method);
matchingButton.Click += eventHandler;
}
if (matchingMemberType == typeof(ListBox))
{
var matchinListBox = view.Get<ListBox>(match.Name);
var eventHandler = (EventHandler)EventHandler.CreateDelegate(typeof(EventHandler), this, method);
matchinListBox.SelectedIndexChanged += eventHandler;
}
}
private void wireMethod(IBaseView view, MemberInfo match, MethodInfo method)
{
var viewType = view.GetType();
var matchingEvent = viewType.GetEvent(match.Name);
if (matchingEvent != null)
{
if (matchingEvent.EventHandlerType == typeof(EventHandler))
{
var eventHandler = EventHandler.CreateDelegate(typeof(EventHandler), this, method);
matchingEvent.AddEventHandler(view, eventHandler);
}
if (matchingEvent.EventHandlerType == typeof(FormClosedEventHandler))
{
var eventHandler = FormClosedEventHandler.CreateDelegate(typeof(FormClosedEventHandler), this, method);
matchingEvent.AddEventHandler(view, eventHandler);
}
}
}
}
I've got this working here as it is. It will autowire the EventHandler on the Presenter to the Default Events of the Controls that are on IView.
Also, on a side note, I want to share the BindData method.
protected void BindData(Client client)
{
string nameOfPropertyBeingReferenced;
nameOfPropertyBeingReferenced = MVP.Controller.GetPropertyName(() => client.Id);
view.ClientId.BindTo(client, nameOfPropertyBeingReferenced);
nameOfPropertyBeingReferenced = MVP.Controller.GetPropertyName(() => client.FullName);
view.ClientName.BindTo(client, nameOfPropertyBeingReferenced);
}
public static void BindTo(this TextBox thisTextBox, object viewModelObject, string nameOfPropertyBeingReferenced)
{
Bind(viewModelObject, thisTextBox, nameOfPropertyBeingReferenced, "Text");
}
private static void Bind(object sourceObject, Control destinationControl, string sourceObjectMember, string destinationControlMember)
{
Binding binding = new Binding(destinationControlMember, sourceObject, sourceObjectMember, true, DataSourceUpdateMode.OnPropertyChanged);
//Binding binding = new Binding(sourceObjectMember, sourceObject, destinationControlMember);
destinationControl.DataBindings.Clear();
destinationControl.DataBindings.Add(binding);
}
public static string GetPropertyName<T>(Expression<Func<T>> exp)
{
return (((MemberExpression)(exp.Body)).Member).Name;
}
This eliminates "magic strings" from the Binding. I think it can also be used on INotificationPropertyChanged.
Anyway, I hope someone finds it useful. And I completely ok if you want to point out code smells.
Related
I am trying to roll an event handling system in C# - in my case it's for a game in Unity but it's abstract enough to apply to any system.
A singleton class "EventManager" has a private Dictionary(System.Type,Dictionary(long, EventListener)) __listeners along with a public methods to Register, Unregister and ThrowEvent(EventInfo ei).
The dictionary's key is a type derived from EventInfo, so there will be keys for EventInfoFoo, EventInfoBar and so forth, and these may not necessarily have the same fields.
I would also like to be able to only listen for specific conditions within these classes derived from EventInfo such as "only fire when ei.CreatureType==Animal" or "position.x between 1 and 5".
I have a working solution using Reflection, however its performance is just not good enough. My next idea was to have this filter be a delegate method passed on by the class registering the listener, but since I expect almost all, if not all filters to be equality/range checks, I wonder if there's a cleaner way of handling it.
Here are the pertaining classes:
EventListener:
public class EventListener {
public Dictionary<string, string> eventFilter;
public delegate void eventHandler(EventInfo ei);
public eventHandler Eh;
public EventListener( eventHandler evH,Dictionary<string, string> filter)
{
Eh= evH;
eventFilter = filter;
}}
EventInfo:
public class EventInfo {
public Object Caller;
public EventInfo (Object __caller)
{
Caller = __caller;
}
public EventInfo()
{ Caller = null; }
}
public class EventInfoExample : EventInfo
{
public int Testint;
public EventInfoExample(Object __caller)
{
Caller = __caller;
}
}
EventManager:
public class EventManager : MonoBehaviour {
private static EventManager __em;
public static EventManager Em
{
get { return EventManager.__em; }
}
private Dictionary<System.Type,Dictionary<long, EventListener>> __listeners;
private long __idcounter = 1;
private long getNewID()
{
long __ret = __idcounter;
__idcounter++;
return __ret;
}
//true on let through , false on block
private bool __doFilter(Dictionary<string,string>eventFilter , EventInfo ei)
{
// if no filters, accept
if (eventFilter == null || eventFilter.Count < 1)
return true;
System.Type __eit = ei.GetType();
FieldInfo[] __fields = ei.GetType().GetFields();
List<string> __fieldlist = __fields.Select(f => f.Name).ToList();
foreach (KeyValuePair<string,string> kvp in eventFilter)
{
if (__fieldlist.Contains(kvp.Key) == false)
Debug.LogError("Fieldlist for " + __eit.ToString() + " does not contain a field named " + kvp.Key);
//this is what we are filtering for
//TODO add support for operators, for now its just ==
if (__eit.GetField(kvp.Key).GetValue(ei).ToString() != kvp.Value)
return false;
}
return true;
}
public Object ThrowEvent(EventInfo ei)
{
Debug.Assert(__listeners != null);
Debug.Assert(ei != null);
if (__listeners.ContainsKey(ei.GetType()) == false)
return null;
//call all
foreach ( KeyValuePair<long,EventListener> __kvp2 in __listeners[ei.GetType()])
{
// apply listener filters
if (__doFilter(__kvp2.Value.eventFilter , ei))
{
Debug.Log("Invoking ID " + __kvp2.Key.ToString() + " for " + ei.GetType().ToString());
__kvp2.Value.Eh(ei);
}
}
return null;
}
public long Register(System.Type eventType,EventListener el)
{
Debug.Assert(eventType.IsSubclassOf(typeof(EventInfo)));
Debug.Assert(el != null);
Debug.Assert(__listeners != null);
// if we dont have a key for this type, create new dict, then add to dict
if (__listeners.ContainsKey(eventType) == false)
__listeners.Add(eventType, new Dictionary<long, EventListener>());
long __newID = getNewID();
//add to list
__listeners[eventType].Add(__newID, el);
return __newID;
}
public bool Unregister(long ID)
{
return true;
}
// Use this for initialization
void Start () {
// pop singleton
EventManager.__em = this;
}
// Update is called once per frame
void Update () {
}}
The main idea what I am trying to do - to have one VM, which has a lot of other VMs.
The problem is to organize data transportation.
Main VM is connected with a template and other VMs have their own templates.
I use a navigator to change VMs and template selector to change templates.
Navigator:
public class NavigationController : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private List<ViewModelBase> _viewModels;
private ViewModelBase _currentViewModel;
public ViewModelBase CurrentViewModel {
get { return _currentViewModel; }
set { _currentViewModel = value; OnPropertyChanged(nameof(CurrentViewModel)); }
}
private List<ViewModelBase> _legViewModels;
private ViewModelBase _legViewModel;
public ViewModelBase LegViewModel
{
get { return _legViewModel; }
set { _legViewModel = value; OnPropertyChanged(nameof(LegViewModel)); }
}
public NavigationController()
{
_viewModels = new List<ViewModelBase>
{
new ViewModelLogin(this),
new ViewModelPhysicalOverview(this),
...list of VMs...
};
_currentViewModel = _viewModels.First();
_legViewModels = new List<ViewModelBase>
{
new SFSViewModel(this),
new BPVHipViewModel(this)
};
_legViewModel = _legViewModels.First();
}
public void NavigateTo<T>()
{
var target = _viewModels.FirstOrDefault(e => e.GetType() == typeof(T));
if (target != null)
CurrentViewModel = target;
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
//если PropertyChanged не нулевое - оно будет разбужено
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
My main VM:
public BPVHipViewModel LeftBPVHip { get; protected set; }
public SFSViewModel LeftSFS { get; protected set; }
public BPVHipViewModel RightBPVHip { get; protected set; }
public SFSViewModel RightSFS { get; protected set; }
public ViewModelAddPhysical(NavigationController controller) : base(controller)
{
LeftBPVHip = new BPVHipViewModel(Controller);
RightBPVHip = new BPVHipViewModel(Controller);
LeftSFS = new SFSViewModel(Controller);
RightSFS = new SFSViewModel(Controller);
Controller = controller;
base.HasNavigation = false;
ToRightBPVHipCommand = new DelegateCommand(
() =>
{
Controller.LegViewModel = RightBPVHip;
Controller.NavigateTo<LegPartViewModel>();
}
);
ToLeftBPVHipCommand = new DelegateCommand(
() =>
{
Controller.LegViewModel = LeftBPVHip;
Controller.NavigateTo<LegPartViewModel>();
}
);
ToLeftSFSCommand = new DelegateCommand(
() =>
{
Controller.LegViewModel = LeftSFS;
Controller.NavigateTo<LegPartViewModel>();
}
);
ToRightSFSCommand = new DelegateCommand(
() =>
{
Controller.LegViewModel = RightSFS;
Controller.NavigateTo<LegPartViewModel>();
}
);
}
So before I go to another VM and change my screen, I do
Controller.LegViewModel = RightSFS;
and I thought if I change something in RightSFS - it will keep changes after returning to main VM. But I guess it doesn't work like this.
In children I have:
private bool _isEmpty = true;
public bool IsEmpty {
get
{
return _isEmpty;
}
protected set {
_isEmpty = value;
OnPropertyChanged("IsEmpty");
}
}
public string ButtonText
{
get
{
if (!IsEmpty) return "Edit";
else return "Fill";
}
}
And a fn that fires before I return to parent screen:
SaveCommand = new DelegateCommand(
() =>
{
IsEmpty = false;
Controller.NavigateTo<ViewModelAddPhysical>();
}
);
so I want a button from main template to show if we already have visited child screen, in this case I want "Edit" text. But it returns "Fill" all the time, 'cause IsEmpty doesn't change from true to false for him and I don't understand how to fix it. Please help.
For me, you are trying to invent a wheel of your own. It's done, multiple times. Every MVVM framework outthere has built-in navigation.
Take a look at ReactiveUI (great framework) samples, they are doing exactly what you need.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ninject;
using ReactiveUI.Samples.Routing.Views;
using Splat;
namespace ReactiveUI.Samples.Routing.ViewModels
{
/* COOLSTUFF: What is the AppBootstrapper?
*
* The AppBootstrapper is like a ViewModel for the WPF Application class.
* Since Application isn't very testable (just like Window / UserControl),
* we want to create a class we can test. Since our application only has
* one "screen" (i.e. a place we present Routed Views), we can also use
* this as our IScreen.
*
* An IScreen is a ViewModel that contains a Router - practically speaking,
* it usually represents a Window (or the RootFrame of a WinRT app). We
* should technically create a MainWindowViewModel to represent the IScreen,
* but there isn't much benefit to split those up unless you've got multiple
* windows.
*
* AppBootstrapper is a good place to implement a lot of the "global
* variable" type things in your application. It's also the place where
* you should configure your IoC container. And finally, it's the place
* which decides which View to Navigate to when the application starts.
*/
public class AppBootstrapper : ReactiveObject, IScreen
{
public RoutingState Router { get; private set; }
public AppBootstrapper(IMutableDependencyResolver dependencyResolver = null, RoutingState testRouter = null)
{
Router = testRouter ?? new RoutingState();
dependencyResolver = dependencyResolver ?? Locator.CurrentMutable;
// Bind
RegisterParts(dependencyResolver);
// TODO: This is a good place to set up any other app
// startup tasks, like setting the logging level
LogHost.Default.Level = LogLevel.Debug;
// Navigate to the opening page of the application
// you can set any property of this new VM to transport data
Router.Navigate.Execute(new WelcomeViewModel(this));
}
private void RegisterParts(IMutableDependencyResolver dependencyResolver)
{
dependencyResolver.RegisterConstant(this, typeof(IScreen));
dependencyResolver.Register(() => new WelcomeView(), typeof(IViewFor<WelcomeViewModel>));
}
}
}
I used MessageBus pattern, it was perfect solution for me
class Subscription
{
public object Instance { get; set; }
public Action<object, object> Handler;
}
public class MessageBus
{
#region Singleton
private static readonly MessageBus _instance = new MessageBus();
public static MessageBus Default => _instance;
private MessageBus()
{
}
#endregion
private readonly Dictionary<string, List<Action<object, object>>> _hadlersMap
= new Dictionary<string, List<Action<object, object>>>();
public void Call(string name, object sender, object data)
{
List<Action<object, object>> handlers;
if(!_hadlersMap.TryGetValue(name.ToUpper(), out handlers))
return;
foreach (var handler in handlers)
{
handler?.Invoke(sender,data);
}
}
public void Subscribe(string name, Action<object, object> handler)
{
name = name.ToUpper();
List<Action<object, object>> handlers;
if (!_hadlersMap.TryGetValue(name, out handlers))
{
handlers = new List<Action<object, object>>{ handler };
_hadlersMap.Add(name, handlers);
}
else
{
handlers.Add(handler);
}
}
}
So I have never seem things be done this way. Have you looked at Windsor. I believe dependency injection and inversion of control could improve the scalability here. As far as suggestions go.
There is a lot of instantiation going on in many different places in the code here. Maybe creating a factory to handle all the new-ing up. IOC would help with that as well. You could place your list of models globally. App.Current.Properties[ "someVm" ] = vmInstance; if you are wanting to save the vm state.
Another way to persist the vm state would of course be to make that vm a singleton ensuring that when called it returns that only instance if already exists or instantiates if not.
Finally, I have persisted vm state upon unloading and reading state from somewhere upon loading. This is common and many default controls do this.
I want to wait for my listener to complete before returning the containing function.
Consider this function:
public static Users GetUser(string sUserID)
{
Users oUser = null;
var listener = new UserEventHelper((sender, e) =>
{
if ((e as UserArgs).Value != null)
{
oUser = (e as UserArgs).Value;
}
});
oRoot.Child("users").OrderByKey().EqualTo(sUserID).AddListenerForSingleValueEvent(listener);
return oUser;
}
Other Classes:
class UserEventHelper : Java.Lang.Object, IValueEventListener
{
EventHandler OnChange;
public UserEventHelper(EventHandler OnChange)
{
this.OnChange = OnChange;
}
public void OnCancelled(DatabaseError error)
{
//throw new NotImplementedException();
}
public void OnDataChange(DataSnapshot snapshot)
{
if (OnChange != null && snapshot.Value != null)
{
Users oUser = JsonConvert.DeserializeObject<Users>(snapshot.Value.ToString());
OnChange.Invoke(this, new UserArgs(oUser));
}
else
{
OnChange.Invoke(this, new UserArgs(null));
}
}
}
public class UserArgs : EventArgs
{
public UserArgs(Users value)
{
this.Value = value;
}
public Users Value { get; set; }
}
How can I structure my code in such a way that I know the user value is set from inside the listener before I return from the function? Note: There could be other issues with the code but I would like to focus on this conceptual question.
I cannot really think of a way to return an object from the GetUser method, this method just subscribes to an event and provides an action to be taken when the event is fired, and there is no guarantee when the event will be fired.
So it should not be called GetUser, since it cannot return a user object. Besides, what do you expect the method to return when the event is fired multiple times? Just take the first one and discard the others?
The solution is, instead of trying to return a user object to the caller, just doing whatever you want on the user object just inside the action (the listener) you provided when you subscribing to the event.
var listener = new UserEventHelper((sender, e) =>
{
if ((e as UserArgs).Value != null)
{
oUser = (e as UserArgs).Value;
//you have a user object now, just do your work here!
}
});
I would like to set an event handler only by reflection, I can get all the types but I can't achieve it.
public delegate void MyHandler<T>(IMyInterface<T> pParam);
public interface IMyInterface<T>
{
MyHandler<T> EventFired { get; set; }
}
void myFunction()
{
//admit vMyObject is IMyInterface<ClassA>
var vMyObject = vObj as IMyInterface<ClassA>;
//get the generic type => ClassA
var vTypeGeneric = vTypeReturn.GenericTypeArguments.FirstOrDefault();
//build the type handler for the event MyHandler<ClassA>
Type vAsyncOP = typeof(MyHandler<>).MakeGenericType(vTypeGeneric);
// here I don't know how to create the Event handler to set EventFired
// dynamically with the code inside
var vEventFired = vMyObject.GetType().GetProperty("EventFired");
vEventFired.SetMethod etc...
}
I found some code with the usage of Lambda/Expression but I don't understand how to use it in this case.
Full sample:
public delegate void MyHandler<T>(IMyInterface<T> pParam);
public interface IMyInterface<T>
{
MyHandler<T> EventFired { get; set; }
}
public class MyClass : IMyInterface<int>
{
public MyHandler<int> EventFired { get; set;}
}
public void ConcreteHandler(IMyInterface<int> p)
{
Console.WriteLine("I'm here");
}
void Main()
{
var myValue = new MyClass();
var deleg = Delegate.CreateDelegate(typeof(MyHandler<int>), this, "ConcreteHandler");
myValue.GetType().GetProperty("EventFired").SetValue(myValue, deleg);
// Test delegate invocation:
myValue.EventFired.Invoke(new MyClass());
}
Since the question asks about setting an event and the code refers to delegate, here is the code for setting an event using reflection (via extension method):
public delegate void MyHandler<T>(IMyInterface<T> pParam);
public interface IMyInterface<T>
{
event MyHandler<T> EventFired;
}
public class ClassA : IMyInterface<int>
{
public event MyHandler<int> EventFired;
private int _Count = 0;
public void Fire()
{
var handler = EventFired;
if (handler != null) handler(this);
}
public override string ToString()
{
return "Call: " + (++_Count).ToString();
}
}
public static class Extension
{
public static void Add<T>(this IMyInterface<T> i, System.Reflection.MethodInfo method, object method_instance = null)
{
if (method.IsGenericMethodDefinition) method = method.MakeGenericMethod(typeof(T));
Delegate d = Delegate.CreateDelegate(typeof(MyHandler<>).MakeGenericType(typeof(T)), method_instance, method);
i.GetType().GetEvent("EventFired").GetAddMethod().Invoke(i, new object[] { d });
}
}
public class Program
{
public static void Print<T>(IMyInterface<T> val)
{
string output = val.ToString();
Console.WriteLine(output);
System.Diagnostics.Debug.WriteLine(output);
}
static void Main(string[] args)
{
ClassA ca = new ClassA();
ca.EventFired += Print<int>;
ca.Add(typeof(Program).GetMethod("Print", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public));
ca.Fire();
}
}
Sorry for the title, I meant not an event but a delegate property.
I found the solution meanwhile :
public void MyDelegate<T>(IMyInterface<T> pParam)
{
}
void myFunction()
{
//admit vMyObject is IMyInterface<ClassA>
var vMyObject = vObj as IMyInterface<ClassA>;
//get the generic type => ClassA
var vTypeGeneric = vTypeReturn.GenericTypeArguments.FirstOrDefault();
//build the type handler for the event MyHandler<ClassA>
Type vAsyncOP = typeof(MyHandler<>).MakeGenericType(vTypeGeneric);
// SOLUTION here :
// Create MyDelegate<vTypeGeneric>
// Then instanciate it with CreateDelegate and typeof(IMyInterface<vTypeGeneric>)
var vMyDelegate= this.GetType().GetMethod("MyDelegate");
var vMyDelegateGeneric = vMyDelegate.MakeGenericMethod(vTypeGeneric);
Type vTypeHandlerGeneric = typeof(IMyInterface<>).MakeGenericType(vTypeGeneric);
// this => bind to method in the class
var vMethodDelegate = vMyDelegateGeneric.CreateDelegate(vTypeHandlerGeneric, this);
// Set delegate Property
var vEventFired = vMyObject.GetType().GetProperty("EventFired");
vEventFired.SetValue(value, vDelegate);
}
The following code should be self explanetory: we have an adaptor, who consumes events from the transport (layer), which holds the MessageRegistrar (object type because we can't tell it's type, and basically because this is legacy code :-) ). The transport layer have a concrete which have an event.
I want to test a case where the event is triggered, so..
After hours of trying to figure why it won't pass, I present the following challenge:
[TestFixture]
public class AdaptorTests
{
public delegate void TracksEventHandler(object sender, List<int> trklst);
public class MyEventHolder
{
public virtual event TracksEventHandler EventName;
}
public interface ITransport
{
object MessageRegistrar { get; }
}
public class MyTransport : ITransport
{
private readonly MyEventHolder m_eventHolder;
public MyTransport(MyEventHolder eventHolder)
{
m_eventHolder = eventHolder;
}
public virtual object MessageRegistrar
{
get { return m_eventHolder; }
}
}
public class MyAdaptor
{
private readonly ITransport m_transport;
public MyAdaptor(ITransport transport)
{
EventTriggered = false;
m_transport = transport;
}
public void Connect()
{
MyEventHolder eventHolder = m_transport.MessageRegistrar as MyEventHolder;
if (eventHolder != null)
eventHolder.EventName += EventHolderOnEventName;
}
private void EventHolderOnEventName(object sender, List<int> trklst)
{
EventTriggered = true;
}
public bool EventTriggered { get; private set; }
}
[Test]
public void test1()
{
Mock<MyEventHolder> eventHolderMock = new Mock<MyEventHolder> {CallBase = true};
Mock<MyTransport> transportMock = new Mock<MyTransport>(eventHolderMock.Object) {CallBase = true};
MyAdaptor adaptor = new MyAdaptor(transportMock.Object);
adaptor.Connect();
MyEventHolder eventHolder = transportMock.Object.MessageRegistrar as MyEventHolder;
Mock.Get(eventHolder).Raise(eh => eh.EventName += null, new List<int>());
Assert.IsTrue(adaptor.EventTriggered);
}
[Test]
public void test2()
{
Mock<MyEventHolder> eventHolderMock = new Mock<MyEventHolder> { CallBase = true };
Mock<MyTransport> transportMock = new Mock<MyTransport>(eventHolderMock.Object) { CallBase = true };
MyAdaptor adaptor = new MyAdaptor(transportMock.Object);
adaptor.Connect();
MyEventHolder eventHolder = transportMock.Object.MessageRegistrar as MyEventHolder;
Mock.Get(eventHolder).Raise(eh => eh.EventName += null, null, new List<int>());
Assert.IsTrue(adaptor.EventTriggered);
}
}
My question is: why wont the test (at least one of them) pass?
EDIT #151217-0822 Addded 'adaptor.Connect()' to the original post (still won't fix the issue).
WORKAROUND
Credits to #Patrick Quirk: Thanks!!
For those encountering the same issue: after I understood what Patrick-Quirk detected, and trying couple of failed workarounds, I've ended up adding the following verified fix: 'eventHolder.FireEventNameForTestings(new List());':
public class MyEventHolder
{
public virtual event TracksEventHandler EventName;
public virtual void FireEventNameForTestings(List<int> trklst)
{
TracksEventHandler handler = EventName;
if (handler != null)
handler(this, trklst);
}
}
[Test]
public void test3()
{
Mock<MyEventHolder> eventHolderMock = new Mock<MyEventHolder> { CallBase = true };
Mock<MyTransport> transportMock = new Mock<MyTransport>(eventHolderMock.Object) { CallBase = true };
MyAdaptor adaptor = new MyAdaptor(transportMock.Object);
adaptor.Connect();
MyEventHolder eventHolder = transportMock.Object.MessageRegistrar as MyEventHolder;
eventHolder.FireEventNameForTestings(new List<int>());
Assert.IsTrue(adaptor.EventTriggered);
}
HTH..
It seems that CallBase and Raise() have an unexpected (to me) interaction.
When you are attaching an event handler to a virtual event on a mock, you go through this code in Moq:
if (invocation.Method.IsEventAttach())
{
var delegateInstance = (Delegate)invocation.Arguments[0];
// TODO: validate we can get the event?
var eventInfo = this.GetEventFromName(invocation.Method.Name.Substring(4));
if (ctx.Mock.CallBase && !eventInfo.DeclaringType.IsInterface)
{
invocation.InvokeBase();
}
else if (delegateInstance != null)
{
ctx.AddEventHandler(eventInfo, (Delegate)invocation.Arguments[0]);
}
return InterceptionAction.Stop;
}
You can see that if CallBase is true, then it will add your handler to the concrete object's event (via invocation.InvokeBase()). If CallBase is false, it will add it to an invocation list on the mock (via AddEventHandler). Now let's look at the code for Raise(), which gets the event object from the Expression and then calls DoRaise():
internal void DoRaise(EventInfo ev, EventArgs args)
{
// ... parameter validation
foreach (var del in this.Interceptor.InterceptionContext.GetInvocationList(ev).ToArray())
{
del.InvokePreserveStack(this.Object, args);
}
}
See the call to GetInvocationList()? That retrieves the invocation list from the mock that I mentioned above. This code never invokes the actual event on the base object.
So, it seems there's no way to raise an event on a mocked object where CallBase is set to true.
The only workaround I see, if you require CallBase being true, is to add a method to your concrete MyEventHolder to trigger your event. Obviously what you posted is a simplified example so I can't give you more guidance than that, but hopefully I've shown you why what you have does not work.