RO-SDK and ASP.NET MVC - How to?

Hello,

I’m starting with the whole ASP.NET - using MVC - and RO-SDK, but I’m quite lost.

I already have the RO-SDK Service implemented in C++Builder, and a client app also implemented on C++Builder which works fine. I need to create a web app that consumes said service, but there are a lot of things “new” for me here that I don’t really know where to start. The services expose a simple Login/Logout interface, and several other services that mostly query the database, do some process, and return stuff.

One of my starting issues is that I don’t know how to handle the stateless of ASP.NET with the RO-SDK services. I don’t want to Login each time I need to do some operation, and on the server side I already handle the session state with a MemorySession, but I’m not sure how to do this from the ASP.NET app.

I was thinking on storing the instance of the services created in Session[] values in the ASP.NET code, but this doesn’t feel anything close to ok, so I think I am missing something.

So, any ideas of solutions, books, tutorials, examples, and so on?

I’m sorry for the broad question, but hopefully somebody else has walked this way and can share some ideas.

Thanks

Hello

So you need to call a RO SDK service from ASP.NET MVC server code.
This article shows how to work with Data Abstract for .NET (they use RO SDK services as tarsport): http://wiki.remobjects.com/wiki/Data_Abstract_and_ASP.NET_MVC

The next question is how to handle the server authentication info.
This is not that hard. You need to do the following:
In the login operatiom generate a random Guid and store it in the session:

this.Session["RO_GUID"] = Guid.NewGuid();

Then use this guid as client id while performing login and data access operations:

var clientId = this.Session["RO_GUID"];

var message = new BinMessage();
message.ClientID = clientId;

var channel = new IpHttpClientChannel();

var service = CoROServer3Service.Create(message, channel);
service.Sum(1, 2);

This approach lets you to tie each ASP.NET MVC user session to a RemObjects SDK server session.

Regards

Thanks for your Reply Anton,

I saw yesterday the post you link before asking, but still was somewhat lost.

From your reply, it appears that I would need to create the messages, channel and service itself every time I need them, correct? Is that the only way? I don’t know exactly how expensive that is in runtime, but it must cost some time to do so.

I was thinking about encapsulating all the needed objects in a class, and store the instance of that class in the Session dictionary… but I don’t know if this is recommended or not.

Are the ClientChannel componentes thread safe? I mean, can I have one that serves all the services created, or every service needs it’s own? The message, if I use the technique of storing the ClientId, would need to be for each specific session, but what about the channel? And the EncryptionEnvelope?

Thanks for your help

Instantiation of the client channel, message and service proxies is very cheap, especially compared to the cost of remote service requests (that involve data transfer over network).

There are two kinds of ClientChannels - “simple” that cannot serve several requests at once and so called “super” ones that can server multiple requests at the same moment of time. Still using “super” channels would be an overkill in your case.

You could try the following approach:
1 Add a method to the service proxy class named f.e. SetClientId that will do the following:

this.___Message.ClientID = clientId;

Service proxy classes are marked as ‘partial’ so there’s no need to edit the generated _Intf file direclty, this method can be defined in additional code file.
2 Define an ObjectPool that will hold instantiated service proxies. RemOjects SDK provides object pool class, you can see it in action in the file

RemObjects SDK for .NET\Source\RemObjects.SDK.Server\SessionManagement\OlympiaServerSessionManager.cs

where object pool is used to store connections to the Olympia Server. Take a look how the filed fConnectionPool is used in that code file.
3 When you need to call the remote server do the following:
3.1 Ask the object pool for a service proxy instance. The pool will either return already existing instance or create a new one.
3.2 Call method SetClientId (defined at step 1) to set the RO SDK session identifier
3.3 Call remote service
3.4 Release service proxy instance back into the pool or drop it if for some reason service call failed (f.e. server doesn’t respond)

This approach should give you good performance and won’t wast server resources.

Ok, sounds good! I will do as you say and let you know if I bump into some issue.

Thanks!

Hello Anton,

I’ve moved forward with this but I’m having a problem.

Right now I have the Login correctly implemented, and when Login returns sucess then I store, as you instructed, the ClientId in a Session variable in the client.

Now when I want to call Logout, I first check if the session is correctly created, and retrieve the ClientId value. At this point I can see debugging that the Id is the expected one. I set the ClientId property of the Message to this value, and call the Logout operation. Up to this point: the call to @__ClientChannel.Dispatch(@__LocalMessage the ClientId value is correct.

But on the server the SessionId is not the same. In the Login call the SessionId was the same as the ClientId on the client, but in this call the SessionId is not the same. And the Session values on the server don’t contain what I need to trace the session and close it down. I don’t know if I’m doing something incorrect, maybe on the Server? the Logout code is like this:

void __fastcall TClientServices::Logout()
{
  if (Session != NULL)
  {
    EscribirALogSistema(adAppServer, DBGLVL_4, false, "[TClientServices::Logout] SessionId: %s", ARRAYOFCONST((Sysutils::GUIDToString(Session->SessionID))), lpInfo, "SMartis5server");
    EscribirALogSistema(adAppServer, DBGLVL_4, false, "[TClientServices::Logout] SessionId: %s, LoginTimestamp: %s, Usuario.GUID: %s, Usuario: %x", ARRAYOFCONST((Sysutils::GUIDToString(Session->SessionID), Session->Values["LoginTimestamp"], Session->Values["Usuario.GUID"], (int)Session->Values["Usuario"])), lpInfo, "SMartis5server");

    TUsuario *usuario = reinterpret_cast< TUsuario* >((int)Session->Values["Usuario"]);
    delete usuario;
  }

  DestroySession();
}
//---------------------------------------------------------------------------

Any idea what could be wrong?

Thanks!

Hello

I suspect that you’re using the SuperTCP channel (according to the testcase in your another thread). This approach won’t work with super channels, and its my bad that I didn’t explain why earlier (I just mentioned that this would be an overkill)

Main advantage of the SuperTCP is that it can receive server events faster. However there is a price for this: the connection has stay open, and while it is open it is tied to a some SessionID (to be able to send back events data for this Id)

So setting ClientID of the message only wouldn’t work for super channels - one would also have to perform reconnect operation, that is a rather costly operation. Otherwise on the server side this particular channel instance will stay tied to
a different SessionID.

You need to use a SimpleTCP channel instead, SuperTCP doesn’t give you any advantages in your scenario. In case you already have desktop clients that benefit from being connected via SuperTCP you could just expose your server using 2 different server channels on 2 different ports.

Regards

Hello Anton,

Thanks for the info and the detailed explanation.

I will publish another transport in the server to avoid changing the desktop clients.