Monday, January 5, 2009

Xmlawy - XML Framework-C# freindly





Introduction



This framework provides an easy way to access XML files with support for inserting,
updating, deleting, and selecting by querying the XML like querying SQL.



Using the code



I'll start by writing simple lines of code that demonstrate the operations of inserting,
updating, deleting, and selecting, and then show the results in an XML file.



Here is how we create an XML file and access an object:


static void Main(string[] args)
{
BaseDataAccess access =
new BaseDataAccess("D:/xmltest.xml", "Objects");
}


Simple inserting



The Insert method takes an object of type BaseDataObject
as a parameter, which contains the name of the node, the value, the attributes,
and a list of children which are of the same type as well.


access.Insert(new BaseDataObject("type", "human")).Execute();
access.Insert(new BaseDataObject("type", "animal")).Execute();


2.png



Simple selecting



You can query the XML to select specific nodes:


List<BaseDataObject> dataList = access.Select("type").
Where("type").
IsEqualTo("animal").
ExecuteSelect<BaseDataObject>();

for (int i = 0; i < dataList.Count; i++)
{
Console.WriteLine(dataList[i].Name + " : " + dataList[i].Value);
}


3.png



Updating



It is also easy to update specific nodes:


access.Update("type").Set("type").EqualTo("animal").
Where("type").IsEqualTo("human").Execute();


4.png



Deleting



And here is how we delete:


access.Delete("type").Where("type").IsEqualTo("animal").Execute();


6.png



Advanced inserting



The way to insert children of children is to make a list of BaseDataObjects
and insert them in the list of the children of another BaseDataObject.
(The best way to do this is recursively.) The following code inserts three levels
of children in depth:


static void Main(string[] args)
{
// ACCESS
BaseDataAccess access =
new BaseDataAccess("test.xml", "objects");

// INSERTING 3 LEVELS IN DEPTH
List<BaseDataObject> parents = new List<BaseDataObject>();
for (int i = 0; i < 5; i++)
{
parents.Add(new BaseDataObject("Parent", MakeChilds(5)));
}
BaseDataObject parentOfParents =
new BaseDataObject("GrandParent", parents);
access.Insert(parentOfParents).Execute();

Console.WriteLine(access.XmlString);

Console.ReadLine();
}

private static List<BaseDataObject> MakeChilds(int count)
{
List<BaseDataObject> childs = new List<BaseDataObject>();
for (int i = 0; i < count; i++)
{
childs.Add(new BaseDataObject("N" + i.ToString(),
"V " + i.ToString()));
}
return childs;
}


7.png



Advanced selecting



Here is how we select any child in the selected objects, recursively, with the method
PrintObject(obj):


static void Main(string[] args)
{
// ACCESS
BaseDataAccess access = new BaseDataAccess("test.xml", "objects");

// INSERTING 3 LEVELS IN DEPTH
List<BaseDataObject> parents = new List<BaseDataObject>();
for (int i = 0; i < 5; i++)
{
parents.Add(new BaseDataObject("Parent", MakeChilds(5)));
}
BaseDataObject parentOfParents = new BaseDataObject("GrandParent", parents);
access.Insert(parentOfParents).Execute();

// SELECTING
List<BaseDataObject> objects =
access.Select("GrandParent").ExecuteSelect<BaseDataObject>();
for (int i = 0; i < objects.Count; i++)
{
PrintObject(objects[i]);
}

Console.ReadLine();
}

private static List<BaseDataObject> MakeChilds(int count)
{
List<BaseDataObject> childs = new List<BaseDataObject>();
for (int i = 0; i < count; i++)
{
childs.Add(new BaseDataObject("N" + i.ToString(),
" V" + i.ToString() + " "));
}
return childs;
}

private static void PrintObject(BaseDataObject obj)
{
if (obj.Childs.Count > 0)
{
Console.WriteLine(obj.Name + " >");
for (int i = 0; i < obj.Childs.Count; i++)
{
PrintObject(obj.Childs[i]);
}
}
else
{
Console.WriteLine(obj.Name + " : " + obj.Value);
}
}


8.png



Inserting simple objects



If you want to quickly save the properties of an object in XML (simple serialization),
there is another generic Insert method which takes any object as a
parameter and inserts all the properties of type string or
int in the XML file.


class MyClass
{
public MyClass()
{
Age = 20;
FirstName = "Islam";
LastName = "Eldemery";
Address = "Egypt, Cairo, bla bla bla";
}

public int Age { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }

}

class Program
{
static void Main(string[] args)
{
MyClass obj = new MyClass();

// ACCESS
BaseDataAccess access = new BaseDataAccess("test.xml", "objects");

// INSERT
access.Insert<MyClass>(obj).Execute();

Console.WriteLine(access.XmlString);

Console.ReadLine();
}
}


9.png



So far so good, let's dig deeper..



Overriding the virtual methods in the base class



Imagine you want to encrypt the XML after inserting, and decrypt it before selecting.
This can be easily done by overriding the base methods as follows:


class XmlAccess : BaseDataAccess
{
XMLEncryptor encryptor;

public XmlAccess(string path, string rootElement)
: base(path, rootElement)
{
encryptor = new XMLEncryptor(path);
}

public override bool Execute()
{
bool executed = base.Execute();

///////////////////////////////////////////////////
// ENCRYPTOR GOES HERE
encryptor.Encrypt(base._objectToInsert.Name, "myKey");
///////////////////////////////////////////////////

return executed;
}

public override List<T> ExecuteSelect<T>()
{
///////////////////////////////////////////////////
// ENCRYPTOR GOES HERE
encryptor.Decrypt("myKey");
///////////////////////////////////////////////////

return base.ExecuteSelect<T>();
}
}


Select From




// ACCESS
// True will delete the file if exist (for debugging purposes)
BaseDataAccess access = new BaseDataAccess(@"test4.xml", "doc", true);

// Initialize 6 childs
List<BaseDataObject> childs = new List<BaseDataObject>();
for (int i = 0; i < 6; i++)
{
childs.Add(new BaseDataObject("Name" + i.ToString(), "Value" + i.ToString()));
}

