On CNET: Pristine HDTV picture quality costs

Applied Reflection: Creating a dynamic Web service to simplify code

Tags: Web services, Channel management, Zach Smith, Web service, business logic, Web

  • Save
  • Print
  • 2

Takeaway: While Visual Studio and the .NET Framework enable developers to create and consume Web services via a simple interface, there are times when a developer must dig a little deeper into the technology to accomplish what is required. Zach Smith explains how to leverage a combination of the .NET Framework's Reflection API and Web services to create a dynamic business layer proxy.

This article is also available as a TechRepublic download, complete with sample code.

Many developers are using Web services to communicate with their business logic, and there are a lot of advantages to this. This approach allows a wide range of flexibility in the architecture that would otherwise be very hard to come by. However, there are also a few disadvantages. One disadvantage is the amount of tedious work involved in keeping your Web service methods in-sync with your business logic methods. This article will show you how you can avoid this the sync issue, while still enjoying all the benefits of using Web services.

The reasoning

I recently designed and built an application which used Web services for business layer communication. The interface used a custom component to request data, and the custom component used the Web services to communicate with the business layer. This allowed the interface to be deployed anywhere we wanted, and also gave us the ability to secure all communication via SSL. The general architecture was similar to Figure A.

Figure A

General architecture

The Business Logic classes contained static methods to process data before being sent to the Data Access Layer, or returned to the Communication Component via the Web Service.

This was working great for the first two weeks while we worked on the main system functionality. However, as time went on, we began adding more and more methods to the business logic that needed to be exposed via the Web service. This was consuming a tremendous amount of time as we had a one-to-one ratio of Web services to business logic classes. Every time we added a new business logic class, we had to make a new Web service, make the proxy class for the Web service, keep the proxy class in-sync with the Web service, and keep the Web service in-sync with the business logic.

We had a hard deadline, and a very tight schedule. What we needed was a way to automate or simplify the maintenance associated with using Web services. After tossing around some ideas (such as code generators -- we already used one to generate classes from DB tables), I came up with the idea of using reflection to dynamically invoke methods in the business logic and return their results through the Web service. After 1/2 an hour or so I had the first prototype, and about two hours later I had a component that I felt would work in a production environment.

The setup

Let's assume that you already have some classes developed. These classes can be thought of as "Data Transfer Objects" (DTOs – read more about DTOs here)

  • CustomerData -- Holds data for a single customer
  • OrderData -- Holds data for a single order

We also have these business logic classes whose task it is to talk with the data access layer and populate CustomerData and OrderData objects:

  • Customer -- Has two static methods, LoadByCustomerID(int customerID) and LoadByOrderID(int orderID). Both of these methods return a single CustomerData object.
  • Order -- Has two static methods, LoadByOrderID(int orderID) and LoadAllForCustomerID(int customerID).

Normally, for this type of situation, you would have two Web services - One for returning CustomerData objects, and one for returning OrderData objects (you could pack them into one Web service, but that gets crowded when dealing with a large number of classes). You would also have two methods in each of those Web services. The Web service methods would be called from your interface layer, or in my case the Communication Component. The Web services then would call your business layer to get the data required. So for the normal situation, we've got two DTOs, two classes, two Web services, and two methods in each Web service. (See Figure B)

For example:

Figure B

The setup

The application I was working on had about 100 instances of this, and we were adding more and more each day. Obviously that wasn't a solution that would work given our time constraints.

Using reflection, you can have one Web service with two methods handle each and every single call you would ever need to make to your business layer. This frees you up from having to maintain the Web service and proxy classes. (See Figure C)

For Example:

Figure C

Reflection

How it works

This works by exposing two methods in the Web service -- ExecuteMethod and ExecuteArrayMethod. Both of these methods accept the same parameters and work basically the same internally. The difference is that one returns an ArrayList of objects and one returns one object.

These methods require you to tell them which class in the business layer you want to use (the typeName parameter), which method within that class to execute (the method parameter -- remember that we’re using static methods here), and the values you want to send to that method as parameters (the arguments parameter). Once the Web service is called it accesses the business class, executes the method requested with the given parameters using the Type.Invoke method, and then returns the same value that the business class method returned.

So basically you are not directly executing anything on the business logic layer via the Web service. You’re instructing the Web service on which method to execute, and it is dynamically executing the method.

The code

The code for this type of dynamic Web service is in Listing A.

Listing A


C# Code:

