Wednesday, July 23, 2008

Reflection, Dynamically In C#

On the current project I am working at my job, I need to call web services dynamically. What I am trying to do is add additional functionality to a web page that receives its data from several web services. The web services are called through a proxy web service that puts the data into an XML format that is then past on to the web page and parsed through XSLT transforms into HTML. What I want to do is to be able to add a web service at most anytime without having to touch the web page. All of the web page configuration is stored in a table, so getting a new web service added is simple enough in that all I need to do is update the proxy web service and add the configuration to the database.

To do all of this, I need to be able to call the methods dynamically using Reflection.

Here's how:
In my application, I use a criteria (or request) object to pass in parameters to the method and a response object on the return. So, here are those objects:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
namespace Namespace
{
    public class Request
    {
        public Request() { }

        private string name;

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        private string message;

        public string Message
        {
            get { return message; }
            set { message = value; }
        }
    }
}


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
namespace Namespace
{
    public class Response
    {
        public Response() { }

        private string name;

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        private string message;

        public string Message
        {
            get { return message; }
            set { message = value; }
        }
    }
}

Next up, we have our class which has the method we are going to call.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
namespace Namespace
{
    public class DisplayName
    {
        public DisplayName() { }

        public Response Write(Request req)
        {
            // Run once just to show we are actually doing something.
            Console.WriteLine(string.Format("{0}: {1}", req.Name, req.Message));
            Response resp = new Response();
            resp.Name = req.Name;
            resp.Message = req.Message;
            return resp;
        }
    }
}

Finally, our method to process our dynamic call.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
using System;
using System.Reflection;
 
class ReflectionSample
{
    static void Main()
    {
        // Get our assembly loaded.
        Assembly asm = Assembly.Load("Namespace");
        
        // Define our main type. This is the type that 
        // we will use to get our actual object.
        Type mainType = asm.GetType("Namespace.DisplayName");
        
        // Create our object based on the type we defined
        // in the last step.
        object obj = Activator.CreateInstance(mainType);
 
        // Now, we define our request type. The request object
        // is in the same assembly.
        System.Type reqTyp = asm.GetType("Namespace.Request");
        
        // Next, create our request object. 
        object reqObj = Activator.CreateInstance(reqTyp);
        
        // After we create our object, we set the properties.
        // In my actual application, these properties are set
        // via a loop, assuming multiple dynamic properties.
        PropertyInfo piName = reqTyp.GetProperty("Name");
        piName.SetValue(reqObj, "John", null);
        PropertyInfo piMess = reqTyp.GetProperty("Message");
        piMess.SetValue(reqObj, "Hello", null);
 
        // The next step is to create the array of parameters
        // we need to pass into the method we will be calling.
        // In the case of my application, we only need one.
        System.Type[] prmTyp = new System.Type[1];
        prmTyp.SetValue(reqTyp, 0);
        
        // The next step is to get our MethodInfo object.
        // We pass in to GetMethod the name of the method
        // and the parameter type list.
        MethodInfo mi = mainType.GetMethod("Write", prmTyp);
 
        // Next, we create our actual parameter array.
        object[] prm = new object[1];
        
        // In this case we only have one parameter, so 
        // we set it as our request object.
        prm.SetValue(reqObj, 0);
 
        // Here is where we get tricky. Since we are trying to be
        // dynamic with this process, we don't want to assume a 
        // definitive type for our return type. Also, in my application
        // I use response objects that contain multiple properties,
        // this makes passing multiple properties back easier.
        System.Type respType = asm.GetType(mi.ReturnType.ToString());
        
        // Now, we create an object of our return type.
        object respObj = Activator.CreateInstance(respType);
        
        // Finally, we Invoke our method passing in the parameter array.
        respObj = mi.Invoke(obj, prm);
 
        // This is where we can parse through the properties in the 
        // response object. This is similar to the setting of the 
        // request properties, but there are additional Reflection 
        // methods to parse these dynamically. Here, I'll just use
        // predefned properties.
        PropertyInfo piRespName = respType.GetProperty("Name");
        string name = piRespName.GetValue(respObj, null).ToString();
        PropertyInfo piRespMessage = respType.GetProperty("Message");
        string message = piRespMessage.GetValue(respObj, null).ToString();
 
        // Prove we actualy did something.
        Console.WriteLine(string.Format("{0}: {1}", name, message));
    }

I hope this little sample helps someone else in their struggle to dynamically call code in .NET. Translation to VB shouldn't be too difficult. And as far as I know, this should work in all versions of .NET from 1.0 to 3.5.

No comments: