Tuesday 27 August 2013

Consuming WCF Service via Reflection

In this article, I will like to show a sample in which we can consume a WCF service on the go by using reflection instead of adding references to our project or application.

In this sample I will cover how can we work with simple data types as well as complex data types while accessing the WCF Service via reflection?

So demonstrate using sample, first of all create a simple WCF application. In it I have created two Methods:

  1. GetTestString – It expects a parameter of string type and return a string
  2. GetTestDataUsingDataContract – It expects a complex data type and returns a complex data type.

Actually it is very simple, when you will add a default WCF application, it will automatically give you two test methods with a default service contract and default bindings. So I haven’t done many changes in it as the purpose of this article to let one consume WCF Service via reflection.

IService1.cs

    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        string GetTestString(string value);

        [OperationContract]
        CompositeType GetTestDataUsingDataContract(CompositeType composite);
    }


Default Composite Data Type you will automatically get is:

    [DataContract]
    public class CompositeType
    {
        bool boolValue = true;
        string stringValue = "Hello ";

        [DataMember]
        public bool BoolValue
        {
            get { return boolValue; }
            set { boolValue = value; }
        }

        [DataMember]
        public string StringValue
        {
            get { return stringValue; }
            set { stringValue = value; }
        }
    }

Service class Service1.cs looks something like this:

    public class Service1 : IService1
    {
        public string GetTestString(string value)
        {
            return string.Format("Welcome {0}", value);
        }

        public CompositeType GetTestDataUsingDataContract(CompositeType composite)
        {
            if (composite.BoolValue)
            {
                composite.StringValue += " From service";
            }
            return composite;
        }
    }

Now run this service either by code or host it on IIS and get the service path.

Note: One can use different bindings while hosting the service as per their requirement, defining bindings is out of the scope of this article.

Now create a Demo Website to check if the service can be loaded with reflection.
For this I have created a Default page in my Website:

Default.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <table>
                <tr>
                    <td>
                        <asp:Label runat="server" Text="User"></asp:Label>
                    </td>
                    <td>
                        <asp:TextBox ID="txtUser" runat="server"></asp:TextBox>
                    </td>
                </tr>
                <tr>
                    <td>
                        <asp:Button ID="btnGetUserFromService" runat="server" Text="Get User Message" OnClick="btnGetUserFromService_Click"></asp:Button>
                    </td>
                    <td>
                        <asp:Label ID="lblUserMessage" runat="server"></asp:Label>
                    </td>
                </tr>

            </table>
        </div>
        <div>
            <table>
                <tr>
                    <td>
                        <asp:Label ID="lblCompostiteDataTypeStringValue" runat="server" Text="Enter String Value which will go to service in a composite data type"></asp:Label>
                    </td>
                    <td>
                        <asp:TextBox ID="txtCompostiteDataTypeStringValue" runat="server"></asp:TextBox>
                    </td>
                </tr>
                <tr>
                    <td>
                        <asp:Button ID="btnCompostiteDataTypeStringValue" runat="server" Text="Get Message From Service" OnClick="btnCompostiteDataTypeStringValue_Click"></asp:Button>
                    </td>
                    <td>
                        <asp:Label ID="lblCompostiteDataTypeStringValueFromService" runat="server"></asp:Label>
                    </td>
                </tr>

            </table>
        </div>
    </form>
</body>
</html>


The code behind of this page which is actually accessing the service via reflection is shown below, one can find inline comments in my code to understand what has been implemented and achieved via code.

Default.aspx.cs

