.NET Reflection is a very, very powerful tool. One thing I used it for is making a basic plugin system for my web server.

I needed some sort of way to know what assemblies to load, so I used a configuration file with a simple format to parse. For each module I wanted to load, I put a line that was the path to the DLL, preceded by the word “mod”

mod C:modulesmodule.dll

I figured that me or someone else might write a plugin that used another external library, so I used “dep” to mark DLL’s that were needed for the modules.

dep C:dependenciesdependency.dll

When the server starts up, it reads through this configuration file, takes all the lines that start with mod or dep and assigns puts their path in module and dependency ArrayList’s respectively. Once the config file is read, it goes through and loads the dependencies first.

 Assembly.LoadFile(DependencyPath);

Loading the modules is a little more complicated. For this I created a simple Module class to hold some information I would need about it, and be able to give me access to the Assembly object that loading the dll returns. In this case, when I load the module, I want to go into a class called ModuleMap that contains the url mapping info that I require for the plugin, and call its GetUrlMap method.

    public class Module
    {
        public string ModulePath;
		public string ModuleNamespace;
        public Assembly ModuleAssembly;
        public List<UrlMapItem> UrlMap;

        public Module(string modulepath)
        {
            ModulePath = modulepath;
        }

        public void Load()
        {
            if (!File.Exists(ModulePath))
            {
                throw new NoSuchModuleException("Error: No such module at '" + ModulePath + "'.");
            }
            ModuleAssembly = Assembly.LoadFile(ModulePath);
            int lastslash = ModulePath.LastIndexOf(@"");
            string assemblynamespace = ModulePath.Substring(lastslash+1, ModulePath.LastIndexOf('.') - lastslash-1);
			ModuleNamespace = assemblynamespace;
            Type t = ModuleAssembly.GetType(assemblynamespace+".ModuleMap");
            if (t != null)
            {
                MethodInfo m = t.GetMethod("GetUrlMap");
                if (m != null)
                {
                    UrlMap = (List<UrlMapItem>)m.Invoke(null, (new object[]{}));
                }
                else
                {
                    throw new InvalidModuleMapException("Error: The ModuleMap class is incorrect!");
                }
            }
            else
            {
                throw new InvalidModuleMapException("Error: The ModuleMap class is missing!");
            }
        }
    }

When I use the Invoke() method, I passed it two arguments. In this case, since it is a static method, the first argument is null. The second argument is an object array that contains the arguments for the invoked method. The Invoke() method returns an instance of an object class, so I casted it into the object type I needed, which in this case was a List.

Now, that UrlMapClass, that I now have a list of, contains two pieces of information:

  • A url
  • A method with the full namespace/class path (ex. “testnamespace.testclass.methodname”)

Then I took a Hashtable and used the namespace.class.method path as the key, and the Module.ModuleAssembly as the value object. This way when I go to call it, I can enter the path that I know is being called, take the Assembly object, and do what I did before and call MethodInfo.Invoke().

Assembly assembly = (Assembly)ModuleList[methodnamespace];
Type t = assembly.GetType(method.Substring(0, method.LastIndexOf('.')));
MethodInfo m = t.GetMethod(method.Substring(method.LastIndexOf('.') + 1));
Page p = (Page)m.Invoke(null, (new object[] { rq }));

The method variable contained the whole method.class.methodname path and the methodnamespace variable contained just the namespace. To get the class for GetType() I used a substring of the whole namespace.class.methodname string that was from 0 to the last . which would give you just namespace.class.

This is a pretty simple example. I structured my plugin DLL’s in such a way that it gave me all the info I needed to call the corresponding methods in a very simple manner. There are obviously much more complex ways to do this.

 

I’m writing this post for two reasons:

  1. It is a pretty cool thing to do and very useful
  2. The amount of useful documentation and links you can find is very small

It took me about 12 hours of futsing with this and Googling things to get this correct. Most of my information actually came from bug reports of various open source projects that use UPnP for peer-to-peer connections.

I haven’t put much error checking in here, I want to keep the code short and as easy to understand as possible. I’ll leave that one up to the users to figure out.

