About
MatriX
MatriX is a library for the eXtensible Messaging and Presence Protocol (XMPP) for the Microsoft .NET platform. MatriX can be used to build high quality and high performance XMPP Software products. Like XMPP MatriX is designed for easy extendibility.
MatriX is the successor of our successful agsXMPP library. It was rewritten from scratch to include all new features of the latest .NET platforms. Requirements for MatriX are .NET 3.5 and newer.
If you have not downloaded MatriX yet then download it from here.
This document
- This document is tutorial for XMPP development.
- You should already be familiar with the XMPP basic before you start working with MatriX and this document.
- If you are not familiar with the XMPP basics then we suggest to
read a book about XMPP first
- XMPP: The Definitive Guide is a very good book so start with.
Logging
When you have a specific problem then in many cases we request a Xml log from you. This Xml log is all incoming and outgoing Xml of the XMPP session. You get this information from the OnReceiveXml and OnSendXml handlers in all XmppStream classes.
xmppClient.OnReceiveXml += new EventHandler<TextEventArgs>(XmppClientOnReceiveXml);
xmppClient.OnSendXml += new EventHandler<TextEventArgs>(XmppClientOnSendXml);
void XmppClientOnSendXml(object sender, TextEventArgs e)
{
AddDebug(e.Text);
}
void XmppClientOnReceiveXml(object sender, TextEventArgs e)
{
AddDebug(e.Text);
}
void AddDebug(string debug)
{
// write the debug data to a file, textbox etc...
}
Client connection
Client connections are handled with the XmppClient class in MatriX.
Open
Connecting to a XMPP server is very simple. All you have to do is set the Username, Password and XMPP server. After that call the Open method to establish the connection.
The Open method is asnychronous. This means when the Open call returns you are not connected, but MatriX in executing the process to connect. You have to use the MatriX events. Use the error events to find out if connection failed.
If you want to execute an action right after the Login we suggest to start the action from the OnRosterEnd, OnPresence, OnLogin or OnBind event.
private void Connect()
{
xmppClient.SetUsername("username");
xmppClient.SetXmppDomain("server.com");
xmppClient.Password = "secret";
xmppClient.Status = "I'm chatty";
xmppClient.Show = Matrix.Xmpp.Show.chat;
xmppClient.Open();
}
Transport
XMPP sessions are persistent TCP socket connections by default on port 5222. The socket gets connected on the start of the sessions and disconnected when the session (XMPP stream) ends.
SRV-Records
XMPP is using SRV records to find the hostname and port of the XMPP service. MatriX for .NET automatically looks up the SRV records and is using the data to establish the connection.
You can also manual specify the hostname and port and disable the SRV lookups.
private void Connect()
{
xmppClient.SetUsername("username");
xmppClient.SetXmppDomain("gmail.com");
xmppClient.Password = "secret";
// disable SRV lookups and specify hostname manual
xmppClient.ResolveSrvRecords = false;
xmppClient.Hostname = "talk.google.com";
xmppClient.Status = "I'm chatty";
xmppClient.Show = Matrix.Xmpp.Show.chat;
xmppClient.Open();
}
MartriX Mobile and MatriX for Silverlight don't support SRV lookups because this API's are missing in the underlying .NET Framework editions.
BOSH
Bidirectional-streams Over Synchronous HTTP (BOSH) is an extensions to transport XMPP streams over the HTTP protocol. BOSH was developed for constrained clients like web browsers which can't open sockets any only communicate over the XMPP protocol.
Even Silverlight is able to create sockets it makes sense in many cases to choose the BOSH Transport for Silverlight applications.
- Easier server setup
- Non standard ports are often blocked in firewalls while HTTP requests are not blocked
- You can secure the connection when your BOSH service supports HTTPS. Silverlight has no SslStream, therefore sockets cannot be secured using the TLS protocol.
Many enterprise companies use HTTP proxies as the only connection to the Internet. In this scenario the only way to establish a XMPP connection will be BOSH as well.
The following example show how you choose BOSH as the transport layer and setup a XMPP client stream.
private void Connect()
{
xmppClient.SetUsername("username");
xmppClient.SetXmppDomain("ag-software.de");
xmppClient.Password = "secret";
xmppClient.Transport = Matrix.Net.Transport.BOSH;
xmppClient.Uri = new System.Uri("http://ag-software.de:5280/http-bind/");
xmppClient.Status = "I'm chatty";
xmppClient.Show = Matrix.Xmpp.Show.chat;
xmppClient.Open();
}
Authentication
A XMPP session can be authenticated with the following mechanisms in MatriX.
- user and password
- Single sign on using kerberos
- Certificates
Single Sign On
Using Kerberos you can authenticate with your Windows logon credentials in MatriX. If you have a working XMPP server which supports Kerberos authentication you don't have to provide username and password. Just set UseSso to true and login. You can get your full Jid which includes the username in the OnBind event when required.
private void Connect()
{
xmppClient.OnBind += new System.EventHandler<JidEventArgs>(xmppClient_OnBind);
xmppClient.Status = "I'm chatty";
xmppClient.Show = Matrix.Xmpp.Show.chat;
xmppClient.UseSso = true;
xmppClient.Open();
}
private void xmppClient_OnBind(object sender, JidEventArgs e)
{
// get our own Jid in the bind event.
Debug.WriteLine(e.Jid);
}
Certificates
When you sign on using certificates you have to provide a X509Certificate2 object to the XmppClient object. When you provide a certificate no username ad password is required. When you set a username then this username gets used at the authz id during the SASL EXTERNAL authentication.
When no username is provided and the authentication was successful you can get your full Jid which includes the username from the OnBind event.
The following example uses a certificate file to login. No username is provided.
private void Connect()
{
xmppClient.OnBind += new System.EventHandler<JidEventArgs>(xmppClient_OnBind);
xmppClient.Status = "I'm chatty";
xmppClient.Show = Matrix.Xmpp.Show.chat;
xmppClient.ClientCertificate = new X509Certificate2(@"C:\certs\xmpp\alex.ag-software.de.p12", "secret");
xmppClient.Open();
}
private void xmppClient_OnBind(object sender, JidEventArgs e)
{
// get our own Jid in the bind event.
Debug.WriteLine(e.Jid);
}
Roster
In XMPP the contact list aka buddy listâ„¢ is called roster. When AutoRoster is true (default) MatriX requests the roster automatically on each login. We call this the full roster. If you don't want to receive the roster on each login set AutoRoster to false. The most existing clients request the roster on each login. Here is a small list where setting AutoRoster to false makes sense.
- save bandwidth (mobile devices)
- faster login time
- you cache the roster in a database and synchronize it manual
Events
The following events get raised while the contact list is received
| OnRosterStart | occurs before the first OnRosterItem gets fired. This event is often used to stop GUI updates (BeginUpdate) of the roster control while receiving the complete contact list for faster GUI updates. |
| OnRosterItem | gets raised for each contact in the contact list when the full roster gets received. This event gets also fired when a single contact is added, removed or updated. |
| OnRosterEnd | occurs when all contacts are received. This is also the place where you call EndUpdate when you used BeginUpdate before. When AutoRoster is true this event also indicates that the XMPP session is ready to execute actions in your business logic. |
You have to consider all contacts as offline until you get a presence from them with another show type.
OnRosterStart and OnRosterEvent get only fired when the full roster is received, not for single contact updates.
Management
You can manage your contacts (roster) with the RosterManager class.
Add contacts
Add the user with the Jid joe@server.com under the name Joe Jones to the roster.
var rm = new RosterManager(xmppClient); Jid jid = "joe@server.com"; rm.Add(jid, "Joe Jones");
Add the user with the Jid joe@server.com under the name Joe Jones to the roster and put it in the group Work.
var rm = new RosterManager(xmppClient); Jid jid = "joe@server.com"; rm.Add(jid, "Joe Jones", "Work");
Add the user with the Jid joe@server.com under the name Joe Jones to the roster and put it in the 2 groups Work and Friends.
var rm = new RosterManager(xmppClient);
Jid jid = "joe@server.com";
rm.Add(jid, "Joe Jones", string[] {"Work", "Friends"});
Add and subscribe
When we add a contact then in the most cases we also want to exchange presence with the contact. To exchange presence we must subscribe to the contacts presence. The following example describes how to add a contact and subscribe to the contacts presence at the same time.
var rm = new RosterManager(xmppClient); var pm = new PresenceManager(xmppClient); Jid jid = "joe@server.com"; rm.Add(jid, "Jones", "Friends"); pm.Subscribe(jid);
Update contacts
We have the user joe@server.com with no name on no groups in the contact list. Now we want to add the name "Joe" to the contact and put him in the group "Friends".
var rm = new RosterManager(xmppClient); Jid jid = "joe@server.com"; rm.Update(jid, "Joe", "Friends");
When we update a contact we always have the pass the complete contact information, not only a diff of the properties which we want to add, change or upate.
Remove contacts
Remove the contact with the Jid joe@server.com.
var rm = new RosterManager(xmppClient); Jid jid = "joe@server.com"; rm.Remove(jid);
Filters
Filters can be used to get notifications (events) for stanzas you are interested at. Using filters you code is much easier to read, because you get rid of lots of nested if-then-else statements.
Currently there are 3 filter classes:
- IqFilter
- MessageFilter
- PresenceFilter
- XPathFilter
IqFilter
In XMPP we have a request response mechanism with the Iq stanzas. Its similar to HTTP GET and HTTP PUT.
<!-- Example 1 --> <!-- Client request --> <iq from='juliet@example.com/balcony' id='rg1' type='get'> <query xmlns='jabber:iq:roster'/> </iq> <!-- Server response --> <iq id='rg1' to='juliet@example.com/chamber' type='result'> <query xmlns='jabber:iq:roster' ver='ver7'> <item jid='nurse@example.com'/> <item jid='romeo@example.net'/> </query> </iq>
The example above is a roster query. The client requests the contact list from the server. The type of the request is get because the client wants to retrieve information. The server responds with a type of result and sets the id to the same id as in the request. In this case 'rg1'. You can use your own logic with many if-then-else clauses to assign the response to the associated request or the IqFilter class of MatriX.
When you send a request with the IqFilter you can define a callback which handles the response. The IqFilter handles all processing and raises your callback automatically when the result is received. The logic of the IqFilter is based on the unique id IQ's.
Asynchronous Iq Filter
This example describes the asynchronous usage of the IqFilter. A RosterIq query gets created with the RosterIq class. The RosterIq class automatically assigns a unique id to the request. You pass the query stanza and your defined callback to the SendIq member of the IqFilter. Once Matrix receives the packet the callback gets raised and removed from the IqFilter collection.
// Example 2
private void RequestRoster()
{
var riq = new RosterIq(IqType.get);
xmppClient.IqFilter.SendIq(riq, RosterResponse);
}
private void RosterResponse(object sender, IqEventArgs e)
{
var iq = e.Iq;
if (iq.Type == IqType.result)
{
// process result here
}
else if (iq.Type == IqType.error)
{
// process errors here
}
}
Synchronous Iq Filter
We normally suggest to use asynchronous patterns. But often developers
prefer synchronous programming and in some environments synchronous programming
is required. You can also do synchronous requests in the IqFilter with the
SendIqSynchronous member, when required with a given timeout.
The default timeout is 5000ms. When no response was received from the server
within the given timeout then the return value is null, otherwise the resulting
Iq stanza.
the following example requests a user VCard using an synchronous IqFilter with a timeout of 2 seconds.
Because the synchronous calls are using AutoResetEvents they cannot be executed from the MatriX thread. Doing this would block until you reach the timeout and return always null.
// Example 3
private void RequestVcardSynchronous()
{
var viq = new VcardIq {To = "user@server.com", Type = IqType.get};
Iq result = xmppClient.IqFilter.SendIqSynchronous(viq, 2000);
if (result != null)
{
// process result here
}
}
XPathFilter
The XPath filter can be used to filter stanzas using XPath expressions. The XPathFilter is based on the XPathSelectElement extension of System.Xml.XPath Namespace.
Of course you can do filtering based on powerful LinQ statements and the other filter classes in MatriX. But in many cases it's required to build expressions dynamically on the fly. Also many programmers are familiar with XPath and prefer XPath over LinQ statements. The XPathFilter makes it also much easier to filter big complex stanzas with many nested elements.
The following example describes the usage of the XPathFilter. We setup a filter that matches all presence stanzas from the full Jid 'user@jabber.org/MatriX'. Because MatriX and XPath in .Net are namespace aware we have to define prefixes in the XmlNamespaceManager. Otherwise we would get no results.
e.Stanza is the complete stanza which matches the expression
e.Result is the result of the XPath expression. This is
useful when you are interested only in fragments of the complete stanza.
void XPathFilter()
{
xmppClient.XPathFilter.XmlNamespaceManager.AddNamespace("JC", "jabber:client");
xmppClient.XPathFilter.Add("/JC:presence[@from='user@jabber.org/MatriX']", XPathCallback);
}
void XPathCallback(object sender, XPathEventArgs e)
{
Debug.WriteLine("Stanza: " + e.Stanza);
Debug.WriteLine("Result: " + e.Result);
}
Here are some other XPath example expressions to filter stanzas:
- filter all messages with the exact body of 'Hello MatriX
/JC:message[JC:body='Hello MatriX'] - filter all presences where the Jid starts with 'gnauck@'.
/JC:presence[starts-with(@from ,'gnauck@')] - filter all messages of type=error which have a from and and a to
Jid
/JC:message[@from and @to and @type='error']
Registration
Register new account
ItIt is possible to register new accounts over XMPP and with MatriX. But for most services its not recommended to enable automatic account creation over XMPP because it invites spammers which can use the accounts as "throw away" addresses. Its also easier in the most cases to write new accounts directly to the SQL database of the server using scripting language for HTML like ASP, PHP and others.
Below is a code snippet which shows how to register a new account. In the OnRegisterInformation event required the Register object must be populated with the data. Most severs offer simple fields like username, password and email, or extended registration over XData. In our example we use the simple fields for username and password and remove XData from.
var XmppClient = new XmppClient();
xmppClient.OnRegister += new EventHandler<Matrix.EventArgs>(xmppClient_OnRegister);
xmppClient.OnRegisterInformation += new EventHandler<Matrix.Xmpp.Client.RegisterEventArgs>(xmppClient_OnRegisterInformation);
xmppClient.OnRegisterError += new EventHandler<Matrix.Xmpp.Client.IqEventArgs>(xmppClient_OnRegisterError);
xmppClient.SetUsername(txtUsername.Text);
xmppClient.SetXmppDomain(txtServer.Text);
xmppClient.Password = txtPassword.Text;
xmppClient.RegisterNewAccount = true;
xmppClient.Open();
private void xmppClient_OnRegisterInformation(object sender, RegisterEventArgs e)
{
e.Register.RemoveAll<Data>();
e.Register.Username = xmppClient.Username;
e.Register.Password = xmppClient.Password;
}
private void xmppClient_OnRegister(object sender, EventArgs e)
{
// registration was successful
}
private void xmppClient_OnRegisterError(object sender, IqEventArgs e)
{
// registration failed.
xmppClient.Close();
}
Extending MatriX
XMPP was designed for extensibility from ground up. It's very easy to define your own subsets of XMPP protocol and send them over the wire. Its very easy to add you own custom extensions to MatriX.
In the following example we will create a simple extension for a weather service. We will request weather information like temperature and humidity from a weather service over XMPP. Because this is a request<--> response mechanism we will use the XMPP IQ stanza for this.
Protocol design
in the first step we design the Xml representation of the protocol we are going to use.
<!-- weather request --> <iq from='user1@server.com/MatriX' to='user2@server.com/MatriX' type='get' id='weather1'> <weather xmlns='ag-software:weather'> <zip>74080</zip> </weather> </iq> <!-- weather response --> <iq from='user2@server.com/MatriX' to='user1@server.com/MatriX' type='result' id='weather1'> <weather xmlns='ag-software:weather'> <humidity>60</humidity > <temperature>24</temperature> </weather> </iq>
In XMPP protocols are defined by their namespaces. I have chosen the namespace agsoftware:weather for this extensions. And I have chosen the tag name <weather/> for the root element. This makes sense because Xml is verbose and human readable. As first level children of the weather root element I defined
- <humidity/> the humidity as Integer returned in the response.
- <temperature/> the temperature as Integer returned in the response
- <zip/> zip code of the city we request the weather information from. Here in Germany we have numeric zip codes with 5 digits. Because this example should work worldwide and the other 2 tags are already Integer values we choose a String here.
Class design
After the design of the protocol we can start to define our classes now. For this basic example we will create 2 classes Weather.cs and WeatherIq.cs.
using Matrix.Xml;
namespace WeatherExample
{
public class Weather : XmppXElement
{
public Weather() : base("ag-software:weather", "weather")
{
}
public int Humidity
{
get { return GetTagInt("humidity"); }
set { SetTag("humidity", value); }
}
public int Temperature
{
get { return GetTagInt("temperature");}
set { SetTag("temperature", value);}
}
public string Zip
{
get { return GetTag("zip"); }
set { SetTag("zip", value); }
}
}
}
The weather.cs class which represents our custom Xml object must derive from XmppXElement. XmppXElement is inherited from System.Xml.Linq.XElement and is the base of all XMPP protocol classes in MatriX.
XmppXElement has many helper functions to serialize and deserialize Xml. We use GetTag, and GetTagInt here which return the value of the given Xml Tag as String or Integer, and we use SetTag to add a tag and set its value as String or Integer. There are many other helper functions which makes it very simple to build complex Xml structures. And of course you can use all members of the powerful System.Xml.LinQ namespace.
After we have written this class we can run a simple test to see if we get the desired results.
private void TestWeather()
{
var weather = new Weather {Zip = "74080", Humidity = 60, Temperature = 22};
Debug.WriteLine(weather);
}
The Debug Console shows the following result. This is what we expected.
<weather xmlns="ag-software:weather"> <zip>74080</zip> <humidity>60</humidity> <temerature>22</temerature> </weather>
Now we create the WeatherIq class. This class is only a helper to create WeatherIq's in a easier fashion. Instead of WeatherIq.cs we could also use a normal Iq object and add the weather childnode with the Add member.
using Matrix.Xmpp.Client;
namespace WeatherExample
{
public class WeatherIq : Iq
{
public WeatherIq()
{
GenerateId();
}
public Weather Weather
{
get { return Element<Weather>(); }
set { Replace(value); }
}
}
}
We write again a simple test to build a weather Iq request with the new WeatherIq class.
private void TestWeatherIq()
{
var wiq = new WeatherIq
{
Type = IqType.get,
To = "user2@server.com/MatriX",
Weather = new Weather {Zip = "74080"}
}
Debug.WriteLine(wiq);
}
And again we get the result we expected. Don't care about the additional jabber:client namespace declaration. Its there because this is a Xml fragment of the complete Xml stream. When we inject the packet into the Xmpp Xml stream its automatically removed because the jabber:client namespace is already declared in the stream header. MatriX is namespace aware which is the reason why we see this namespace declarations on partial Xml fragments.
<iq id="MX_1" type="get" to="user2@server.com/MatriX" xmlns="jabber:client"> <weather xmlns="ag-software:weather"> <zip>74080</zip> </weather> </iq>
Now we can put the pieces together.
- first we have to register our custom elements in the factory. The function RegisterCustomElements is doing this and should be called once at the very beginning of your app.
- RequesWeatherInfo makes a weather request to the specified Jid for a given zip code. The jid must be a full jid. The response of the request is received in the WeatherInfoResponse callback.
- To handle incoming weather requests and respond to them we subscribe to the OnIq handler of the XmppClient class, check if the incoming Iq is a weather request, read the zip code, lookup the weather data build the response and send it back to the requesting XMPP entity.
private static void RegisterCustomElements()
{
Factory.RegisterElement<Weather>("ag-software:weather", "weather");
}
private void RequestWeatherInfo(Jid from, string zip)
{
var wiq = new WeatherIq
{
Type = IqType.get,
To = from,
Weather = new Weather { Zip = zip }
};
// we pass the zip code as state object to the IqFilter
xmppClient.IqFilter.SendIq(wiq, WeatherInfoResponse, zip);
}
private void WeatherInfoResponse(object sender, IqEventArgs e)
{
var iq = e.Iq;
if (iq.Type == IqType.result)
{
var weather = iq.Element<Weather>();
if (weather != null)
{
// read the zip code from the stat again because its not contained in the xml object
var zip = e.State as string;
Debug.WriteLine("Humidity: " + weather.Humidity);
Debug.WriteLine("Temperature: " + weather.Temperature);
}
}
}
private void xmppClient_OnIq(object sender, IqEventArgs e)
{
if (e.Iq.Type == IqType.get &&
e.Iq.Query is Weather)
{
var weather = e.Iq.Query as Weather;
string zip = weather.Zip;
// here you should lookup the weather information for the given zip code in a database or webservice
// we just return some random numbers
var temp = new Random().Next(-10, 40);
var humidity = new Random().Next(10, 90);
var wiq = new WeatherIq
{
To = e.Iq.From,
Type = IqType.result,
Weather = new Weather {Temperature = temp, Humidity = humidity}
};
// send the response
xmppClient.Send(wiq);
}
}
You should be able copy & paste the code snippets above to a new project an test the code with 2 different XMPP users. You can also login with the same user twice using different resources and send the weather packets from one resource to the other.
Don't forget to register your own protocol classes in the
MatriX Factory (Matrix.Xml.Factory).
When your classes are not registered in the Factory MatriX is unable to
deserialize the Xml to your custom class.
Silverlight
There are different ways to setup a XMPP connection with MatriX in Silverlight
- direct socket connection on a port within the Silverlight port range
- direct connection over a HTTP proxy with the CONNECT command (HTTP tunneling)
- BOSH connection
Silverlight has different network security access restrictions for sockets and web requests. If you choose option 1 or 2 then the socket policies apply, if you choose 3 then the web request policy apply because MatriX is using .NET WebRequest classes for the connections. Read more details about the Silverlight network security access restrictions here.
Silverlight allows socket connections only on ports 4502-4534. For option 1 and 2 this means you have to configure your XMPP or proxy server to listen on a port within this port range, or use other technologies like port forwarding to redirect connections to the XMPP default port 5222. Before a socket can be connected the Silverlight runtime requests the policy on the target site at port 943 and checks the permissions. You have to run a policy server which must be able to serve requests on the same domain as your XMPP or proxy server is hosted.
When using BOSH and crossing domains the Silverlight runtime requests a policy xml file from the root of the BOSH Uri. This means you can connect to other XMPP servers (not under your control) only if they host a policy file on the root of the BOSH Uri which allows you to connect. Or you run your own BOSH server which is able to connect to any XMPP server in the federated network. Punjab is a good choice for the latter.
Example:
when the BOSH Uri is http://example.com:5280/http-bind/ the Silverlight
runtime request either a Flash policy file at http://example.com:5280/crossdomain.xml
or a Silverlight policy file at http://example.com:5280/clientaccesspolicy.xml
when crossing domains.
To avoid cross domain requests on your own server you can either
- choose a XMPP server which allows you to host your application on the BOSH Uri
- proxy from your web server to the BOSH server e.g. with mod_proxy in Apache.
