dotnet remoting:
Our csharp Windows forms application uses .Net remoting as a way for third party applications to integrate with us. This API includes methods, properties and events that client applications can do to share context with us. We've also provided a COM wrapper so that non- dotnet apps can use it too. Problem: When we recently attempted to sign the shared assembly and access it from the GAC, certain events stopped working. Events that have parameters derived from EventArgs, and are defined in the shared interface assembly, were broken. Other events that simply passed EventArgs still worked. Here is a derived event argument: [Serializable] public class LoginEventArgs : EventArgs { public string UserName; public LoginEventArgs(string userName) { UserName = userName; } } Our application (server) would fire the events, but they would never get to the client, and there were no exceptions or errors or anything to indicate a problem. I created a simplified test program that duplicates the problem without even having to include methods and properties, just two events. One event uses an EventArgs parameter, and the other uses the derived LoginEventArgs parameter. The one that uses the EventArgs parameter works. Here is how the test solution it is layed out (similar to all the remoting samples you have probably seen): RemoteTest - solution Interface - C# class libary containg the shared remoting interface Server - Windows form app that fires the events Client - Windows forms app that receives fired events The Server app has a simple form containing a button that will fire the UserLoggedIn event. When closing the form, it will fire the Terminated event. NOTE: the "event wrapper" and ISynchronize.Invoke method is used to get the events to the client in a thread-safe manner. The Client app has a simple form containing a "Connect" button, which simply connects to Server's remoting port. I broke down the problem one step further: I signed the shared assembly, but did NOT install it in the GAC. I simply let it be copied to each applications local directory. IT STILL DID NOT WORK. I then unsigned the assembly, and sure enough, all events worked. So, it would seem that signing an assembly somehow breaks the reference to the shared interface for the Server and/or Client application(s). Has anyone seen this behavior? Is there some kind of security thing going on here that I don't know about? Any chance that this is a problem with .Net serialization? Any advice is appreciated... The source code starts here... ----------------------------------------------------- // The shared "Interface" assembly using System; using System.Runtime.Remoting.Messaging; using System.ComponentModel; namespace RemoteTest.Interface { //EventArg derivative for Login [Serializable] public class LoginEventArgs : EventArgs { public string UserName; public LoginEventArgs(string userName) { UserName = userName; } } // event delegates public delegate void UserLoggedInHandler(object sender, LoginEventArgs e); public delegate void TerminatedHandler(object sender, EventArgs e); // event wrappers needed for remoting public class UserLoggedInEventWrapper : MarshalByRefObject { public event UserLoggedInHandler UserLoggedInArrivedLocally; [OneWay] public void LocallyHandleUserLoggedIn(object sender, LoginEventArgs e) { foreach (UserLoggedInHandler singleCast in UserLoggedInArrivedLocally.GetInvocationList()) { ISynchronizeInvoke syncInvoke = singleCast.Target as ISynchronizeInvoke; try { if ((null != syncInvoke) && (syncInvoke.InvokeRequired)) syncInvoke.Invoke(singleCast, new object[] { sender, e }); else singleCast(sender, e); } catch { } } } public override object InitializeLifetimeService() { return null; } } [Serializable] public class TerminatedEventWrapper : MarshalByRefObject { public event TerminatedHandler TerminatedArrivedLocally; [OneWay] public void LocallyHandleTerminated(object sender, EventArgs e) { // forward the message to the client foreach (TerminatedHandler singleCast in TerminatedArrivedLocally.GetInvocationList()) { ISynchronizeInvoke syncInvoke = singleCast.Target as ISynchronizeInvoke; try { if ((null != syncInvoke) && (syncInvoke.InvokeRequired)) syncInvoke.Invoke(singleCast, new object[] { sender, e }); else singleCast(sender, e); } catch { } } } public override object InitializeLifetimeService() { return null; } } public interface IRemoteTest { event UserLoggedInHandler UserLoggedIn; event TerminatedHandler Terminated; } } ----------------------------------------------------- // The Server, which fires the events using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp; using System.Text; using System.Windows.Forms; using RemoteTest.Interface; namespace RemoteTest.Server { public partial class Form1 : Form, IRemoteTest { public event UserLoggedInHandler UserLoggedIn; public event TerminatedHandler Terminated; TcpChannel _tcpChannel = null; public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { this.FormClosed += new FormClosedEventHandler(Form1_FormClosed); //initialilze Remoting for Slave BinaryServerFormatterSinkProvider serverProv = new BinaryServerFormatterSinkProvider(); serverProv.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full; IDictionary props = new Hashtable(); props["port"] = 9090; _tcpChannel = new TcpChannel(props, new BinaryClientFormatterSinkProvider(), serverProv); ChannelServices.RegisterChannel(_tcpChannel, true); RemotingServices.Marshal(this, "RemoteTest.Interface", typeof(IRemoteTest)); } void Form1_FormClosed(object sender, FormClosedEventArgs e) { if (null != Terminated) Terminated("RemoteTest_Server", new EventArgs()); } private void btnLogin_Click(object sender, EventArgs e) { if (null != UserLoggedIn)
I think I just answered my own question: When you sign the shared assembly, both the remoting client as well as the server need to set their respective server providor's typeFilterLevel property to full. If you look at my code, the Client was using the default service provider, which apparently defaults to "Low". So, the following code fixed the problem in my test app: BinaryServerFormatterSinkProvider serverProv = new BinaryServerFormatterSinkProvider(); serverProv.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full; _channel = new TcpChannel(props, new
Don't see what you're looking for? Try a search.
|