using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.ServiceModel;
using System.ServiceModel.Description;

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {

    }

    protected void btnGetUserFromService_Click(object sender, EventArgs e)
    {
        CompilerResults compilerResults = null;

        //Create the proxy instance and returns it back, One parameter to this method is compiler compilerResults(Reference Type) this has been done so that the assemblies are
        //compiled only once, one can change the implementation adhering to coding guidelines and as per the implementation and requirement
        object proxyInstance = GetProxyInstance(ref compilerResults);
        string operationName = "GetTestString";

        // Get the operation's method
        var methodInfo = proxyInstance.GetType().GetMethod(operationName);

        //Paramaters if any required by the method are added into optional paramaters
        object[] operationParameters = new object[] { txtUser.Text };

        //Invoke Method, and get the return value
        lblUserMessage.Text = methodInfo.Invoke(proxyInstance, BindingFlags.InvokeMethod, null, operationParameters, null).ToString();

    }

    protected void btnCompostiteDataTypeStringValue_Click(object sender, EventArgs e)
    {
        CompilerResults compilerResults = null;

        //Create the proxy instance and returns it back, One parameter to this method is compiler compilerResults(Reference Type) this has been done so that the assemblies are
        //compiled only once, one can change the implementation adhering to coding guidelines and as per the implementation and requirement
        object proxyInstance = GetProxyInstance(ref compilerResults);

        string operationName = "GetTestDataUsingDataContract";

        // Get the operation's method
        var methodInfo = proxyInstance.GetType().GetMethod(operationName);

        //Here we are getting the method parameters, it has been done because the method parameter is a complex type, so we get the parameter, then its type and then finally
        //create an instance of it so that we can sent the instance to the service method by filling in its parameters
        ParameterInfo[] paramInfos = methodInfo.GetParameters();

        //As said we get the paramater type, we can also loop the paramaters if there are more than one parameter, but as of now i have only one.
        var parameterType = paramInfos[0].ParameterType;

        //Creating the instance of Paramater type
        var parameter = compilerResults.CompiledAssembly.CreateInstance(parameterType.FullName, false, BindingFlags.CreateInstance, null, null, null, null);

        //Now we are setting the properties of that parameter by the value we want to set it before sending the request to the service, there can be multiple properties and
        //parameters, but as of now i know i want to fill in the paramater present at specified index, one can also make checks on the name of parameter or any other thing /
        //before making any assignment.
        parameterType.GetProperties()[1].SetValue(parameter, true);
        parameterType.GetProperties()[2].SetValue(parameter, txtCompostiteDataTypeStringValue.Text);

        //Composite data type parameter if any required by the method are added into optional paramaters
        object[] operationParameters = new object[] { parameter };

        //Invokes service method and get the result.
        var result = methodInfo.Invoke(proxyInstance, BindingFlags.InvokeMethod, null, operationParameters, null);

        //Now retreieving the result type, what i mean is i dont know in which composite type service is returning the result, so when we get the result, we retrieve its type
        //and then create the instance of that type.
        var resultType = methodInfo.Invoke(proxyInstance, BindingFlags.InvokeMethod, null, operationParameters, null).GetType();

        //Getting the values from the result properties and using them
        lblCompostiteDataTypeStringValueFromService.Text = resultType.GetProperties()[2].GetValue(result).ToString();

    }

    private object GetProxyInstance(ref CompilerResults compilerResults)
    {
        object proxyInstance = null;

        // Define the WSDL Get address, contract name and parameters, with this we can extract WSDL details any time
        Uri address = new Uri("http://localhost:64508/Service1.svc?wsdl");
        // For HttpGet endpoints use a Service WSDL address a mexMode of .HttpGet and for MEX endpoints use a MEX address and a mexMode of .MetadataExchange
        MetadataExchangeClientMode mexMode = MetadataExchangeClientMode.HttpGet;
        string contractName = "IService1";

        // Get the metadata file from the service.
        MetadataExchangeClient metadataExchangeClient = new MetadataExchangeClient(address, mexMode);
        metadataExchangeClient.ResolveMetadataReferences = true;

        //One can also provide credentials if service needs that by the help following two lines.
        //ICredentials networkCredential = new NetworkCredential("", "", "");
        //metadataExchangeClient.HttpCredentials = networkCredential;

        //Gets the meta data information of the service.
        MetadataSet metadataSet = metadataExchangeClient.GetMetadata();

        // Import all contracts and endpoints.
        WsdlImporter wsdlImporter = new WsdlImporter(metadataSet);

        //Import all contracts.
        Collection<ContractDescription> contracts = wsdlImporter.ImportAllContracts();

        //Import all end points.
        ServiceEndpointCollection allEndpoints = wsdlImporter.ImportAllEndpoints();

        // Generate type information for each contract.
        ServiceContractGenerator serviceContractGenerator = new ServiceContractGenerator();

        //Dictinary has been defined to keep all the contract endpoints present, contract name is key of the dictionary item.
        var endpointsForContracts = new Dictionary<string, IEnumerable<ServiceEndpoint>>();

        foreach (ContractDescription contract in contracts)
        {
            serviceContractGenerator.GenerateServiceContractType(contract);
            // Keep a list of each contract's endpoints.
            endpointsForContracts[contract.Name] = allEndpoints.Where(ep => ep.Contract.Name == contract.Name).ToList();
        }

        // Generate a code file for the contracts.
        CodeGeneratorOptions codeGeneratorOptions = new CodeGeneratorOptions();
        codeGeneratorOptions.BracingStyle = "C";

        // Create Compiler instance of a specified language.
        CodeDomProvider codeDomProvider = CodeDomProvider.CreateProvider("C#");

        // Adding WCF-related assemblies references as copiler parameters, so as to do the compilation of particular service contract.
        CompilerParameters compilerParameters = new CompilerParameters(new string[] { "System.dll", "System.ServiceModel.dll", "System.Runtime.Serialization.dll" });
        compilerParameters.GenerateInMemory = true;

        //Gets the compiled assembly.
        compilerResults = codeDomProvider.CompileAssemblyFromDom(compilerParameters, serviceContractGenerator.TargetCompileUnit);

        if (compilerResults.Errors.Count <= 0)
        {
            // Find the proxy type that was generated for the specified contract (identified by a class that implements the contract and ICommunicationbject - this is contract
            //implemented by all the communication oriented objects).
            Type proxyType = compilerResults.CompiledAssembly.GetTypes().First(t => t.IsClass && t.GetInterface(contractName) != null &&
                t.GetInterface(typeof(ICommunicationObject).Name) != null);

            // Now we get the first service endpoint for the particular contract.
            ServiceEndpoint serviceEndpoint = endpointsForContracts[contractName].First();

            // Create an instance of the proxy by passing the endpoint binding and address as parameters.
            proxyInstance = compilerResults.CompiledAssembly.CreateInstance(proxyType.Name, false, System.Reflection.BindingFlags.CreateInstance, null,
                new object[] { serviceEndpoint.Binding, serviceEndpoint.Address }, CultureInfo.CurrentCulture, null);

        }

        return proxyInstance;
    }
}

In the above code, what you need to change is the Address of WCF Service, Contract name, method name if different.

Now we are good to go, open this default page and try using the WCF Service, you can see you will get the results from the service; also you can debug it at any point of time.

If you want to download running sample refer  this: Source Code
I have made this sample using in Microsoft Visual Studio 2012/.Net Framework 4.5, although it is not going to give any issue in any of the framework on or above .Net 3.5.


Hope it helps…

No comments:

Post a Comment