To use this code, you must first call NAT.Discover(). This makes sure you have a UPnP device available. After that you can go ahead and Add and Delete ports as you wish. Heres the code:

using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Xml;
using System.IO;

namespace enChatClient
{
    public class NAT
    {
        static TimeSpan _timeout = new TimeSpan(0, 0, 0, 3);
        public static TimeSpan TimeOut
        {
            get { return _timeout; }
            set { _timeout = value; }
        }
        static string _descUrl, _serviceUrl, _eventUrl;
        public static bool Discover()
        {
            System.Net.NetworkInformation.NetworkInterface nic = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces()[0];

            System.Net.NetworkInformation.GatewayIPAddressInformation gwInfo = nic.GetIPProperties().GatewayAddresses[0];
            Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
            string req = "M-SEARCH * HTTP/1.1rn" +
            "HOST: " + gwInfo.Address.ToString() + ":1900rn" +
            "ST:upnp:rootdevicern" +
            "MAN:"ssdp:discover"rn" +
            "MX:3rnrn";
            Socket client = new Socket(AddressFamily.InterNetwork,
                SocketType.Dgram, ProtocolType.Udp);
            IPEndPoint endPoint = new
            IPEndPoint(IPAddress.Parse(gwInfo.Address.ToString()), 1900);

            client.SetSocketOption(SocketOptionLevel.Socket,
                SocketOptionName.ReceiveTimeout, 5000);

            byte[] q = Encoding.ASCII.GetBytes(req);
            client.SendTo(q, q.Length, SocketFlags.None, endPoint);
            IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
            EndPoint senderEP = (EndPoint)sender;

            byte[] data = new byte[1024];
            int recv = client.ReceiveFrom(data, ref senderEP);
            string queryResponse = "";
            queryResponse = Encoding.ASCII.GetString(data);

            DateTime start = DateTime.Now;

            string resp = queryResponse;
            if (resp.Contains("upnp:rootdevice"))
            {
                resp = resp.Substring(resp.ToLower().IndexOf("location:") + 9);
                resp = resp.Substring(0, resp.IndexOf("r")).Trim();
                if (!string.IsNullOrEmpty(_serviceUrl = GetServiceUrl(resp)))
                {
                    _descUrl = resp;
                    return true;
                }
            }
            return false;
        }

        private static string GetServiceUrl(string resp)
        {
            XmlDocument desc = new XmlDocument();
            try
            {
                desc.Load(WebRequest.Create(resp).GetResponse().GetResponseStream());
            }
            catch (Exception)
            {
                return null;
            }   
            XmlNamespaceManager nsMgr = new XmlNamespaceManager(desc.NameTable);
            nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0");
            XmlNode typen = desc.SelectSingleNode("//tns:device/tns:deviceType/text()", nsMgr);
            if (!typen.Value.Contains("InternetGatewayDevice"))
                return null;
            XmlNode node = desc.SelectSingleNode("//tns:service[tns:serviceType="urn:schemas-upnp-org:service:WANIPConnection:1"]/tns:controlURL/text()", nsMgr);
            if (node == null)
                return null;
            XmlNode eventnode = desc.SelectSingleNode("//tns:service[tns:serviceType="urn:schemas-upnp-org:service:WANIPConnection:1"]/tns:eventSubURL/text()", nsMgr);
            _eventUrl = CombineUrls(resp, eventnode.Value);
            return CombineUrls(resp, node.Value);
        }

        private static string CombineUrls(string resp, string p)
        {
            int n = resp.IndexOf("://");
            n = resp.IndexOf('/', n + 3);
            return resp.Substring(0, n) + p;
        }

