I'm in a pickle with saving an object...

Dear all,

This is my first attempt at writing a distributed application in .NET using the C# language and I’ve now run into a hurdle that looks to be a little to high for me to leap over. It is for that reason that I’m hoping some kind soul may offer me a springboard from which to jump.

Please let me apologise in advance for my ‘wordiness’ – I am the least concise, and most verbose, person I know.J

This is what I want to do: Save an object’s data to a SQL Server 2000 without writing code that I’ll regret later.

I have the following physical system architecture:

A. - 1 server;

B. - Many client machines.

The application I’ve wrote so far is broken up into the following tiers:

=========================================================

1. Client Hosting Windows Forms application (sits on one of the many client machines (item ‘B’ in the list above))

2. Client object library that is hosted by the above Windows Forms application (point 1)

=========================================================

3. A Server Hosting Windows Forms application (sits on the 1 server machine (item ‘A’ in the list above)

4. Server object library that is hosted by the above (server) Windows Forms application (point 3).

=========================================================

5. An object library (called ExcelciaBusiness.dll - containing the declarations of the business objects) that is shared by both the client and server object libraries - so that both have a reference to the business object types – a copy held in two physical locations…

=========================================================

A general understanding of how the application works.

A Windows Forms application (ExcelciaHost.exe – item 1 in the list above) hosts a client assembly (called in this case, ‘ExcelciaClient.dll – item 2 in the list above). The hosting windows application communicates with this assembly for all business related requests. For example, if the Windows Forms application wants to get a new business object (declaration of which is stored in the shared object library – item 5 in the list above) it has to ask the client assembly (item 2 in list above) to give it one.

Example call

(Inside a button click event (in a Windows form) for example…)

{

//Code that obtains an instance of a comment object (for an existing comment), places some test data into the comment’s narrative, and then displays the comment’s narrative to the user by means of a MessageBox.

Comment comment_ = _client.GetComment (commentId);

comment_.narrative = “this is a test comment”;

MessageBox.Show(comment_.narrative);

}

When the client receives the request to GetComment(commentId) it, behind the scenes, then forwards the request (by means of .NET Remoting) to a remote server assembly (ExcelciaServer.dll - item 4 in the list above) which is hosted by another Windows Forms application (ExcelciaServerHost.exe - item 3 in the list above) on a remote machine (item ‘A’ in the physical layout above).

So,inside the _client object the following code is executed. Example does not include exception handling and authentication for purposes of clarity.

public Comment GetComment(string commentId)

{

return _server.GetComment(commentId, _ticket.ticketNumber);

}

Note: _ticket.number is the ticket number issued to the client (by the server) when an authenticated connection between the client and the server was initially made – when the user first logged in. It represents a user’s pass-number that has to be supplied to the server every time the client makes a request. The server uses this information to authenticate the client (and apply necessary security permissions) before responding to the request. If the request from the client is authorised the server then returns an instance of the requested object to the client. The client then passes this object back to the Windows Forms hosting application. So, from the perspective of the user of the _client object, the object is returned simply from the client, the user remains totally shielded from what actually takes place from the client point onwards (i.e. the user is totally unaware that the client is forwarding on a request to another object to obtain the data required).

That’s the basic crooks of how everything works.The windows forms application asks the client for an object, the client then forwards on the request to a remote server (in a different physical location to the client), the server handles the request (if authenticated) and then returns the object back to the client, which then returns it back to the windows forms application.

I hope I explained myself well, sorry if not.

Now, this is my predicament. I want to save an object, but I’m a little stuck… This is how I’ve gone about it – apologies if its rubbish, but this is my first attemptJ

When, for example, a comment is to be saved the user of the Comment class instance (object) simply has to invoke the ‘Save()’ method. One thing I forgot to mention earlier was that before an object is passed back to the Window Forms application from the _client object, the client first passes a reference (to itself) into the business object by means of a Client property.This is to allow the business object to invoke the client’s SaveObject(myObject objectInstance) method – which I’ll come to shortly.So, I suppose the code written above (GetComment) should be written a little more like this:

public Comment GetComment(string commentId)

{

Comment comment_ = _server.GetComment(commentId, _ticket.ticketNumber);

comment.Client = this;

return comment_;

}

Remember how I said that the there exists…

‘5. An object library (called ExcelciaBusiness.dll - containing the declarations of the business objects) that is shared by both the client and server object libraries so that both have a reference to the business object types.’

…well, this is where the declaration of the Comment class is contained. The property of the Comment class that is named ‘Client’ is marked as ‘internal’ to the assembly in which it resides. This is to hide it from the outside world (in this case, hiding it from the Windows Forms Hosting application (item 1 in the list above)) because this is something that the outside world should not have access to. However, I still need the _client object to access itas it needs business object type information (class declaration) for early binding compilation. To allow the _client object to see the ‘internal’ property, ‘Client’, of the ‘Comment’ object, I made the internal items (methods, properties, etc.) of the object library (point 5) visible to both the assemblies: ‘ExcelciaClient.dll (item 2 in the list above) and ExcelciaServer.dll (item 4 in the list above). I did this by adding the following lines to he assemble ExcelciaBusiness.dll:

[assembly:InternalsVisibleTo("ExcelciaClient")]

[assembly:InternalsVisibleTo("ExcelciaServer

I made it available to the ExcelciaServer.dll because the server (object on the remote machine) like the _client object, also needs to access information from the business objects that should not be visible to the general outside world – which I’ll come to on page 9678 of this book…J …it seems to be turning into one, I told you I’m verbose…please stick with me though, I really need help (in more ways than oneJ).

Ok, getting back to my earlier point, when the user of the business object wants to save the object he/she simply has to invoke the Save() method. Then, what should happen is that the business object should use it’s internal reference to the _client object to invoke it’s SaveObject method – passing itself as a parameter to the method.

E.g. The Commet’s Save Method…

public MyObject Save()

{

//validate object prior to this call…

return _client.SaveObject(this);
}

I also forgot to mention that my objects derive from a base class, MyObject – all saveable objects derive from this base class (as I need to invoke a Persist method later on that should be overridden by all derived classes).

So, what should now happen is that the object should be passed (by value) across the network (by means of .NET Remoting) to the server object (hosted in a Windows Forms Application) on the server machine.

Inside the _client’s SaveObject method…

public MyObject Save(MyObject objectInstance)

{

objectInstance.Client = null; << prevent the client from being serializ(s)ed for transmission across network

objectInstance = _server.SaveObject(_ticket.Number, objectInstance);//à this then sends the object to the server along with the client’s ticketnumber (for authorisation by the server)

//Once the object returns from the server re-issue the client reference

objectInstance.Client = this;

//Then return it to the caller of this function…

return objectInstance;
}

Waiting (to be invoked) inside the server class instance is the method (from line 2 of the above Save method)…

//Inside the server class on the remote machine:

publicMyObject Save(Guid ticketNumber, MyObject objectInstance)… this is where my problem (or one of them) lies…

My original plan looked something like this…

public MyObject Save(Guid ticketNumber, MyObject objectInstance)

{

//Use ticketNumber to get user id permissions, etc. and authorise application to proceed…

objectInstance.server = this;<<I’ll come to this in a mo’

objectInstance.Persist(); <<- tells the object to save itself…

objectInstance.server = null; << ready to be passed back across the network…

return objectInstance;

}

What would happen here is that the Comment object (in this case at base-class level (MyObject)) would be passe a reference to the Server object. The Server object has information that the Comment object needs, but currently does not have…

That information (in a simple case) is the reference to a database connection (I’m not using a DAL yet – I’ve not ready enough info on the subject) and the user’s Id - which is obtained by the server by performing a lookup of the client’s ticketNumber – I don’t want the client having any knowledge of physical database keys – I see it as a small security risk. I have other situations with other objects that are sent to the user without actual database Ids – instead enumerations are used, which are then later translated into key values when the business object arrives at the server (ready to be saved).

So what I have now is the situation whereby the ExcelciaServer.dll references the ExcelciaBusiness.dll for type information on the business objects, and because the business objects (in this case, a Comment class instance) need to reference the server class instance (that resides in the ExcelciaServer.dll) so that they can invoke methods on the server object – such as GetUserId, GetConnection (to the database) the ExcelciaBusiness.dll needs a reference to the ExcelciaServer.dll.This is a problem as it results in the need for a circular reference dependency – not good… bad design? Not sure – not proficient enough to say… got a bad feeling though… Anyway, to get around this problem I thought of creating an Interface for the server object called IServerSide that the server object (inside ExcelciaServer.dll) would implement, and the ExcelciaBusiness.dll could reference - that way the business objects could reference an interface to the server and the server could still reference the ExcelciaBusiness.dll without creating a circular reference – reference-mad am i…? The only downside to this is that the client also needs the class definitions of the business objects; the ExcelciaBusiness.dll therefore needs to be located on the client as well as the server (where any man and his dog can see the server side methods of the interface (IServerSide – which is totally inappropriate! – as they are declared as public)). So, to get around this I tried making the interface internal so that only the server object and client object (both classes defined the in the assemblies ExcelciaClient.dll ExcelciaServer.dll) could see the methods – hence, stopping every man and his dog (external to the dlls) viewing the server-side methods. Great I thought… Until I realised that for the server to implement the interface (which was marked as internal – yet visible to both ExcelciaClient.dll ExcelciaServer.dll because of the lines

[assembly:InternalsVisibleTo("ExcelciaClient")]

[assembly:InternalsVisibleTo("ExcelciaServer")])the methods that it implemented from the IServerSide interface need to be declared as public on the server – making them available to anyone whom may get their grubby hands on the server DLL. The problem with this is that one of the IServerSide methods is GetConnection, which returns an SQLConnection instance – ready access to my database. Now, I don’t want the server to expose this method because it means there’s potential for anyone else who may have access to the DLL to also reference it and gain direct access to my database – a big no, no!!!

So, what’s my alternative…? Design wise, I’m not sure – as I’m still pretty new to O.O. and.NET stuff… What I did think of doing was altering the server instance’s Save method so that, based on the type of object being passed into the method, I would pass various properties into the business objects before invoking the business object’s Persist() method, removing the need for the business object to reference the server…

E.g.

Not yet looked into how to determine object type at runtime, so please ignore my inability to type the necessary code… I hope you get the picture…

(Save method of the Server class instance):

public MyObject Save(MyObject objectInstance)

{

if (object is a Comment)

{

objectInstance.DBConnection = GetConnection();

objectInstance.UserId = GetUserIdFromTicket(ticketNumber);

}

else

{

if (it is something else)

{

//pass it some other parameters that it requires before saving itself…
}

}

objectInstance.Persist();

return objectInstance;

}

My only issue with this is that I may end up with a huge switch/if statement and I’m pretty sure that’s not a good design.It is for that reason that I’m looking for help. I am now at a crossroads and I really, really don’t know which way to turn – and whether there’s another road that I can’t yet see… PS. I cannot afford to start again as my deadline is fast approaching…

Many thanks if you managed to read until this point and congratulations if you can understand my terrible English. If anyone can point my in the right direction I would really, really appreciate it.

Regards,

Craig.

[34999 byte] By [crgsmrt] at [2007-12-27]
# 1

Hi,

Let me try to answer. I think that you complicated alot of things...

So you have a client server application with a single server and multiple clients. I would not have recommanded using remoting but if you don't want to use a different technology because of your schedule that would be understandable. You will need to change a lot of your code to remove the extra complexity that causes your problems.

Here's what your architechture should look like

Server:

  • Not a winform app (windows service more adequate)
  • Business logic dll (implements the objects)
  • Data access dll

Client:

  • winform app

Shared:

  • Object interface library

One of the problem that you have is that your business objects have methods which is not a good idea. The shared object library should only contain interfaces to the objects and these object should only have properties.

IComment is contained by the shared object interface library
Comment is implemented by the business logic dll
The data access dll creates Comments objects from the database
The server exposes a DataAccess class with methods to retreive comments and save objects

Pseudocode:

interface ISavable

{

void Save();

}

interface IComment

{

string Narative {get; set;}

}

class Comment : IComment , ISavable

{

string m_narative;

public void Save()

{

//connect to db

}

string Narative {

get {

return m_narative;

}

set {

m_narative = value;

}

}

}

class DataAccess

{

public IComment GetCommentById( int id );

public IComment[] GetAllComments();

public IComment CreateNewComment();

public void AddNewComment( IComment comment );

public void SaveObject(ISavable obj) {

obj.Save();

}

}

The client only know about the IComment, and the DataAccess class, nothing else. This way it is not possible for the client to access the database by referencing the dll. Once you add the client authentification to the pseudocode above, you just have to add support for other business objects.

Charles

cverdon at 2007-9-4 > top of Msdn Tech,Architecture,Architecture General...