.NET Remoting

The Alternative to Web Services

asp:cover story

LANGUAGES: C#

.NET VERSIONS: 1.1

 

.NET Remoting

The Alternative to Web Services

 

By Ken McNamee

 

You're probably familiar with the idea of using ASP.NET Web services for remote communication between applications. Web services are especially useful for interoperation between different platforms, such as a Java client making a call to an ASP.NET Web service.

 

Web services do have their drawbacks, however, and aren't the best option in some cases. For instance, .NET Remoting may be a better option when both the client and the server are running on the .NET Framework. One of the main issues with a Web service is the overhead involved when the request and response need to be converted back and forth between binary and XML. If you've ever tried to do XML serialization, you know that the performance cost isn't trivial.

 

The good news - at least for those of us lucky enough to be using .NET on both ends of a remote procedure call - is that .NET Remoting doesn't suffer from the XML serialization performance hit - unless you want it to. Like an ASP.NET Web service, which has multiple encoding options, .NET Remoting offers more than one communication format. There's HTTP SOAP, which is roughly equivalent to a SOAP Web service, including the performance hit due to serialization. Then there is HTTP Binary, which, of course, also communicates over the standard Internet protocol. However, the communication format remains binary, thus bypassing the performance problem of SOAP. There is a small performance hit as some serialization to a .NET proprietary format takes place, but it's an improvement over SOAP.

 

Finally, there's the TCP Binary option, which is as close to the metal as .NET gets out of the box. TCP Binary is the fastest option that .NET Remoting offers, and it allows for the most flexibility. At the same time, it provides less built-in functionality than HTTP SOAP or HTTP Binary. One of the major benefits of those options is that they are hosted by an ASP.NET Web server. This point cannot be overstated. ASP.NET is a very deep platform on which to build an application, even one that doesn't serve up any Web pages, and you would be hard-pressed to duplicate its functionality with your own TCP Binary server.

 

For this reason, the code sample on which this article is based (see end of article for download details) uses HTTP Binary as the communication mechanism. Later in the article I'll go into the pros and cons of each Remoting option. However, as a general rule I believe that HTTP Binary is the best option because of the better performance than SOAP, and the wealth of functionality at your disposal by using the ASP.NET platform. There are certainly valid reasons to choose HTTP SOAP or TCP Binary, but it would be hard to go wrong with HTTP Binary as your starting point.

 

The Basics

At a minimum, a .NET Remoting application consists of a remotely callable object, a server process to host the object, and a client process that makes the call to the object. Additionally, the application will more than likely also consist of one or more serializable classes that are passed through the Remoting framework between client and server. This setup is what almost all .NET Remoting applications will have in common.

 

One of the surprising discoveries I made while learning to use Remoting is how easy it is to create production-ready applications. It's almost as easy as creating a Web service. To start with, the remotely callable object is very simple to implement. Just have your class inherit from MarshalByRefObject, which enables the object to be used as an endpoint for a remote procedure call. As far as the object goes, that's all you have to do. Unlike Web services, you don't need to mark each method as a remote method. As long as the object inherits from MarshalByRefObject, all public methods will be accessible to remote clients - providing that the clients know the URL, and can successfully get past any authentication.

 

The Code Sample

The code sample described in and accompanying this article is an interface for managing the Products table in the Northwind database, and consists of three projects. First is the server project that hosts the remote object as an ASP.NET Web application. Second is the client project that communicates with the server. The third is something I haven't yet discussed: a class library project that contains common classes that are shared by the client and the server projects.

 

The Shared Assembly

The purpose of using a shared assembly is to prevent the client project from needing a direct reference to the server project, or vice versa. This provides a clean and logical separation between the two tiers without the danger of creating a circular reference. In this simple example, the shared assembly contains two types of objects: classes that encapsulate and transport data such as the Product class, and interfaces that the client can use to make calls to the actual remote objects.

 

The Product class displayed in Figure 1 encapsulates the Products table, and exposes its columns as .NET properties. Because it's marked with the Serializable attribute, the Product class can be used as a lightweight data container, and can be sent through the .NET Remoting transport mechanism between client and server.

 

[Serializable]

public class Product

{

  // Member variables.

  ...

 

  // Constructors.

  public Product()

  {

    _supplier = new DictionaryItem();

    _category = new DictionaryItem();

  }

 

  // Properties.

  public int ProductID { get {...} set {...} }

  public string ProductName { get {...} set {...} }

  public DictionaryItem Supplier { get {...} set {...} }

  public DictionaryItem Category { get {...} set {...} }

  public string QuantityPerUnit  { get {...} set {...} }

  public decimal UnitPrice { get {...} set {...} }

  public int UnitsInStock { get {...} set {...} }

  public int UnitsOnOrder { get {...} set {...} }

  public int ReorderLevel { get {...} set {...} }

  public bool Discontinued { get {...} set {...} }

}

Figure 1: The Product class encapsulates the Products table in the Northwind database and serves as a serializable transport container.

 

The ProductSearchCriteria class (see Figure 2) serves as the container for users' search criteria - in this case the product ID and the product name. In addition, it contains one helper method (ToWhereClause) that converts the search criteria into the WHERE clause that will be used in the SQL statement that executes the search of the Products table. This logic could just as easily go into the Web service, but I chose to put it here.

 

[Serializable]

public class ProductSearchCriteria

{

  // Member variables.

  ...

 

  // Constructors.

  public ProductSearchCriteria() {}

 

  // Properties.

  public int ProductID { get {...} set {...} }

  public string ProductName { get {...} set {...} }

 

  // Public methods.

  public string ToWhereClause()

  {

    StringBuilder stringBuilder = new StringBuilder();

 

    if (_productID > 0)

      stringBuilder.Append(" (Products.ProductID = " +

        _productID.ToString() + ") ");

    else if (_productName.Trim() != string.Empty)

      stringBuilder.Append(" (Products.ProductName LIKE '" +

        _productName + "%') ");

   

    if (stringBuilder.Length > 0)

      stringBuilder.Insert(0, "WHERE ");

     

    return stringBuilder.ToString();

  }

}

Figure 2: The ProductSearchCriteria class contains the fields that a user can use to search the Products table.

 

The last item of note in this project is the IProductManager interface. The purpose of this interface is to allow the actual remote object on the server to be abstracted away from the client. The client will not be aware of the ProductManager remote object that's being hosted by ASP.NET on the server. It will only know about the interface in the Shared assembly, what methods it contains, and what their signatures are.

 

The Server

The Server project is like any other ASP.NET Web application, except for a few differences:

  • It doesn't need to contain any Web pages or Web services.
  • To be a Remoting host it must contain at least one class that inherits from MarshalByRefObject.
  • The Web.config file must contain a special <system.runtime.remoting> section that tells ASP.NET which remote objects to expose and what their URL filename will be.
  • The Server project must have a reference to the System.Runtime.Remoting assembly and the Shared Project.

 

Although the Web application doesn't need to contain any Web pages, there's nothing stopping you from adding some. .NET Remoting objects and ASP.NET Web forms can co-exist quite nicely within the same Web application. In fact, there are cases where this would be preferable, such as hosting the "help" Web pages or a tutorial for the Windows Forms client.

 

The first task is to create the remote object class, which is ProductManager in this example. You can see part of this class in Figure 3. The ProductManager class inherits from MarshalByRef, which provides it with the status of remotable object. However, the class also implements the IProductManager interface, which means that it must implement all the methods of that interface. One method shown in Figure 3 is GetProductList. This method takes a ProductSearchManager object as its sole parameter and returns an array of Product objects. The ToWhereClause method of the ProductSearchManager object is used to complete the SQL statement that searches the Product table, and returns only the products matching the user's criteria.

 

public class ProductManager :

  MarshalByRefObject, IProductManager

{

  public Product[] GetProductList(

    ProductSearchCriteria searchCriteria)

  {        

    string sql = "SELECT " + "Products.ProductID, " +

      "Products.ProductName, " + "Products.SupplierID, " +

      "Suppliers.CompanyName, " + "Products.CategoryID, " +

      "Categories.CategoryName, " +

      "Products.QuantityPerUnit, " +

      "Products.UnitPrice, " + "Products.UnitsInStock, " +

      "Products.UnitsOnOrder, " +

      "Products.ReorderLevel, " + "Products.Discontinued "+

      "FROM " + "Products " + "LEFT OUTER JOIN " +

      "Suppliers ON Suppliers.SupplierID=" +

      "Products.SupplierID " + "LEFT OUTER JOIN " +

      "Categories ON Categories.CategoryID=" +

      "Products.CategoryID " +

      searchCriteria.ToWhereClause() + " " +

      "ORDER BY " + "Products.ProductName";

    

    DataTable dataTable = SqlHelper.GetDataTable(sql);

    Product[] products = new Product[dataTable.Rows.Count];

    

    for(int i = 0; i < dataTable.Rows.Count; i++)

    products[i] = InitProduct(dataTable.Rows[i]);

    

    return products;

  }

}

Figure 3: The ProductManager class is the object that can be called from the remote client. Inheriting from the MarshalByRef class gives it this ability.

 

So you can see that there is really nothing special about a remotable object class. Other than inheriting from MarshalByRef, you develop the class just as any other class. One more thing to note about this class is that it isn't restricted to being called only from remote clients. Any other code that can see the ProductManager class can call one of its public methods. One option here is to create a Web service that internally calls the ProductManager methods. This would come in handy if you wanted internal, .NET clients to use the Remoting objects and external, non-.NET clients to use the Web service. You could concentrate all your business logic into the remote object and not have to duplicate it in the Web service.

 