// Insert
access.Insert(new BaseDataObject("Human", childs)).Execute();
access.Insert(new BaseDataObject("Animal", childs)).Execute();

// Select
List<BaseDataObject> selected = access.Select("Human").Where("Name2").IsEqualTo("Value2").ExecuteSelect<BaseDataObject>();
// (Prints 1)
Console.WriteLine(selected.Count);
// selected[0] is the Human osbject and holds 6 childs objects

// Select
List<BaseDataObject> selectedN = access.Select("Name2").From("Human").Where("Name3").IsEqualTo("Value3").ExecuteSelect<BaseDataObject>();
// (Prints 1)
Console.WriteLine(selectedN.Count);
// selected[0] is the child osbject


Select all nodes



You can also select all the nodes in the root node with all children and children
of children, in one line of code:


List<BaseDataObject> objects = access.SelectAll<BaseDataObject>();


And again, this is to loop on them recursively:


static void Main(string[] args)
{
// ACCESS
BaseDataAccess access =
new BaseDataAccess(@"D:\XmlFiles\StructureMap.xml",
"doesntMatter");

// SELECTING
List<BaseDataObject> objects = access.SelectAll<BaseDataObject>();

// DISPLAYING
for (int i = 0; i < objects.Count; i++)
PrintObject(objects[i]);

Console.ReadLine();
}

private static void PrintObject(BaseDataObject obj)
{
if (obj.Childs.Count > 0)
{
Console.WriteLine(obj.Name + " >");

for (int i = 0; i < obj.Childs.Count; i++)
PrintObject(obj.Childs[i]);
}
else
Console.WriteLine(obj.Name + " : " + obj.Value);
}


History



The code in the library is fully commented. This is version 1. I hope we can add
more and more features in the future.



Wednesday, May 28, 2008

WCF Server/Client Chat Sample


WCF Server/Client Chat


Download


Screen Shots



This is a simple and smart WCF Client/Server Chat sample, Amit Gupta (visitor to my blog) asked how to convert a
client/server sockets app. into client/server WCF app., which got me interested to make this sample.



It consists of two apps. both are windows forms, the server one includes the contracts and implements the service.
The client one implements the callcack.



WCF server/client sample features:

  • Handling concurrency

  • Handling server state

  • Handling client state

  • Checking server availability

  • Reliable sessions

  • Asynchronous operations



Code will be as follows:



  • Server:

    • form1.cs

    • app.config



  • Client:

    • form1.cs




Server


Form1.cs



using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ServiceModel;

namespace Server
{
[ServiceContract(CallbackContract=typeof(ISampleChatCallback), SessionMode=SessionMode.Required)]
public interface ISampleChat
{
[OperationContract(IsInitiating=true, IsOneWay=true)]
void Connect(string name);

[OperationContract(IsOneWay = true)]
void SayToServer(string name, string msg);

[OperationContract(IsTerminating = true, IsOneWay = true)]
void Disconnect(string name);
}


public interface ISampleChatCallback
{
[OperationContract(IsOneWay = true)]
void SayToClient(string msg);
}



[ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Multiple, InstanceContextMode=InstanceContextMode.Single)]
public partial class Form1 : Form, ISampleChat
{
ServiceHost host;
private Dictionary< string, ISampleChatCallback> clients = new Dictionary< string, ISampleChatCallback>();
private object syncObj = new object();

public Form1()
{
InitializeComponent();
EnableControls(false);
}

private void buttonStart_Click(object sender, EventArgs e)
{
host = null;
host = new ServiceHost(this);
host.Opened += new EventHandler(host_Opened);
host.Closed += new EventHandler(host_Closed);
host.Faulted += new EventHandler(host_Faulted);

//start listening..
host.Open();
}

void host_Faulted(object sender, EventArgs e)
{
HandleHost();
}

void host_Closed(object sender, EventArgs e)
{
HandleHost();
}

void host_Opened(object sender, EventArgs e)
{
HandleHost();
}

private void HandleHost()
{
switch (host.State)
{
case CommunicationState.Closed:
labelStatus.Text = "Closed";
EnableControls(false);
break;
case CommunicationState.Faulted:
labelStatus.Text = "Faulted";
EnableControls(false);
host.Abort();
break;
case CommunicationState.Opened:
labelStatus.Text = "Opened";
EnableControls(true);
break;
}
}

private void EnableControls(bool opened)
{
if (opened)
{
buttonStart.Enabled = false;
buttonStop.Enabled = true;
buttonSend.Enabled = true;
richTextBox1.Enabled = true;
textBox1.Enabled = true;
listBox1.Enabled = true;
}
else
{
buttonStart.Enabled = true;
buttonStop.Enabled = false;
buttonSend.Enabled = false;
richTextBox1.Enabled = false;
textBox1.Enabled = false;
listBox1.Enabled = false;
}
}

private void buttonStop_Click(object sender, EventArgs e)
{
host.Close();
}

private void buttonSend_Click(object sender, EventArgs e)
{
//send message to all clients
foreach (ISampleChatCallback cb in clients.Values)
{
cb.SayToClient("Server : " + textBox1.Text.ToString());
}
richTextBox1.Text += "\n" + "Server : " + textBox1.Text.ToString();
textBox1.Text = "";
}

private ISampleChatCallback CurrentCallback
{
get
{
return OperationContext.Current.GetCallbackChannel();
}
}

#region ISampleChat Members

public void Connect(string name)
{
if (!clients.ContainsKey(name))
{
lock (syncObj)
{
clients.Add(name, CurrentCallback);
listBox1.Items.Add(name);
}

richTextBox1.Text += "\n" + name + " connected..";


//u may want to tell other clients that someone just connected
foreach (ISampleChatCallback cb in clients.Values)
{
cb.SayToClient("Client " + name + " connected.");
}
}


}

public void Disconnect(string name)
{
if (clients.ContainsKey(name))
{
lock (syncObj)
{
clients.Remove(name);
listBox1.Items.Remove(name);
}
}

richTextBox1.Text += "\n" + name + " disconnected..";

//u may want to tell other clients that someone just disconnected
foreach (ISampleChatCallback cb in clients.Values)
{
cb.SayToClient("Client " + name + " disconnected.");
}
}

public void SayToServer(string name, string msg)
{
//here u get the message from the client
//do whatever u want..
richTextBox1.Text += "\n" + name + " : " + msg;

//u may want to tell other clients that someone said something
foreach (ISampleChatCallback cb in clients.Values)
{
cb.SayToClient(name + " : " + msg);
}
}

#endregion
}
}



App.config



< ?xml version="1.0" encoding="utf-8" ?>
< configuration>
< system.serviceModel>

< services>
< service name="Server.Form1" behaviorConfiguration="serviceBehaviorConfiguration">
< host>
< baseAddresses>
< add baseAddress="net.tcp://localhost:4477/SampleWCFChat/"/>
< add baseAddress="http://localhost:4478/SampleWCFChat/"/>
< /baseAddresses>
< /host>
< endpoint address="tcp"
binding="netTcpBinding"
bindingConfiguration="tcpBindingConfiguration"
contract="Server.ISampleChat"/>

< endpoint address="mex"
binding="mexTcpBinding"
contract="IMetadataExchange"/>
< /service>
< /services>
< bindings>
< netTcpBinding>
< binding name="tcpBindingConfiguration"
closeTimeout="00:00:05"
maxBufferSize="1048576"
maxBufferPoolSize="1048576"
maxConnections="10"
maxReceivedMessageSize="1048576"
openTimeout="00:00:05"
receiveTimeout="01:00:00"
sendTimeout="01:00:00"
transferMode="Buffered">
< readerQuotas maxArrayLength="1048576" maxBytesPerRead="1048576" maxStringContentLength="1048576"/>
< reliableSession enabled="true" inactivityTimeout="01:00:00"/>
< /binding>
< /netTcpBinding>
< /bindings>
< behaviors>
< serviceBehaviors>
< behavior name="serviceBehaviorConfiguration">
< serviceDebug includeExceptionDetailInFaults="true"/>
< serviceMetadata httpGetEnabled="true"/>
< /behavior>
< /serviceBehaviors>
< /behaviors>


< /system.serviceModel>
< /configuration>



Client


Form1.cs



using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ServiceModel;
using System.ServiceModel.Description;

namespace Client
{
public partial class Form1 : Form, SampleService.ISampleChatCallback
{
public Form1()
{
InitializeComponent();
EnableControls(false);
}


SampleService.SampleChatClient proxy;
private string name = string.Empty;
private delegate void MyInvoker();

#region ISampleChatCallback Members

public void SayToClient(string msg)
{
richTextBox1.Text += "\n" + msg;
}

public IAsyncResult BeginSayToClient(string msg, AsyncCallback callback, object asyncState)
{
throw new NotImplementedException();
}

public void EndSayToClient(IAsyncResult result)
{
throw new NotImplementedException();
}

#endregion


private void EnableControls(bool connected)
{
if (connected)
{
buttonDisconnect.Enabled = true;
buttonConnect.Enabled = false;
buttonSend.Enabled = true;
richTextBox1.Enabled = true;
textBoxMsg.Enabled = true;
}
else
{
richTextBox1.Text = "";
richTextBox1.Enabled = false;
textBoxMsg.Enabled = false;
buttonDisconnect.Enabled = false;
buttonConnect.Enabled = true;
buttonSend.Enabled = false;
}
}

private void buttonDisconnect_Click(object sender, EventArgs e)
{
proxy.DisconnectAsync(name);
richTextBox1.Text = "";
}

private void buttonSend_Click(object sender, EventArgs e)
{
proxy.SayToServerAsync(name, textBoxMsg.Text.ToString());
textBoxMsg.Text = "";
}

private void buttonConnect_Click(object sender, EventArgs e)
{
//u may want to skip CheckServer to boost speed
if (CheckServer())
{
proxy = null;
InstanceContext context = new InstanceContext(this);
proxy = new Client.SampleService.SampleChatClient(context);

proxy.InnerDuplexChannel.Opened += new EventHandler(InnerDuplexChannel_Opened);
proxy.InnerDuplexChannel.Closed += new EventHandler(InnerDuplexChannel_Closed);
proxy.InnerDuplexChannel.Faulted += new EventHandler(InnerDuplexChannel_Faulted);

name = textBoxName.Text.ToString();
proxy.ConnectAsync(name);
}
else
{
MessageBox.Show("Sorry, Server is not available");
}
}

private bool CheckServer()
{
MetadataExchangeClient mexClient;
bool serverIsUp = false;
try
{
string address = "net.tcp://localhost:4477/SampleWCFChat/mex";
mexClient = new MetadataExchangeClient(new Uri(address), MetadataExchangeClientMode.MetadataExchange);
MetadataSet metadata = mexClient.GetMetadata();

serverIsUp = true;
}
catch
{
serverIsUp = false;
}

return serverIsUp;
}

void InnerDuplexChannel_Faulted(object sender, EventArgs e)
{
if (InvokeRequired)
{
this.Invoke(new MyInvoker(HandleProxy));
return;
}
HandleProxy();
}

void InnerDuplexChannel_Closed(object sender, EventArgs e)
{
if (InvokeRequired)
{
this.Invoke(new MyInvoker(HandleProxy));
return;
}
HandleProxy();
}

void InnerDuplexChannel_Opened(object sender, EventArgs e)
{

if (InvokeRequired)
{
this.Invoke(new MyInvoker(HandleProxy));
return;
}
HandleProxy();
}

private void HandleProxy()
{
switch (proxy.State)
{
case CommunicationState.Closed:
labelStatus.Text = "Disconnected";
EnableControls(false);
break;
case CommunicationState.Faulted:
labelStatus.Text = "Faulted";
EnableControls(false);
break;
case CommunicationState.Opened:
labelStatus.Text = "Connected";
EnableControls(true);
break;
}
}
}
}

Wednesday, April 23, 2008

A SilverLight-WCF Chat



kick it on DotNetKicks.com

Introduction



This is very simple and basic SilverLight-WCF chat application that uses basicHttpBinding and a timer to call the
WCF service in order to refresh the SilverLight client each certain amount of time.





Technique


The solution consists of:




  • ASP.NET Web Project


    • ASP.NET page to host the SilverLight application

    • WCF Service which contains two generic lists to hold online chatters and messages history



  • SilverLight Project (Client)



SilverLight client calls the Join() method and starts the timer, the timer ticks every two seconds
and calls GetChatters() and GetMessages() methods, client now is free to call
Say() method to send message to other clients or call Leave() method to disconnect
from the service and stop the timer.



WCF service responds to client calls to add or remove Client or Message from
the generic lists, or send these lists back to the client.



The Code



WCF Service


This is the service contract



[ServiceContract]
public interface IbasicChatService
{
[OperationContract(IsOneWay = false)]
bool Join(Chatter _chatter);

[OperationContract(IsOneWay = true)]
void Say(Message _msg);

[OperationContract(IsOneWay = false)]
List< Chatter> GetChatters();

[OperationContract(IsOneWay = false)]
List< Message> GetMessages();

[OperationContract(IsOneWay = true)]
void Leave(Chatter _chatter);
}


This is the client and message data contracts



[DataContract]
public class Chatter
{
private string _name;
private DateTime _time;

[DataMember]
public string Name
{
get { return _name; }
set { _name = value; }
}

[DataMember]
public DateTime Time
{
get { return _time; }
set { _time = value; }
}
}

[DataContract]
public class Message
{
private string _sender;
private string _content;
private DateTime _time;

[DataMember]
public string Sender
{
get { return _sender; }
set { _sender = value; }
}

[DataMember]
public string Content
{
get { return _content; }
set { _content = value; }
}

[DataMember]
public DateTime Time
{
get { return _time; }
set { _time = value; }
}
}



This is the service implementation



[AspNetCompatibilityRequirements(RequirementsMode=AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single,
ConcurrencyMode=ConcurrencyMode.Multiple)]
public class basicChatService : IbasicChatService
{

private List< Chatter> chatters = new List< Chatter>();
private List< Message> messages = new List< Message>();

private object syncObj = new object();


#region IbasicChatService Members

public bool Join(Chatter _chatter)
{
foreach (Chatter chtr in this.chatters)
{
if (chtr.Name == _chatter.Name)
{
return false;
}
}

lock (syncObj)
{
this.chatters.Add(_chatter);

Message msg = new Message();
msg.Sender = "Service";
msg.Content = "---- " + _chatter.Name + " joined chat ----";
msg.Time = DateTime.Now;

this.messages.Add(msg);
}

return true;
}

public void Say(Message _msg)
{
lock (syncObj)
{
this.messages.Add(_msg);
}
}

public List< Chatter> GetChatters()
{
return this.chatters;
}

public List< Message> GetMessages()
{
return this.messages;
}

public void Leave(Chatter _chatter)
{

foreach (Chatter chtr in this.chatters)
{
if (chtr.Name == _chatter.Name)
{
this.chatters.Remove(chtr);

if (this.chatters.Count < 1)
{
this.messages.Clear();
return;
}
Message msg = new Message();
msg.Sender = "Server";
msg.Content = "---- " + _chatter.Name + " leftt chat ----";
msg.Time = DateTime.Now;

this.messages.Add(msg);

return;
}
}
}

#endregion
}


To integrate SilverLight application with WCF service you have to use basicHttpBinding as followed in the
service configuarion file



< system.serviceModel>
< serviceHostingEnvironment aspNetCompatibilityEnabled="true">
< /serviceHostingEnvironment>
< services>
< service behaviorConfiguration="SilverlightApp_Host.basicChatServiceBehavior"
name="SilverlightApp_Host.basicChatService">
< host>
< baseAddresses>
< add baseAddress="http://localhost:6464/localsystem"/>
< /baseAddresses>
< /host>
< endpoint address=""
binding="basicHttpBinding"
contract="SilverlightApp_Host.IbasicChatService">
< identity>
< dns value="localhost"/>
< /identity>
< /endpoint>
< endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
< /service>
< /services>
< behaviors>
< serviceBehaviors>
< behavior name="SilverlightApp_Host.basicChatServiceBehavior">
< serviceMetadata httpGetEnabled="true"/>
< serviceDebug includeExceptionDetailInFaults="true"/>
< /behavior>
< /serviceBehaviors>
< /behaviors>
< bindings>
< basicHttpBinding>
< binding name="basicBinding"
closeTimeout="00:00:20"
maxBufferPoolSize="1048576"
maxBufferSize="1048576"
maxReceivedMessageSize="1048576"
openTimeout="00:00:20"
receiveTimeout="01:00:00"
sendTimeout="00:01:00"
transferMode="Buffered">
< readerQuotas maxArrayLength="1048576" maxBytesPerRead="1048576"
maxStringContentLength="1048576"/>
< security mode="None">
< transport clientCredentialType="Windows"/>
< /security>
< /binding>
< /basicHttpBinding>
< /bindings>
< /system.serviceModel>



SilverLight Client



I'm a very bad designer so I'll let you imagine any design for this application which of course will be better than
this, this is just two list boxes to hold the online chatters and messages history coming from the service, other
controls are three buttons to join, leave the chat or send a message, and two textboxes for chatter name, and message.



This is the application xaml code



< UserControl x:Class="SilverlightApp.Page"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
< Grid x:Name="LayoutRoot" >

< Grid.Background>
< LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
< GradientStop Color="Black" Offset="0"/>
< GradientStop Color="LavenderBlush" Offset="1"/>
< /LinearGradientBrush>
< /Grid.Background>

< Grid.RowDefinitions>
< RowDefinition Height="50"/>
< RowDefinition Height="25"/>
< RowDefinition Height="25"/>
< RowDefinition Height="200"/>
< RowDefinition Height="30"/>
< RowDefinition Height="50"/>
< /Grid.RowDefinitions>
< Grid.ColumnDefinitions>
< ColumnDefinition Width="70"/>
< ColumnDefinition Width="360"/>
< ColumnDefinition Width="120"/>
< ColumnDefinition Width="70"/>
< /Grid.ColumnDefinitions>

< TextBlock x:Name="labelStatus"
Margin="8, 5, 2, 2"
Grid.Row="1"
Grid.Column="2"
FontFamily="Consolas"
FontSize="13"
Foreground="White">Offline< /TextBlock>

< Grid x:Name="layoutLogin" Grid.Row="2" Grid.Column="1">
< Grid.RowDefinitions>
< RowDefinition Height="25" />
< /Grid.RowDefinitions>
< Grid.ColumnDefinitions>
< ColumnDefinition Width="80"/>
< ColumnDefinition Width="160"/>
< ColumnDefinition Width="*"/>
< /Grid.ColumnDefinitions>

< TextBlock FontFamily="Consolas"
Margin="4, 2, 2, 0"
Grid.Row="0"
Grid.Column="0"
FontSize="12"
Foreground="White">User Name:< /TextBlock>

< TextBox x:Name="textboxName"
Margin="2, 2, 2, 2"
Grid.Row="0"
Grid.Column="1">< /TextBox>

< Button x:Name="buttonJoin"
Background="Transparent"
Margin="2, 2, 2, 2"
Grid.Row="0"
Grid.Column="2"
Click="buttonJoin_Click"
Content="Join">< /Button>

< /Grid>

< Button x:Name="buttonLeave"
Background="Transparent"
Margin="2, 2, 2, 2"
Click="buttonLeave_Click"
Grid.Row="2"
Grid.Column="2"
Content="Leave">< /Button>

< ListBox x:Name="listBoxMsgs"
LayoutUpdated="listBoxMsgs_LayoutUpdated"
Margin="2, 2, 2, 2"
Grid.Row="3"
Grid.Column="1">< /ListBox>

< ListBox x:Name="listBoxNames"
Margin="2, 2, 2, 2"
Grid.Row="3"
Grid.Column="2">< /ListBox>

< TextBox x:Name="textboxMsg"
Margin="2, 2, 2, 2"
Grid.Row="4"
Grid.Column="1">< /TextBox>

< Button x:Name="buttonSend"
Background="Transparent"
Margin="2, 2, 2, 2"
Grid.Row="4"
Grid.Column="2"
Click="buttonSend_Click"
Content="Send">< /Button>

< /Grid>
< /UserControl>



SilverLight client implementation consists of some feilds, constructor, connection event handlers, private methods, and UI event handlers


Feilds



namespace SilverlightApp
{
public partial class Page : UserControl
{

#region Feilds

//TIMER
DispatcherTimer _timer = null;

SVC.IbasicChatServiceClient proxy = null;
SVC.Chatter localChatter = null;

//List to hold online chatters
List< SVC.Chatter> chatters = new List< SilverlightApp.SVC.Chatter>();

//List to hold messages history
List< SVC.Message> messages = new List< SilverlightApp.SVC.Message>();

//To enable listbox auto scroll
bool flag = false;

#endregion

...



Page constructor



public Page()
{
InitializeComponent();
buttonLeave.IsEnabled = false;
buttonSend.IsEnabled = false;
textboxMsg.KeyDown += new KeyEventHandler(textboxMsg_KeyDown);

//Create Timer and set interval
_timer = new DispatcherTimer();
_timer.Interval = TimeSpan.FromSeconds(2);
_timer.Tick += new EventHandler(_timer_Tick);
}



Connection event handlers




#region Connection Event Handlers

void proxy_JoinCompleted(object sender, SilverlightApp.SVC.JoinCompletedEventArgs e)
{
if (e.Result)
{
labelStatus.Text = "Online";
this._timer.Start();
}
else
{
proxy.Close();
labelStatus.Text = "Name Found";
}
}

void proxy_LeaveCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
proxy.Close();
}

void proxy_GetMessagesCompleted(object sender, SilverlightApp.SVC.GetMessagesCompletedEventArgs e)
{
listBoxMsgs.Items.Clear();
foreach (SVC.Message msg in e.Result)
{
//This will not scroll the listbox
//listBoxMsgs.Items.Add(msg.Sender + " : " + msg.Content);

//Auto scroll, AddItem() is a private method
AddItem(msg.Sender + " : " + msg.Content);
flag = true;
}

}

void proxy_GetChattersCompleted(object sender, SilverlightApp.SVC.GetChattersCompletedEventArgs e)
{
listBoxNames.Items.Clear();
foreach (SVC.Chatter chtr in e.Result)
{
listBoxNames.Items.Add(chtr.Name);
}
}

void InnerChannel_Opened(object sender, EventArgs e)
{
HandleProxy();
}

void InnerChannel_Faulted(object sender, EventArgs e)
{
HandleProxy();
}

void InnerChannel_Closed(object sender, EventArgs e)
{
HandleProxy();
}


#endregion



Private methods



#region Private Methods

public void AddItem(String messageText)
{
listBoxMsgs.Items.Add(new ListBoxItem { Content = messageText });
}

private void Join()
{
proxy = null;
//CREATE PROXY
proxy = new SilverlightApp.SVC.IbasicChatServiceClient();
proxy.InnerChannel.Closed += new EventHandler(InnerChannel_Closed);
proxy.InnerChannel.Faulted += new EventHandler(InnerChannel_Faulted);
proxy.InnerChannel.Opened += new EventHandler(InnerChannel_Opened);

//CREATE LOCAL CLIENT
this.localChatter = new SilverlightApp.SVC.Chatter();
this.localChatter.Name = textboxName.Text.ToString();
this.localChatter.Time = DateTime.Now;

//JOIN()
proxy.JoinAsync(this.localChatter);
proxy.JoinCompleted +=
new EventHandler< SilverlightApp.SVC.JoinCompletedEventArgs>(proxy_JoinCompleted);
}

private void Send()
{
if (proxy != null && proxy.State == CommunicationState.Opened)
{
SVC.Message msg = new SilverlightApp.SVC.Message();
msg.Sender = this.localChatter.Name;
msg.Content = textboxMsg.Text.ToString();
msg.Time = DateTime.Now;

proxy.SayAsync(msg);
textboxMsg.Text = "";
}
else
{
HandleProxy();
}
}

private void HandleProxy()
{
if (proxy != null)
{
switch (proxy.State)
{
case CommunicationState.Closed:
proxy = null;
labelStatus.Text = "Offline";
buttonJoin.IsEnabled = true;
buttonLeave.IsEnabled = false;
buttonSend.IsEnabled = false;
listBoxMsgs.Items.Clear();
listBoxNames.Items.Clear();
break;
case CommunicationState.Closing:
break;
case CommunicationState.Created:
break;
case CommunicationState.Faulted:
proxy.Abort();
proxy = null;
labelStatus.Text = "Offline";
buttonJoin.IsEnabled = true;
buttonLeave.IsEnabled = false;
buttonSend.IsEnabled = false;
listBoxMsgs.Items.Clear();
listBoxNames.Items.Clear();
break;
case CommunicationState.Opened:
buttonJoin.IsEnabled = false;
buttonLeave.IsEnabled = true;
buttonSend.IsEnabled = true;
labelStatus.Text = "Online";
break;
case CommunicationState.Opening:
break;
default:
break;
}
}
else
{
//Join();
}
}

#endregion


And finally the UI event handlers



#region UI Events

void _timer_Tick(object sender, EventArgs e)
{
//Refresh online chatters and messages by calling GetChattersAsync() and GetMessagesAsync()
proxy.GetChattersAsync();
proxy.GetChattersCompleted +=
new EventHandler< SilverlightApp.SVC.GetChattersCompletedEventArgs>(proxy_GetChattersCompleted);

proxy.GetMessagesAsync();
proxy.GetMessagesCompleted +=
new EventHandler< SilverlightApp.SVC.GetMessagesCompletedEventArgs>(proxy_GetMessagesCompleted);
}

void textboxMsg_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
Send();
}
}

