Google
WWW Yariv Hammer's Code Site

Friday, December 16, 2005

Xml Schema Reader Application

Download XmlSchemaReader.zip from here (~200 KB)

Intoduction
There are several ways to handle Xmls in .NET . My favourite is DataSet.ReadXml method. It is fast, clean, efficient and productive.
Xml files are not that different from databases, although their use is somewhat different. The use of DataSet helps us as developers to use one API (which is ADO.NET) in order to handle both relational data when it is brought from the database or when it is brought from Xml files.

In order to read Xmls into Typed Datasets, or to make them valid, we should use Xml Schemas (xsd files). Now, designing Xml Schemas is a whole new ballgame. We can use the designer of VS.NET to help us, but I personally find it very awkward. Most of the time I know how my Xml file should look. I just don't know how the Schema file should look, and coding (or designing) it is a real pain. I also don't know how the DataSet will look when I import my Xml. Which elements are tables, which are fields, and so on. Sometimes DataRelations will be created for me by the ReadXml method, and primary keys too.

One more case which I find difficult, is when I get an already prepared xsd file, and it can be big. The person who designed the xsd may not even have an Xml file example for me. So it can be very difficult to work with it (I will need few hours spent to investigate the schema).

So here comes XmlSchemaReader for our aid.

The Application
XmlSchemaReader is a simple tool, written in C# by me, which is designed to address these issues. You simply load the Xml or Xsd file (by clicking on xml or xsd button, and then selecting it from the Open Dialog). The application will tell you if it is illegal to read it into a DataSet.

After the file is loaded the DataTables are seen in the ListBox in the left. When the selecting a table, the data is seen in the grid in Data tab. The data can be edited and then saved by pressing the Save button (be careful - it will be saved into the file specified in the TextBox). A neat feature is to attach the schema into an Xml file with data (By clicking Save Schema). So I can write my xml first (outside, in notepad for example), and then generate the schema later.

Two more tabs are Column and Relations. The Columns tab will show you the generated columns for each table, the datatype, and if it is a primary key (+ or -). Last, the Relations tab will show you the generated relations from the Xml.

Best Practice
I suggest you follow these steps:
1. Write the xml in Notepad, as you think it should look.
2. Load the xml to the XmlSchemaReader application.
3. Look at the tables and columns. Add data if you wish and save. I suggest you create a backup before saving.
4. Save the schema into a seperate file, or into the xml file.
5. Edit the schema outside the XmlSchemaReader application. For example, change types of columns.
6. Whenever you need a view, load the file into the application.

It really saved me a lot of time!!!

Last Words
I am giving the XmlSchemaReader for free, without any profit on my part. Please do not distribute it, or use it in production versions. Please use it only for your own use. Read the License file before using it. Feel free to advertise me of course ;)

Obviousely improvements can be made to the interface and capabilities. I will probably get there, but the tool is really helpful even as it is. You may post comments on any failures, bugs, or suggestions you have.

Tuesday, December 06, 2005

Recovering From Server/Clients Disconnections in Remoting

Introduction
In the previous article I showed how to create a Broadcast Event Server. The clients could send messages through the server to all the other connected clients.
One of the problems you have maybe seen is that if the client starts and the server is not working, than you will get an exception: "Additional information: No connection could be made because the target machine actively refused it".
On this tutorial I will show how to improve our Client/Server system by automatically connect the clients to the server once the server starts, get notifications about connectivity problems, and recover the connection.

Download Full Source Code (~300KB)
New: Download the Full Source Code in VB (~22KB)
Step 1 – Create a ServerConnector Class
First we add another interface to Common project. This interface is IServerConnector interface:
------------------------------------------------------------

namespace Common
{
public interface IServerConnector
{
bool IsAlive
{
get;
}
}
}

-------------------------------------------------------------
As you can see the interface has one read-only property IsAlive. This property will be called by the client periodically in order to check if the server is running.
Next we add a class called ServerConnector to the Server project:
-------------------------------------------------------------

using System;
using Common;

namespace Server
{
[Serializable()]
public class ServerConnector:MarshalByRefObject,IServerConnector
{
public bool IsAlive
{
get { return true; }
}

public override object InitializeLifetimeService()
{
return null;
}
}
}

---------------------------------------------------------------
This class will be called remotely by the client once in a while. If the return value is true then all is well. If the client gets an exception, then the server is not functioning.

Step 2 – Create a ClientConnector Class
Now that we have our ServerConnector class, we need to call the IsAlive method. We create a class that will have a Timer, and every period of time we will call ServerConnector. The class will have two events: ServerConnected and ServerDisconnected. This events can be used later by other objects in the client in order to provide functionality in those two states.
Add the following ClientConnector class to the Client project:
-------------------------------------------------------------

using System;
using System.Threading;
using System.Timers;
using Common;
using Timer = System.Timers.Timer;