        public static void ForwardPort(int port, ProtocolType protocol, string description)
        {
            if (string.IsNullOrEmpty(_serviceUrl))
                throw new Exception("No UPnP service available or Discover() has not been called");

            IPHostEntry ipEntry = Dns.GetHostByName(Dns.GetHostName());
            IPAddress addr = ipEntry.AddressList[0];

            XmlDocument xdoc = SOAPRequest(_serviceUrl,
                "<m:AddPortMapping xmlns:m="urn:schemas-upnp-org:service:WANIPConnection:1"><NewRemoteHost xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="string"></NewRemoteHost><NewExternalPort xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="ui2">" +
                port.ToString() + "</NewExternalPort><NewProtocol xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="string">" +
                protocol.ToString().ToUpper() + "</NewProtocol><NewInternalPort xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="ui2">" +
                port.ToString() + "</NewInternalPort><NewInternalClient xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="string">" +
                addr + "</NewInternalClient><NewEnabled xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="boolean">1</NewEnabled><NewPortMappingDescription xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="string">" +
                description + "</NewPortMappingDescription><NewLeaseDuration xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="ui4">0</NewLeaseDuration></m:AddPortMapping>",
                "AddPortMapping");
        }

        public static void DeleteForwardingRule(int port, ProtocolType protocol)
        {
            if (string.IsNullOrEmpty(_serviceUrl))
                throw new Exception("No UPnP service available or Discover() has not been called");
            
            XmlDocument xdoc = SOAPRequest(_serviceUrl,
            "<u:DeletePortMapping xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">" +
            "<NewRemoteHost></NewRemoteHost>" +
            "<NewExternalPort>" + port.ToString() + "</NewExternalPort>" +
            "<NewProtocol>" + protocol.ToString().ToUpper() + "</NewProtocol>" +
            "</u:DeletePortMapping>", "DeletePortMapping");
        }

        public static IPAddress GetExternalIP()
        {
            if (string.IsNullOrEmpty(_serviceUrl))
                throw new Exception("No UPnP service available or Discover() has not been called");
            XmlDocument xdoc = SOAPRequest(_serviceUrl, "<u:GetExternalIPAddress xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">" +
            "</u:GetExternalIPAddress>", "GetExternalIPAddress");
            XmlNamespaceManager nsMgr = new XmlNamespaceManager(xdoc.NameTable);
            nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0");
            string IP = xdoc.SelectSingleNode("//NewExternalIPAddress/text()", nsMgr).Value;
            return IPAddress.Parse(IP);
        }

        private static XmlDocument SOAPRequest(string url, string soap, string function)
        {
            string req = "<?xml version="1.0"?>" +
            "<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">" +
            "<s:Body>" +
            soap +
            "</s:Body>" +
            "</s:Envelope>";
            WebRequest r = HttpWebRequest.Create(url);
            r.Timeout = 10000;
            r.Method = "POST";
            byte[] b = Encoding.UTF8.GetBytes(req);
            r.Headers.Add("SOAPACTION", ""urn:schemas-upnp-org:service:WANIPConnection:1#" + function + """);
            r.ContentType = "text/xml; charset="utf-8"";
            r.ContentLength = b.Length;
            r.GetRequestStream().Write(b, 0, b.Length);
            XmlDocument resp = new XmlDocument();
            WebResponse wres = r.GetResponse();
            Stream ress = wres.GetResponseStream();
            resp.Load(ress);
            return resp;
        }
    }
}

These SOAP requests use two UPnP “commands”:

  • AddPortMapping
  • DeletePortMapping

AddPortMapping needs the following parameters:

  1. NewRemoteHost – Used to make it so that only one remote host can connect. Generally not used and OK to leave blank.
  2. NewExternalPort – The port that the incoming outside connection should be connecting on.
  3. NewProtocol – This is the protocol that will be running on the port. should be either “TCP” or “UDP”.
  4. NewInternalPort – The port on the inside of the network that the connection will be forwarded to.
  5. NewInternalClient – The internal IP address the connection should be forwarded to.
  6. NewEnabled – Whether the connection should be enabled or not. You want to set this to 1 so it actually works.
  7. NewPortMappingDescription – Just a short description of what the port is actually being used for. Generally the program name.
  8. NewLeaseDuration – Just set this to 0.

If the AddPortMapping doesn’t work for some reason you will get a server 500 error. Otherwise you will get an xml response

DeletePortMapping needs the following parameters:

  1. NewRemoteHost – Same thing as AddPortMapping. Not really used.
  2. NewExternalPort – The external port that the forwarding you’re deleting is running on.
  3. NewProtocol – The protocol it is running. “TCP” or “UDP”

If there is no such port mapping in the table, you will get a server 500 error, so be careful.

You can also check to see if a mapping exists by using GetSpecificPortMapping entry which takes NewRemoteHost, NewExternalPort, and NewPortMappingProtocol, and it returns a bunch of data on the connection. However the downside is that if it doesn’t exist, you get a server 500 error, and these can be trick to handle if you’re using a WebRequest, as it just throws it as a WebException.

The other way to do it is to GetGenericPortMappingEntry which returns the table of them and you can search it for the one you want.

NOTE: AddPortMapping will overwrite the previous mapping on the same port and protocol.

 

Here is just a basic example for how to use threads.

using System;
using System.Threading; // <-- if you dont want to include the entire Threading namespace, just prefix your threading object with System.Threading.[object]
namespace ThreadingExample
{
class ThreadingExample
{
public void Main(String[] args)
{
Console.WriteLine("Starting...");
Thread myThread = new Thread(myThreadedMethod); // <-- remember, don't put the () after the method you want running on that thread!
myThread.Start();
while (true)
{
// do stuff here
}
}
public void myThreadedMethod()
{
while(true)
{
//do other stuff here too
}
}
}
}

Now, both of those while loops will be running forever simultaneously. Like i said, this is just a simple example. Threading can get extremely complicated. If you want to know about threading with a method that needs parameters, you should go look through the MSDN library on it. It has an -ok- example. and some explanation I believe.

 

Heres a little bit of code for making random strings. I’m sure someone will find it useful. I used to use it a lot in web development. Not sure how useful it would be for software development but use it if you’d like to :)


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Ruf
{
public static class StringGnerator
{
public static Char[] charArray = { ‘-’, ‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, ‘g’, ‘h’, ‘i’, ‘j’, ‘k’, ‘l’, ‘m’, ‘n’, ‘p’, ‘q’, ‘r’, ‘s’, ‘t’, ‘u’, ‘v’, ‘w’, ‘x’, ‘y’, ‘z’, ’1′, ’2′, ’3′, ’4′, ’5′, ’6′, ’7′, ’8′, ’9′, ’0′, ‘_’ };

public static string RandomStr()
{
String returns = “”;
Random r = new Random();
for (int i = 0; i < 11; i++)
{
returns += “” + charArray[(int)(r.NextDouble() + 1.0 * 38.0)];
}
return returns;
}

public static string RandomStr(int Length)
{
String returns = “”;
Random r = new Random();
for (int i = 0; i < Length; i++)
{
returns += “” + charArray[(int)(r.NextDouble() + 1.0 * 38.0)];
}
return returns;
}

public static string RandomStr(int Seed, int Length)
{
String returns = “”;
Random r = new Random(Seed);
for (int i = 0; i < Length; i++)
{
returns += “” + charArray[(int)(r.NextDouble() + 1.0 * 38.0)];
}
return returns;
}

}
}

 

I’ve been working on Fizzure A LOT recently. I made a FizzSrvLight that is not a distributed system like the regular one, which therefore allowed me to write one effectively in about 3 hours. On the way I decided to make a few of my own methods and then realized, hey these can be used in other projects too!

So I made a class library (.dll – Dynamically Linked Library ) with a few methods that have to do with TCP Data transmition. The most important of which is the Send method that I made. Now this is really only useful for the client. Anyway, heres the snippet:


public static void Send(TcpClient Client, String Command)
{
Console.WriteLine("Opening Server Stream");
NetworkStream n = Client.GetStream();
String send = Command;
String receive = null;
byte[] msg = System.Text.Encoding.ASCII.GetBytes(send);
n.Write(msg, 0, msg.Length);
Console.WriteLine("SENT: {0}", send);
}

this method is meant for console programs, but if you are using a GUI all you really need to do is delete the Console.WriteLines()’s in there and replace it with wherever you want the output.

Hope this is helpful to everyone!

 

Ok, this is just a quick snippet of code I wrote to get a working server up. Obviously theres more commands I could put in there in plenty of different ways, but I really just wanted to keep things simple for now. This took me about 2 hours.

This snippet is the main body of code that controls everything. If you go through it and read you’ll see that I made a struct to hold the information on files named File, in the namespace Structure. So you would access it by saying in this [MainNamespace].Structure.File; or you can just use Structure.File. I’ll paste the code for the struct at the end.

I didn’t leave too many comments because I used a lot of Writelines to tell me what it was doing, and for debugging purposes. Those kind of tell you what things do what.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace FizzSrvLight
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(“FizzSrvLight :: Non-Distributed Fizzure Serving Capabilities”);
System.Threading.Thread.Sleep(1000);
Console.Write(“Loading…”);
Console.WriteLine(“!”);

Console.WriteLine(“Initiating Server Variables…”);
System.Net.IPAddress localaddr = System.Net.IPAddress.Parse(“127.0.0.1″);

Console.WriteLine(“Constructing Server Objects…”);
System.Net.Sockets.TcpListener MainServer = new System.Net.Sockets.TcpListener(localaddr, 9000);

Console.WriteLine(“Starting Server…”);
MainServer.Start();

Byte[] bytes = new Byte[1024];
String data = null;
String send = null;

while (true)
{
Console.WriteLine(“Waiting for connection…”);

// Accept Requests
System.Net.Sockets.TcpClient client = MainServer.AcceptTcpClient();
Console.WriteLine(“Client Connected!”);

// Clear Buffers
data = null;
send = null;

// Get Stream Object for reading and writing
System.Net.Sockets.NetworkStream stream = client.GetStream();

int i;

// Initialize File Holder
System.Collections.ArrayList CurrentFiles = new System.Collections.ArrayList();

// Loop to recieve all data sent from client
while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
{
// Clear buffers again
data = null;
send = null;
string message = “OK”;
// Get data as string
data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
Console.WriteLine(“FIZZ_RCV: {0}”, data);

String[] command = data.Split(‘ ‘);

// Insert Possible Commands Here
if (command[0] == “FIZZ_ADDFILE”)
{
FizzSrvLight.Structure.File file = new FizzSrvLight.Structure.File(command[1], command[2], command[3], command[4], command[5], command[6]);
CurrentFiles.Add(file);
}
else if (command[0] == “FIZZ_RMVFILE”)
{
FizzSrvLight.Structure.File file = new FizzSrvLight.Structure.File(command[1], command[2], command[3], command[4], command[5], command[6]);
CurrentFiles.Remove(file);
}
else if (command[0] == “FIZZ_AUTH”)
{
string username = command[1];
string password = command[2];
}
else
{
Console.WriteLine(“FIZZ_INVALID_INPUT”);
Console.WriteLine(“Error Handled”);
message = “ERROR”;
}

send = message;

byte[] msg = System.Text.Encoding.ASCII.GetBytes(send);

// Send back an OK response;
stream.Write(msg, 0, msg.Length);
Console.WriteLine(“FIZZ_SND: ” + message);
}
System.Threading.Thread.Sleep(1000);
}
}
}
}

Now, time for the struct.

namespace FizzSrvLight
{
namespace Structure
{
public struct File
{
public string FileName;
public string FilePath;
public string FileType;
public string SharedBy;
public string IPAddress;
public string Blacklist;

public File(string name, string path, string type, string user, string ipaddr, string blacklisted)
{
FileName = name;
FilePath = path;
FileType = type;
SharedBy = user;
IPAddress = ipaddr;
Blacklist = blacklisted;
}

}
}
}

Well, there you have it. A very simple TcpListener Serve. Obviously theres better ways to do it but this is pretty simple, straight forward, and just all around easy. Please leave comments if you find bugs in it or see errors or even if you just don’t understand what some of it does.

© 2012 Code Brain Suffusion theme by Sayontan Sinha