private void buttonJoin_Click(object sender, RoutedEventArgs e)
{
Join();
}

private void buttonSend_Click(object sender, RoutedEventArgs e)
{
Send();
}

private void buttonLeave_Click(object sender, RoutedEventArgs e)
{
this._timer.Stop();
if (proxy != null && proxy.State == CommunicationState.Opened)
{
proxy.LeaveAsync(this.localChatter);
proxy.LeaveCompleted +=
new EventHandler< System.ComponentModel.AsyncCompletedEventArgs>(proxy_LeaveCompleted);
}
else
{
HandleProxy();
}
}

private void listBoxMsgs_LayoutUpdated(object sender, EventArgs e)
{
if (flag && listBoxMsgs.Items.Count > 1)
{
listBoxMsgs.ScrollIntoView(listBoxMsgs.Items[listBoxMsgs.Items.Count - 1]);
flag = false;
}
}

#endregion




Other Stuff


Enable cross domain calls for SilverLight application


In order to enable cross domain calls just copy this file clientaccesspolicy.xml to your host root, if you use IIS then
copy the file to WWWRoot folder and then restart IIS. (file is included in the source code).

Tuesday, April 15, 2008

WCF WPF Internet Chat Application with File Transefer

kick it on DotNetKicks.com

Try The Chat Online




Chat Login


Chat Login

See the article on CodeProject for better reading

Download Source Code.

About Online Chat..


As I let the service opened 24x7 , I expected to see some people who will try the demo chat online, this picture shows some (I wished I could talk to them, Thank you for trying).


Try Chat Online

Friday, March 7, 2008

ASP.NET Ajax Chat Web Application


Introduction




I've developed a simple chat web application that deals with xml files to store information about online chatters,
using ASP.NET, C#, Linq to XML, Ajax -Anthem Framework-.




Chat UI


Technique




The application includes a class to handle the xml file by some methods to create the xml or load it, to save info,
modify it, or remove it.
The public methods of this class are:

  • ---------------------------------------------------------------------------

  • void Join(string userName, DateTime dateTime)

  • void Say(string userName, string msg, DateTime dateTime)

  • void Leave(string userName, DateTime dateTime)

  • ---------------------------------------------------------------------------

  • List< string > GetOnlineNames()

  • int GetNumberOfOnlines()

  • List< string > GetMessagesHistory()

  • ---------------------------------------------------------------------------





The Client-side calls (asynchronously using ajax) these methods Join(), Say(), Leave().


And a timer on the Server-side ticks every fixed time (2 seconds in our app.),
to read the xml (by calling these methods GetOnlineNames(), GetNumberOfOnlines(), GetMessagesHistory())
and refresh our controls (asynchronously).

The Code


Starting with our XHandle.cs class which is like a chat room, lets see its members:

using System.Linq;
using System.Xml.Linq;
using System.Collections.Generic;

public class XHandle
{
private string _path;
private string _xPageName;
private string _FullPath;

private XDocument _xDoc;
private bool _xDocCreated;

private XElement _usersRoot;
private int _onlineUsers;

....
}


The constructor job is to:


Know the path of the App_Data folder (_path),
and the name of the xml file (_xPageName), to set the _FullPath
that we will be using shortly.


Check to see if the xml file is already created, or create it if it is not.

public XHandle(string _path)
{
this._path = _path;
this._xDocCreated = false;
this._xPageName = "Default";
this._FullPath = this._path + @"\" + this._xPageName + ".xml";
this._onlineUsers = 0;

//Check the xml page if already exist
LoadXPage();

//or create it if it doesnt
if (!_xDocCreated)
{
CreateXPage();
}

}

public XHandle(string _path, string ChatRoomName)
{
this._path = _path;
this._xDocCreated = false;
this._xPageName = ChatRoomName;
this._FullPath = this._path + @"\" + this._xPageName + ".xml";

//Check the xml page if already exist
LoadXPage();

//or create it if it doesnt
if (!_xDocCreated)
{
CreateXPage();
}

}


Private Methods


The LoadXPage() and CreateXPage() are private methods to load or create
the xml file and set the _xDocCreated bool to true or false;


private void CreateXPage()
{
_xDoc = new XDocument();
XDeclaration dec = new XDeclaration("1.0", "utf-8", "yes");
_xDoc.Declaration = dec;
_usersRoot = new XElement("users");
_xDoc.Add(_usersRoot);
_xDoc.Save(_FullPath);
_xDocCreated = true;
}

private void LoadXPage()
{
try
{
_usersRoot = XElement.Load(_FullPath);
_xDocCreated = true;
}
catch
{
_xDocCreated = false;
}
}


Creating the xml file will end up with this:


XML File




Public Methods


When a user joins the chat, we save their information as name, message, and dateTime,
when a user say something we also save their information as name, message, and dateTime,
and when they leave we just remove any info that corresponds to their name.


This is the xml file when a user named Ahmed joins the chat


join




This is the xml file when a user named Ahmed says hello


say




And finally this is the xml file when the user(s) leave


XML File





The three methods Join(..), void Say(..), void Leave(..):

public void Say(string userName, string msg, DateTime dateTime)
{
XElement user = new XElement("user");
XElement elementName = new XElement("name", userName);
XElement elementLastMsg = new XElement("message", msg);
XElement elementDate = new XElement("date", dateTime.ToString());
user.Add(elementName);
user.Add(elementLastMsg);
user.Add(elementDate);
_usersRoot.Add(user);
_usersRoot.Save(_FullPath);
}

public void Join(string userName, DateTime dateTime)
{
string systemMsg = userName + " joined chat room.";
this.Say(userName, systemMsg, dateTime);
}

public void Leave(string userName, DateTime dateTime)
{
var user = from o in _usersRoot.Elements("user")
where (string)o.Element("name").Value == userName
select o;

user.Remove();

_usersRoot.Save(_FullPath);
}




Other public methods are methods to get the names, number of all online chatters,
and the messages history.


public List< string> GetOnlineNames()
{
List< string> names = new List< string>();
var users = (from o in _usersRoot.Elements("user")
select o).Distinct();

foreach (var user in users)
{
if (!names.Contains(user.Element("name").Value))
{
names.Add(user.Element("name").Value);
}
}
_onlineUsers = names.Count;

return names;
}

public int GetNumberOfOnlines()
{
var users = (from o in _usersRoot.Elements("user")
select o).Distinct();
List< string> names = new List< string>();
foreach (var user in users)
{
//Filter the names to avoid duplicates
if (!names.Contains(user.Element("name").Value))
{
names.Add(user.Element("name").Value);
}
}
if (names.Count > 0)
{
_onlineUsers = names.Count;
return names.Count;
}
_onlineUsers = 0;
return 0;
}

public List< string> GetMessagesHistory()
{
List< string> messages = new List< string>();
var users = (from o in _usersRoot.Elements("user")
where o.Element("message").Value != string.Empty
orderby DateTime.Parse(o.Element("date").Value) ascending
select o).Distinct();
foreach (var user in users)
{
string fullString = user.Element("name").Value + " : " + user.Element("message").Value;
if (!messages.Contains(fullString))
{
messages.Add(fullString);
}
}
return messages;
}



Well, now we are done with our class, we have the ability to get the online names, the number of them,
and the messages history (this means if someone started the chat at x time and another one joined the chat
after 30 minutes from x, the last one will get the messages history since the chat has been started..).



Moving to ChatRoom.aspx



Design


In the Page_Load event, we are going to:

  • Register the page to the Anthem Manager to allow it to be called from client-side script.

  • Get an instance of the XHandle class.

  • Get the number of online chatters if there is.

  • Set a fixed time (may be 2 seconds) as the timer interval

  • Subscribe to the Tick event

  • Attach the refreshing controls methods to delegates, So we can invoke them asynchronously.




public partial class ChatRoom : System.Web.UI.Page
{
XHandle xmll;

List< string> names;
List< string> msgs;

private delegate void AsyncCallingNames();
private delegate void AsyncCallingMessages();

AsyncCallingNames callerNames;
AsyncCallingMessages callerMessages;

protected void Page_Load(object sender, EventArgs e)
{
Anthem.Manager.Register(this);

xmll = new XHandle(Server.MapPath("App_Data"), "FirstChatRoom");
//Get chat room name from user or chat admin

int x = xmll.GetNumberOfOnlines();
if (Session["userName"] != null)
{
LabelError.Text = "Online, Users Online: " + x.ToString();
}
else
{
LabelError.Text = "Offline, Users Online: " + x.ToString();
}

Timer1.Interval = 2;
//I set it to 1 second, and it wroked well

Timer1.Tick += new EventHandler(Timer1_Tick);

callerNames = new AsyncCallingNames(this.RefreshListNames);
callerMessages = new AsyncCallingMessages(this.RefreshMessages);
}

.....

}



Now we have 2 jobs:

  • Join, say and leave, This is done by:

    • Join button click event handler

    • Send button click event handler

    • onunload client-side script event handler



    • protected void ButtonJoin_Click(object sender, EventArgs e)
      {
      if (Session["userName"] == null)
      {
      Session["userName"] = TextBoxName.Text.ToString();
      xmll.Join(Session["userName"].ToString(), DateTime.Now);

      TextBoxName.Enabled = false;

      Timer1.StartTimer();

      TextBoxType2.Focus();
      }
      }
      protected void ButtonSend_Click(object sender, EventArgs e)
      {
      if (Session["userName"] != null)
      {
      string name = (string)Session["userName"];
      string msg = TextBoxType2.Text.ToString();
      xmll.Say(name, msg, DateTime.Now);
      TextBoxType2.Text = "";
      TextBoxType2.Focus();
      }
      else
      {
      LabelError.Text = "You have to join with a name first..";
      }
      }

      [Anthem.Method]
      public void Leave()
      {
      Timer1.StopTimer();

      if (Session["userName"] != null)
      {
      string name = (string)Session["userName"];
      xmll.Leave(name, DateTime.Now);

      LabelError.Text = "Offline";
      }
      }




  • Refresh our controls in timer tick event handler:

    • ListBox of chatters names

    • ListBox of messages



    • void Timer1_Tick(object sender, EventArgs e)
      {
      if (Session["userName"] != null)
      {
      IAsyncResult resultN = callerNames.BeginInvoke(null, null);
      if (!resultN.IsCompleted)
      {
      callerNames.EndInvoke(resultN);
      }

      IAsyncResult resultM = callerMessages.BeginInvoke(null, null);
      if (!resultM.IsCompleted)
      {
      callerMessages.EndInvoke(resultM);
      }
      TextBoxType2.Focus();
      }
      else
      {
      Timer1.StopTimer();
      TextBoxType2.Text = "You have to join with a name first..";
      }
      }


      private void RefreshListNames()
      {
      ListBox1.Items.Clear();
      names = xmll.GetOnlineNames();
      foreach (var name in names)
      {
      ListBox1.Items.Add(name);
      }
      }

      private void RefreshMessages()
      {
      ListBox2.Items.Clear();
      msgs = xmll.GetMessagesHistory();
      foreach (var msg in msgs)
      {
      ListBox2.Items.Add(msg);
      }
      }






Leave()


The last thing is how to call the Leave() method from Client-side script

< body ├Ânunload="Leave(); return false;">
< script type="text/javascript">
function Leave()
{
Anthem_InvokePageMethod('Leave', null, null);
}

< /script>


Reports


I'll really appreciate your try of this code and any problems you may get, to make this application better.

Monday, January 28, 2008

WCF and Callbacks - How to generate proxy using svcutil tool

Many of us tried to implement even a small simple of WCF service and a callback implementation, which (at least to me) I couldn't
get it to work. I've looked here,
there,
and some many others and I couldn't create a proxy which I use in my client.

In order to make the WCF service able to make a Callback to clients (if some client fired some events.., or even if the service decides to
because of an operation it monitors), you have to create a proxy (on service standards) so the client can be able to implement the ICallback interface.

How can I create this proxy?
There is two solutions for this,


  • First: Use a tool called SvcUtil.exe to generate a proxy.cs and app.config file from the service, so I can use both in my client.

  • Second: After making the service, start it, in the client add a service reference to thats you have just started.


Generate proxy.cs and app.config files
To use the svcutil.exe tool follow this:

  • Start > All Programs > Visual Studio 200x > Visual Studio Tools > Visual Studio 200x Command Prompt

  • type cd.. and switch to this path C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin

  • type sn.exe -Vr svcutil.exe Run svcutil tool

  • now we can use svcutil, switch to the path where your service is Where is svcutil.exe

  • if the service is .svc start it then type svcutil http://localhost:8080/CallbackAppNAME/YOURSERIVCENAME.svc?wsdl
    Use svcutil tool


  • if the service is .exe type svcutil YOURSERVICE.exe

  • there is gonna be file .wsdl and .xsd, type svcutil fileName.wsdl filename.xsd /language:C# /out:Proxy.cs /config:app.config



The generated files can be found here C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin (this where the svcutil tool is).
Note: There are more options you can use with svcutil tool like:
merging new configuration file with a one already exists, use this /mergeConfig.
generating a proxy with asynchronous methods, use this /a

Add a service reference
You can just add a service reference from your client:

  • With Visual Studio, start your ready .svc service.

  • In the client solution right click to add a service reference and just point to the service.

  • This will make a reference to call the service directly.

Thursday, January 24, 2008

Design Patterns - Observer Pattern

Introduction

This article explains the Observer patterns which is one of the C# Design Patterns, the article provides a very simple implementation so its complexity can be easily understood.

The observer pattern makes an object that if its state changed somehow, other instances will be notified (or updated) automatically, In other words, it is used to keep track of many objects.
observer
Implementation

Imagine if we have cards game, some tables, and some players..
On a table.., in the game context the suit of cards is changed time by time and we want to notify the players each time the
suit is changed.

Note* this is not a complete logic for a cards game, its an example.

The player object is going to have a function Update() which will be called by the notifier, and data:
  • Player Number (Player Identity) int

  • Current Suit string

  • Current Table Object Table



How can the player be notified? we pass the (always changes) object of the table as a parameter in the Update() function,
So the player (object) can see the new differences in the table (object).

This is the Player object..

interface IPlayer
{
void Update(Table _table);
}

class Player : IPlayer
{

public void Update(Table _table)
{
this._table = _table;
_currentSuit = _table.currentSuit;
Console.WriteLine("Player '" + _PlayerNo + "' notified that current suit is " + _currentSuit);
}

private int _PlayerNo;
private Table _table;
private string _currentSuit;

public Player(int _PlayerNo)
{
this._PlayerNo = _PlayerNo;
}

public Table CurrentTable
{
get { return _table; }
}

}



In our table object you want to add players or remove players (or count them for some reasons..)
So in the table object you'll have:
  • An array list of the players objects List<>

  • Current Suit string

  • Table Number int

  • Add Player Function AddPlayer()

  • Remove Player Function RemovePlayer()

  • Notify Function Notify()



The notify function is sending the updated object (table) to all players objects..


abstract class Table
{
private string _currentSuit;
private int _tableNo;
private List<> players = new List<>();

public Table(int _tableNo, string _currentSuit)
{
this._tableNo = _tableNo;
this._currentSuit = _currentSuit;
}
public void AddPlayer(Player _player)
{
players.Add(_player);
}

public void RemovePlayer(Player _player)
{
players.Remove(_player);
}

public string currentSuit
{
get { return _currentSuit; }
set
{
_currentSuit = value;
Notify();
}
}

public void Notify()
{
foreach (Player _player in players)
{
_player.Update(this);
}
}
}

class ReadyTable : Table
{
public ReadyTable(int _tableNo, string _currentSuit) : base(_tableNo, _currentSuit)
{ }
}



And the Main()

class Program
{
static void Main(string[] args)
{
//Create Table
ReadyTable table = new ReadyTable(1, "Spade");
Console.WriteLine("Starting table 1 with suit spades, suit will be changing in 3 seconds");

//Create Players
List players = new List(4);
for (int i = 0; i < 4; i++)
{
players.Add(new Player(i));
table.AddPlayer(players[i]);
}

Thread.Sleep(3000);
//Change the suit and all players will be notified
table.currentSuit = "Hearts";

Thread.Sleep(6000);
table.currentSuit = "Diamonds";

Console.ReadLine();
}
}