namespace Client
{
public class ClientConnector
{
public ClientConnector()
{
_serverConnectivityTimer = new Timer(1000);
_serverConnectivityTimer.Elapsed += new ElapsedEventHandler (serverConnectivityTimer_Elapsed);
_serverConnectivityTimer.Start();
}

public event EventHandler ServerDisconnected;
public event EventHandler ServerReconnect;

private Timer _serverConnectivityTimer;
private void serverConnectivityTimer_Elapsed(object sender, ElapsedEventArgs e)
{
_serverConnectivityTimer.Stop();
CheckServerConnectivity();
_serverConnectivityTimer.Start();
}

private bool _serverIsAlive = false;
private void CheckServerConnectivity()
{
try
{
IServerConnector serverConnector = (IServerConnector) RemotingHelper. GetObject(typeof(IServerConnector));
bool isAlive = serverConnector.IsAlive;
if (isAlive && !_serverIsAlive)
SetServerAlive();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Thread t = new Thread(new ThreadStart (SetServerNotAlive));
t.Start();
t.Join();
}
}

private void SetServerAlive()
{
if (!_serverIsAlive && ServerReconnect != null)
ServerReconnect(this,EventArgs.Empty);
_serverIsAlive = true;
}

public void SetServerNotAlive()
{
if (_serverIsAlive && ServerDisconnected != null)
ServerDisconnected(this,EventArgs.Empty);
_serverIsAlive = false;
}

public bool ServerAlive
{
get
{
_serverConnectivityTimer.Stop();
CheckServerConnectivity();
_serverConnectivityTimer.Start();
return _serverIsAlive;
}
}
}
}

-------------------------------------------------------------
The timer elapses every second. A call to ServerConnector.IsAlive is done. If no exception was thrown, the server is connected (we raise the ServerReconnected event only if the state has changed). If an exception was thrown, we know that the server is not connected, and we can raise the ServerDisconnected event (only if soemthing has changed).

We now have a nice generic class to tell us when the server connects and when it disconnects.

Step 3 – Hook things up
First we need to register our remote objects in the configuration file.
Add the following line to the Server App.Config (inside the service tags):
<wellknown mode="Singleton" type="Server.ServerConnector, Server" objectUri= "ServerConnector.soap"/>

Add the following line to the Client App.Config (inside the client tags):
<wellknown type="Common.IServerConnector, Common" url= "tcp://localhost:16784/EventServer/ ServerConnector.soap"/>

Second, we change the ClientStartup class to contain an instance of ClientConnector. This will be the instance that all objects in the client will use:
-------------------------------------------------------------

using System;
using System.Runtime.Remoting;
using System.Windows.Forms;

namespace Client
{
public class ClientStartup
{
private static ClientConnector _connector;
public static ClientConnector Connector
{
get
{ return _connector; }
}

[STAThread]
static void Main()
{
_connector = new ClientConnector();
InitRemoting();
Application.Run(new ClientForm());
}

private static void InitRemoting()
{
string fileName = "client.exe.config";
RemotingConfiguration.Configure(fileName);
}
}
}

-------------------------------------------------------------

Next, we add changes to MessageBroadcaster in the client. We want the class to get the singleton objects from the server when the server reconnects (since we loose them when the server disconnects). Additionally we re-set the events (Once the server disconnects we loose the events).
-------------------------------------------------------------

using System;
using System.Net.Sockets;
using System.Threading;
using Common;

namespace Client
{

public class MessageBroadcaster:IBroadcaster
{
private IBroadcaster _bcaster = null;
private BroadcastEventHelper _eventHelper = null;

public MessageBroadcaster()
{
if (ClientStartup.Connector.ServerAlive)
{
InitializeObjects();
}
ClientStartup.Connector.ServerDisconnected += new EventHandler (Connector_ServerDisconnected);
ClientStartup.Connector.ServerReconnect += new EventHandler (Connector_ServerReconnect);
}

public void BroadcastMessage(Message msg)
{
if (_bcaster == null)
throw new ServerNotAliveException();
try
{
_bcaster.BroadcastMessage(msg);
}
catch (SocketException ex)
{
Console.WriteLine(ex.Message);
}
catch (ThreadAbortException ex)
{
Console.WriteLine(ex.Message);
}
}

private void HandleMessage(Message msg)
{
if (MessageArrived != null)
MessageArrived(msg);
}

private void InitializeObjects()
{
_bcaster = (IBroadcaster)RemotingHelper. GetObject(typeof(IBroadcaster));
_eventHelper = new BroadcastEventHelper();
_eventHelper.MessageArrivedLocally += new MessageArrivedHandler HandleMessage);
_bcaster.MessageArrived += new MessageArrivedHandler (_eventHelper.LocallyHandleMessageArrived);
}

private void Connector_ServerDisconnected(object sender, EventArgs e)
{
_bcaster = null;
_eventHelper = null;
}

private void Connector_ServerReconnect(object sender, EventArgs e)
{
InitializeObjects();
}

public event MessageArrivedHandler MessageArrived;
}
}

-------------------------------------------------------------
Step 4 – Fix the Demo
If you want to see the changes, you need to do the following modification to the ClientForm:
Change the constructor of the ClientForm:

public ClientForm()
{
InitializeComponent();
_messageBroadcaster.MessageArrived += new Common.MessageArrivedHandler (broadcaster_MessageArrived);
ClientStartup.Connector.ServerDisconnected += new EventHandler (Connector_ServerDisconnected);
ClientStartup.Connector.ServerReconnect += new EventHandler (Connector_ServerReconnect);
if (ClientStartup.Connector.ServerAlive)
this.Text = "Client Connected";
else
this.Text = "Client Not Connected";
}


Add the following event handlers to the form:

private void Connector_ServerDisconnected(object sender, EventArgs e)
{
this.Text = "Client Not Connected";
}
private void Connector_ServerReconnect(object sender, EventArgs e)
{
this.Text = "Client Connected";
}


Now you can run the server or the clients in any order you wish. If the client starts and the server is not started you will see it in the Text of the client form. If the server disconnects and reconnects you also see it in the clients. And most importantly – the client and server continue to function as they should.

Feel free to use everything here. Add links to my site if you wish.

Do not copy anything to other sites without adding link to here.

All the contents of the site belong to Yariv Hammer.