Component Services for .NET III of V...

Part III: Practicalities - creating and consuming a serviced component.

Knowledge assumed: VB.NET, VS.NET, component/ n-tier development, transactions, Parts I and II


By: Chris Sully Date: September 18, 2004

Introduction

As introduced in Part I of this series of 5 articles, the topic of component services is about creating enterprise applications that need to be secure, reliable, available, efficient and scalable with the minimum developer effort. In this part we continue our look at the practicalities of creating and consuming a serviced component, in particular looking at performing the following remaining necessary steps:

An acknowledgement: the examples presented here are based on those within Mike Gunderloy's Developing XML WebServices and Server Components.

Creating interfaces that are visible to COM

COM clients and COM+ services communicate with .NET components using the interfaces provided by the .NET components. The CCW automatically generates these interfaces for a .NET class based on the settings of the ClassInterface and the InterfaceType attributes. Additionally, you can also write an interface explicitly and inherit the .NET class from that interface. These are important in defining how COM and COM+ interact with the managed components.

ClassInterface

This attribute specifies how the interfaces will be generated for a class, if they will be generated at all. It can be applied to either a class or an assembly. Once applied to an assembly it applies to all classes in that assembly.

Acceptable values of the attribute are defined by the ClassInterfaceType enumeration:

AutoDispatch: the default, generating a dispatch-only interface for the class, meaning that the class only supports late binding for COM clients. No type information is published to the COM type libraries.

AutoDual: firstly, it automatically creates an interface that exposes all the public members of a class. Secondly, it generates dual interfaces meaning that COM clients can use these interfaces for both late and early binding. All the type information is produced for the class interface and published in the type library. This is not a recommended setting as it creates versioning problems.

None: does not generate any automatic interfaces – you need to explicitly write interfaces for your class. This is the recommended setting.

Thus the next step in our explorations is to create an interface for our component that is visible to COM. We're actually going to employ the AutoDual option to start with for demonstration purposes, despite the information above.

Add the following line before your class definition:

<ClassInterface(ClassInterfaceType.AutoDual)> _
Public Class NorthwindSC ...

Delete your component from the catalog, rebuild and install it.

Now if you navigate to the _NorthwindSC interface of your component you'll see a number of methods available. If you right click these methods you will see you can configure their properties.

Now return to your class, add an interface and modify the code as follows:

Public Interface INorthwind
  Function ExecuteQuery(ByVal strQuery As String) As DataSet
  Function UpdateData(ByVal ds As DataSet) As Integer
End Interface

<ClassInterface(ClassInterfaceType.None)> _
Public Class NorthwindSC
...

Public Function ExecuteQuery(ByVal strQuery As String) As DataSet Implements INorthwind.ExecuteQuery
...

Public Function UpdateData(ByVal ds As DataSet) As Integer Implements INorthwind.UpdateData
...

Here you see we are explicitly defining and using our interface.

Again unregister and register the component with COM+.

You'll now see in the Component Services administrative tool that your Interface is listed rather than one created automatically for you. Further, only the methods of the serviced component you explicitly implement are displayed rather than those of the base classes as well.

We have now seen all three of the ClassAttribute options in action, AutoDual and None explicitly and AutoDispatch implicitly as this is the default option applied when the ClassInterface attribute is omitted. We've seen that with both the AutoDispatch and AutoDial settings an interface is automatically generated. With None, you must explicitly define and implement the interface.

Versioning Issues due to AutoDial

The AutoDual setting of the ClassInterface exports the type information and DispId of the class and base class members to the COM type library. This allows COM clients to bind their programs with the DispId of the members at compile time. These DispIds are generated based on the position of the member in the class. This can cause versioning problems because if in the next version of your component, you change the order of the members and export the class to a COM type library, the DispId of the members will be changed. This will cause already compiled COM programs to fail. Hence AutoDual is not a recommended option.

The AutoDispatch setting does not export the type information and DispId meaning that clients cannot bind to a particular DispId at compile time, and no problems occur when the order of methods are changed in the next version. However, because of its support for late binding only, AutoDispatch is normally limited to scripted or interpreted execution environments.

The None setting prevents any automatic interface generation in the COM type library – you must explicitly define them and you will also get the benefit of both early and late binding. However, writing your own interface separates the 'view' of a class from its implementation. Even in the future, if you decide to change the order of the methods in your implementation, you are fine unless you modify the interface definition.

Thus best practice dictates None should be used meaning you must create your own interface explicitly.

The InterfaceType attribute

The InterfaceType attribute can be applied to the interfaces that you declare and configures how the interfaces are exposed to COM clients and the values are those of the ComInterfaceType enumeration:

Component Identification

GUIDs are used by COM+ to uniquely identify each COM+ application (each assembly is registered as a separate COM+ application). You can assign these explicitly yourself or the assembly registration process will assign them automatically.

To assign explicitly, you use the Guid attribute as follows:

<Guid(“GUID GOES HERE”)>
Public Class ...

GUIDs can be created using the command line GUID generation tool (guidgen.exe) or through the VS.NET Tool.

You may want to use explicit assignation when developing and registering the component several times with COM+ to prevent multiple redundant copies of your component being registered with COM+.

Installing to the GAC

Commonly you will install your component to the Global Assembly Cache. This is not a requirement for using a serviced component but it is recommended as it ensures the CLR is always able to locate the assembly. The installation is achieved via the GAC tool, gacutil.exe, easily achieved by navigating to the folder where the dll resides and executing the following from the VS.NET command prompt:

gacutil /i dataSC.dll

For further information on the GAC see my other articles due on DotNetJohn shortly.

Consuming a serviced component

This is no different from using any other managed component. Here's an example to prove it, with the client being a web form in this case.

Create a new web application and add a multiline textbox (txtQuery) a button (btnExecute) and a datagrid (dgResults) to the form. Add project references to System.EnterpriseServices and your serviced component.

To your code behind add a couple of imports directives:

Imports System.Data.SqlClient Imports DataSC

and code for the button click event:

Private Sub btnExecute_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExecute.Click
Dim nsc As NorthwindSC = New NorthwindSC
  Try
    ' Call the ExecuteQuery() method of the
    ' NorthwindSC serviced component to execute
    ' query and bind the results to the data grid
    dgResults.DataSource = nsc.ExecuteQuery(txtQuery.Text)
    dgResults.DataMember = "Results"
    dgResults.DataBind()
  Catch ex As Exception
    Response.Write(ex.Message & ": Invalid Query")
  Finally
    nsc.Dispose()
  End Try
End Sub

Run the page and you'll see it works as you expect, using the component as if it were any other.

Conclusion

This article completed our overview of creating and consuming a serviced component. In the next article in the series we'll make a start at looking at what additional support COM+ can provide to our component.

References

.NET SDK

Developing XML WebServices and Server Components with VB.NET and the .NET Framework
Mike Gunderloy
Que