///<summary>
/// Summary description for BusinessPipe.
///</summary>
[WebService(Namespace="http://tempuri/")]
[XmlInclude(typeof(CustomerData)),XmlInclude(typeof(OrderData))]
publicclass BusinessPipe : System.Web.Services.WebService
{
      //This should be set to the name of your business logic assembly.
      privateconststring BUSINESS_ASSEMBLY = "My.Business.Assembly";

      #region Component Designer generated code
      //Required by the Web Services Designer
      private IContainer components = null;

      ///<summary>
      /// Clean up any resources being used.
      ///</summary>
      protectedoverridevoid Dispose( bool disposing )
      {
             if(disposing && components != null)
             {
                   components.Dispose();
             }
             base.Dispose(disposing);       
      }
      #endregion

      private Type AccessType(string typeName)
      {
             Type type = null;
             Assembly assembly = System.Reflection.Assembly.Load(BUSINESS_ASSEMBLY);

             if(assembly == null)
                   thrownew Exception("Could not find assembly in BusinessPipe!");

             type = assembly.GetType(BUSINESS_ASSEMBLY + "." + typeName);

             if(type == null)
                   thrownew Exception("Could not find type!");

             return type;
      }

      ///<summary>
      /// Executes a method on the Business Logic and returns whatever object that
      /// method returns.
      ///</summary>
      ///<param name="typeName">The class in the Business Logic to reference.</param>
      ///<param name="method">The method that you want to execute in the class.</param>
      ///<param name="arguments">The arguments to send to the method.</param>
      ///<returns>The same object that the business logic method returns.</returns>
      [WebMethod]
      publicobject ExecuteMethod(string typeName, string method,
                                paramsobject[] arguments)
      {
             object returnObject = null;
             Type type = AccessType(typeName);

             try
             {
                   returnObject = type.InvokeMember(method,
                          BindingFlags.Default | BindingFlags.InvokeMethod,
                          null, null, arguments);
             }
             catch
             {
                   //Do some custom exception handling here.
                   throw;
             }

             return returnObject;
      }

      ///<summary>
      /// Executes a method on the Business Logic and returns the same ArrayList that
      /// the method returns.
      ///</summary>
      ///<param name="typeName">The class in the Business Logic to reference.</param>
      ///<param name="method">The method that you want to execute in the class.</param>
      ///<param name="arguments">The arguments to send to the method.</param>
      ///<returns>The same object that the business logic method returns.</returns>
      [WebMethod]
      public ArrayList ExecuteArrayMethod(string typeName, string method,
                                      paramsobject[] arguments)
      {
             ArrayList returnObject = null;
             Type type = AccessType(typeName);

             try
             {
                   returnObject = type.InvokeMember(method,
                          BindingFlags.Default | BindingFlags.InvokeMethod,
                          null, null, arguments) as ArrayList;
             }
             catch
             {
                   //Do some custom exception handling here.
                   throw;
             }

             return returnObject;
      }
}

Notes on the code


Notice that at the top of the Web service, I have included two XmlInclude statements. These two statements are required so that the framework knows how to serialize the OrderData and CustomerData objects. You will need to add these statements for each type of object that will be sent to or from the Web service. Also, the assembly which contains your business logic MUST be referenced from the Web service. If it is not, your Web service will not be able to access your business logic.

Disadvantages

There are a couple of disadvantages to this approach:

  1. You lose IntelliSense for the Web service and the method being called. This is a disadvantage if you're planning on having your Web service be consumed by another party, or if you have developers who are unfamiliar with the business logic code. This also means you lose type-safety when interacting with the Web service. The loss of type safety is, in my opinion, the largest downfall of the technique described here.
  2. There is no built-in security to restrict the methods in your business logic that are accessible from the Web service.

While these are certainly issues, I feel that they can be addressed and corrected relatively easily:

  1. To allow IntelliSense, you could easily create proxy classes that do nothing more than hand off requests/responses to/from the Web service. However, this takes away one of the advantages of the dynamic Web service, since you would then have to keep the proxy class in-sync with the business classes.
  2. To provide security, you could use custom attributes to define which business logic methods are visible, and which are not. This would be relatively easy to accomplish by using reflection to check the attributes before calling Type.Invoke().

Not for everyone

The solution detailed in this article is certainly not for everyone. For instance I wouldn’t use this for an external Web service. However, there are instances where this type of solution could save a lot of time. When used in combination with database mapping functionality, my team wrote about 60 percent less code than would have been required with traditional techniques.

  • Save
  • Print
  • 2

Print/View all Posts Comments on this article

Web servicesMark W. Kaelin Techrepublic | 10/19/06
I personally think this a cool ideadaveanand  | 10/27/06
Re: I personally think this a cool ideazs_box@...  | 10/27/06
I personally think this a cool ideadaveanand  | 10/31/06
Non Standard TechnologyJaqui  | 10/23/06
Re: Non Standard Technologyzs_box@...  | 10/24/06
Don't worry.bluemoonsailor  | 10/25/06
NopeJaqui  | 10/25/06
trueJaqui  | 10/25/06
Non_standard Microsoftganesh@...  | 10/31/06
Great explanationLukCAD  | 10/23/06
Secure web servicesLukCAD  | 10/23/06
Cautionary notejconigliaro@...  | 10/25/06
Re: Cautionary notezs_box@...  | 10/25/06
Code Generationnathan@...  | 10/25/06
Inappropriate Approachsthirupputkuzhi@...  | 10/25/06
Re: Inappropriate Approachzs_box@...  | 10/25/06
The webserviceganesh@...  | 10/25/06

What do you think?

advertisement
Click Here