You might be thinking, "How does the client call the remote object if there's no Web page or Web service to complete a URL?" This is where the Web.config file comes in. You need to add some configuration information to fake a URL, which you can see demonstrated in Figure 4.

 

<system.runtime.remoting>

  <application>

    <service>

      <wellknown mode="SingleCall"

        type="CodeSample.Server.ProductManager,

               CodeSample.Server"

        objectUri="ProductManager.rem"/>

    </service>

    <channels>

      <channel ref="http">

        <serverProviders>

          <formatter ref="soap" typeFilterLevel="Full"/>

          <formatter ref="binary" typeFilterLevel="Full"/>

        </serverProviders>

      </channel>

    </channels>

  </application>

</system.runtime.remoting>

Figure 4: The web.config file needs some configuration settings to provide the remote object with a fake URL for the client to call.

 

The key node in the <system.runtime.remoting> section is the <wellknown> node. The type attribute points to the fully qualified class name and the assembly in which to find it. The objectUri attribute is the resource name that you want to assign the remote object in the URL. So the <wellknown> node in this case configures the CodeSample.Server.ProductManager class in the CodeSample.Server assembly to have a resource name of ProductManager.rem. The extension that you give in the objectUri is important. It can be almost anything you like, as long as it doesn't conflict with another extension that is already mapped to the ASP.NET ISAPI DLL. There are two extensions set aside for Remoting when you install the .NET Framework: .rem and .soap. It's preferable that you use one of these two extensions.

 

The Client

The actual implementation of the Remoting client isn't important. What is important is the code for calling a remote object. Unlike Web services, you don't need to add a Web Reference or create a proxy object to use .NET Remoting on the client. You only need a few pieces of information:

  • The URL of the remote object.
  • The type of the remote object, or the interface that the remote object implements.
  • What the communication protocol will be (HTTP or TCP) and what the format of the communication will be (SOAP or Binary).

 

The only other step you need to take is to make sure that, like the server project, your client references the System.Runtime.Remoting assembly and the Shared project. As displayed in Figure 5, the code for calling a remote object's methods is not exactly intuitive. In fact, there are a couple of ways to accomplish the task: programmatically, and by using a configuration file. Figure 5 displays the programmatic method.

 

private IProductManager GetIProductManager()

{

  string objectUri="http://domain/vdir/ProductManager.rem";

 

  HttpClientChannel httpBinaryChannel =

    new HttpClientChannel("HttpBinary",

      new BinaryClientFormatterSinkProvider());

  try {

    ChannelServices.RegisterChannel(httpBinaryChannel);

  } catch {}

 

  return Activator.GetObject(typeof(IProductManager),

    objectUri);

}

 

private void btnSearch_Click(object sender, EventArgs e)

{

  //..construct searchCriteria

  Product[] products = GetIProductManager().

    GetProductList(searchCriteria);

}

Figure 5: Programmatically calling a remote object is not very intuitive, but it will make sense - eventually.

 

Remoting works using the concept of channels, which the .NET Framework uses to transport the request to the ASP.NET runtime on the server. In this case, we're using an HttpClientChannel and initializing it as HttpBinary in its constructor. Next, the channel must be registered by the ChannelServices object. Lastly, we call Activator.GetObject and pass in the type we're trying to activate and the URL where it can be found. The GetIProductManager method returns an instance that matches the IProductManager interface, which enables the GetProductList method to be called. You can encapsulate most of this plumbing code into a helper class library, so that your regular form code can call remote methods as easily as a local method.

 

SOAP or Binary or TCP?

Like almost all programming decisions, which .NET Remoting option you choose really depends on your specific project and the importance of performance versus maintainability, project completion speed, and security. I've already stated my preference for HTTP Binary, which is where I always start.

 

If performance is of the utmost importance, your best - and usually only - option is TCP Binary. This will allow for the fastest possible communication between client and server with an absolute minimum of overhead on either end. You can develop your own encryption and authentication methods to use if ASP.NET doesn't suit your needs. Another key difference between the TCP and HTTP options is that you can choose which ports to use on the client and the server. With HTTP, IIS chooses the ports.

 

Conclusion

.NET Remoting is a useful technology to learn. Although newer and better inter-application communication technologies are on the way from Microsoft, Remoting is here now and should at least be considered when the client and server are both based on .NET. Granted, it will never overtake Web services in flexibility and popularity. However, as you have seen, .NET Remoting can be just as simple to develop as Web services, and it offers a few options that Web services does not.

 

Resources

.NET Remoting FAQ: http://www.ingorammer.com/RemotingFAQ/

 

The sample code accompanying this article is available for download.

 

Ken McNamee is a Senior Software Developer with Vertigo Software, Inc., a leading provider of software development and consulting services on the Microsoft platform. Prior to this, he led a team of developers in re-architecting the Home Shopping Network's e-commerce site, http://www.HSN.com, to 100% ASP.NET with C#. Readers can contact him at [email protected].

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish