[en] No component registered for interface (solved)

Topics: Technical Support
Jan 9, 2013 at 9:27 AM

Hi,

i'm getting started with Zyan and ran into a problem. I wanted my server part register new components when a Client is calling a method. The component gets registered but when i try to create a proxy for it at the Client i get an ApplicationException saying there is no component registered for interface 'x' on server 'y'. Am i missing something?

Serverpart:

static void Main(string[] args)
{
    TcpDuplexServerProtocolSetup protocol = new TcpDuplexServerProtocolSetup(_C.Port, new NicknameAuthProvider(), true);

    using (ZyanComponentHost host = new ZyanComponentHost("ND", protocol))
    {
       _Lobby = new Lobby.Lobby(host);
       host.RegisterComponent<ILobby, Lobby.Lobby>(_Lobby);
       [...]	
       Console.ReadLine();
    }
}
public class Lobby : ILobby
{
    ZyanComponentHost _Host;

    public Lobby(ZyanComponentHost host)
    {
        _Host = host;
    }

    public Boolean CreateGame(String name, String creator, float buyin)
    {
        if (_Games.Find(g => g.Name == name) == null)
        {
            Game.GameCore _G = new Game.GameCore(name, creator, buyin, NDServerCore.Program._DB);
            _G.GameEnded += new Action<Guid>(_G_GameEnded);
            _Games.Add(_G);

            _Host.RegisterComponent<IGameCore, Game.GameCore>(_G.Name, _G);

            return true;
        }
        return false;
    }
}
Clientpart:
class Program
{
    private static ZyanConnection _connection;
    private static Hashtable _credentials;

    static void Main(string[] args)
    {
        _credentials = new Hashtable();
        _credentials.Add("nickname", "test");
        _credentials.Add("password", "test");

        TcpDuplexClientProtocolSetup protocol = new TcpDuplexClientProtocolSetup(true);

        _connection = new ZyanConnection("tcpex://localhost:8000/ND", protocol, _credentials, false, true);
        ILobby _lobbyProxy = _connection.CreateProxy<ILobby>();

        ThreadPool.QueueUserWorkItem(x => _lobbyProxy.CreateGame("test", "test", 2));

        Console.ReadLine();

        IGameCore _G = _connection.CreateProxy<IGameCore>("test"); // ApplicationException

        Console.ReadLine();
    }
}
Coordinator
Jan 9, 2013 at 12:15 PM

Hi Dittany,

where do you define interface ILobby?
It should be defined in a library shared between client and server:

  • server.exe — class Lobby, class ServerProgram
  • client.exe — class Program
  • shared.dll — public interface ILobby (this dll is referenced by client.exe and server.exe)
Jan 9, 2013 at 12:38 PM
Edited Jan 9, 2013 at 12:44 PM

Hi yallie,

ILobby is indeed defined in a class library project which is referenced by both the server and client project. (IGameCore is there too)

Creating the proxy for the Lobby object is working fine and if i register a GameCore component in the Main method of the server i can use that at the client too.

Seems to me that components registered after the client is connected cant be found.

Edit: I just tested what happens when i first let a client connenct and then let the server register the Lobby object. I then get the same error at the client saying theres no component for the ILobby interface registered.

Coordinator
Jan 9, 2013 at 2:37 PM
Edited Jan 9, 2013 at 2:55 PM

Oh, I didn't notice that you register IGameCore instances dynamically! You're right, that's the reason why you get the exception. Zyan doesn't currently support dynamic component registration.

When the client connects to the server, ZyanConnection gets the list of registered components. It only allows creating proxies for components that are known to be registered on the server. It uses locally cached list of components to check whether the requested component is available. Otherwise, CreateProxy operation would cause extra round-trip to the server which is unacceptable.

I'll consider your scenario of dynamic component registration, but please don't expect anything ready-to-use soon. Dynamic registration will impact many important things (such as MEF integration), so we have to be careful with it.

For now, I can only suggest working around this limitation. For example, you can pass game name along with other parameters to IGameCore methods, so the server component can load a game by its name and act accordingly:

public interface IGameCore // your version
{
	void Move(int x, int y);

	bool Scan(float degrees);

	bool Fire(float degrees);
}

public interface IGameCore // modified version
{
	void Move(string gameName, int x, int y);

	bool Scan(string gameName, float degrees);

	bool Fire(string gameName, float degrees);
}

Doesn't look great, I know... 

Alternatively (if your client cannot take part in more than one game at a time) you can set server-side session variable (say, CurrentGameName) which will serve as an implicit gameName parameter. It will keep your IGameCore interface intact. Something like this:

public class Lobby : ILobby
{
	public bool CreateGame(string name, string creator, float buyin)
	{
		// set current game name variable for the current client
		ServerSession.CurrentSession.SessionVariables
			.SetSessionVariable("CurrentGameName", name);

		// the rest of the method is the same...
	}
}

// register this component instead of the real GameCore as a singleton
public class GameCoreWrapper : IGameCore
{
	public GameCoreWrapper(Lobby lobby)
	{
		Lobby = lobby;
	}

	public Lobby Lobby { get; private set; }

	private string CurrentGameName
	{
		get
		{
			// get session variable which belongs to the current client
			return ServerSession.CurrentSession.SessionVariables
				.GetSessionVariable("CurrentGameName", string.Empty);
		}
	}

	private IGameCore CurrentGame
	{
		get
		{
			// get current game instance by its name
			return Lobby.GetGameByName(CurrentGameName);
		}
	}

	// IGameCore implementation follows

	public void Move(int x, int y)
	{
		// redirect all method calls to CurrentGame as is
		CurrentGame.Move(x, y);
	}
}

Hope it helps.

Jan 10, 2013 at 10:15 AM
Edited Jan 10, 2013 at 4:30 PM

Hi yallie,

thanks for your effort!

I also thought about your first suggestion but i wanted to use events and i dont want every user to get all events from all games.

In addition i dont want to restrict users to only be able of participating in one game at a time.

I found a "workaround": If a user wants to join a game he first has to reconnect and so is able to create a proxy for it.

Now i got another problem. So far i used the Zyan 2.3 assembly and ran into KeyNotFoundExceptions when i created a proxy over a uniqueName and then tried to register events.

That is solved with 2.4 but now i guess the "old" way of using events doesnt work anymore. The event Handlers on the server are always null and thus the events never get fired. I read your description of the 2.4 changes but i couldnt figure out how i have to do this now.

This is how i tested:

namespace XTServer
{
    class Program
    {
        static void Main(string[] args)
        {
            ZyanComponentHost ZyanHost;

            ZyanComponentHost.LegacyBlockingEvents = true;

            TcpDuplexServerProtocolSetup protocol = new TcpDuplexServerProtocolSetup(2345, new NicknameAuthProvider());

            ZyanHost = new ZyanComponentHost("XTServer", protocol);
            ZyanHost.RegisterComponent<ISampleServer, SampleServer>("Singleton", new SampleServer());

            Console.WriteLine("Server started.");
            Console.ReadLine();
        }
    }

    public class SampleServer : ISampleServer
    {
        public event EventHandler TestEvent;

        public void RaiseTestEvent(EventArgs args)
        {
            if (TestEvent != null)
            {
                TestEvent(null, args);
            }
        }
    }

    public class NicknameAuthProvider : IAuthenticationProvider
    {
        public AuthResponseMessage Authenticate(AuthRequestMessage authRequest)
        {
            return new AuthResponseMessage()
            {
                AuthenticatedIdentity = new GenericIdentity("test"),
                Success = true
            };
        }
    }
}

namespace XTClient
{
    class Program
    {
        static void Main(string[] args)
        {
            Hashtable _credentials = new Hashtable();

            ZyanConnection ZyanConnection;
            ZyanConnection = new ZyanConnection("tcpex://localhost:2345/XTServer");

            var proxy = ZyanConnection.CreateProxy<ISampleServer>("Singleton");
            proxy.TestEvent += new EventHandler(proxy_TestEvent);
            ThreadPool.QueueUserWorkItem(x => proxy.RaiseTestEvent());

            Console.WriteLine("Done.");
            Console.ReadLine();
        }

        static void proxy_TestEvent(object sender, EventArgs e)
        {
            throw new NotImplementedException();
        }
    }
}
namespace XTShared
{
    public interface ISampleServer
    {
        event EventHandler TestEvent;

        void RaiseTestEvent(EventArgs args = null);
    }
}
Coordinator
Jan 10, 2013 at 5:03 PM

Hi Dittany!

Thanks a lot for the complete sample.
Don't see anything wrong with the code, so it must be a bug.
I'll look into it as soon as possible.

Coordinator
Jan 10, 2013 at 5:07 PM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.
Coordinator
Jan 11, 2013 at 11:12 AM
Edited Jan 11, 2013 at 1:05 PM

Hi Dittany,

thanks for your patience. Please download the latest code drop.
Also, modify your event handler so it doesn't throw an exception:

static void proxy_TestEvent(object sender, EventArgs e)
{
      Console.WriteLine("Event fired.");
}

ZyanComponentHost.LegacyBlockingEvents = true is not necessary.
Everything should work regardless of this setting.
This setting is introduced for backward compatibility and for unit tests.

Coordinator
Jan 11, 2013 at 12:56 PM
Edited Jan 11, 2013 at 12:58 PM

I've added ZyanConnection.RefreshRegisteredComponents method
so you don't have to reconnect before creating IGameCore proxies:

lobby.CreateGame(gameName, ...);
connection.RefreshRegisteredComponents(); // re-query components
var game = connection.CreateProxy<IGameCore>(gameName);

Please let me know if it suits your needs.

Jan 12, 2013 at 6:44 PM

Hi yallie,

i just tested it and it works like a charm. (Events get fired and RefreshRegisteredComponents works too)

Thanks alot for this fast support!

Coordinator
Jan 12, 2013 at 9:24 PM

Great! Very glad to hear that!

Let me know if you need any further assistance.