Server Access Programmer's Guide:
Using eRoom SAAPI with Microsoft .NET

As of eRoom version 7.01, SAAPI can be used from Microsoft.NET languages, with some important restrictions.

 

Important Note

The following requirements and restrictions continue to apply with the following caveat:

Due to the technological restrictions by Microsoft, eRoom allows Visual Basic .NET custom extensions. However, custom extensions should implement Marshal.ReleaseComObject() to release the associated eRoom COM objects.

Requirements and Restrictions


  • eRoom version 7.01 or later. Do not use eRoom V6 or earlier with Microsoft.NET

  • You cannot use .NET to implement eRoom Extensions directly (including custom frames, dialogs, custom commands, viewers, or UIEvents, nor can .NET components be used in synchronous event handlers. eRoom extensions are still native ASP/COM.

  • SAAPI must be called from a Single Threaded Apartment (STA) thread. Do not use SAAPI in .NET components or web services or other non-STA applications.

  • IERUApplication::Close() must be called before returning from your code while still on the same thread. It is imperative that you use the ERUNetApplication object (”r;eRoom.NETApplication”) object and call IERUApplication::Close() prior to returning from your code. This allows eRoom to release underlying storage objects while on the same execution thread. This is required since .NET objects are non-deterministically destroyed on another thread by the .NET garbage collector.  Failure to call ::Close() before returning control from your .NET code can cause serious problems on the eRoom server.

  • Always use AspCompat=true in ASPX pages that use SAAPI

  • The ASPNET user must have ”r;Act as part of Operating System” rights. (Controlled via ”r;Local Security Policy” in Control Panel.)

  • When using ERUUserContext objects (via IERUApplication::LoginUser() or new ERUUserContextClass() & ImpersonateUser()) to temporarily establish a user context, you must release the underlying storage objects by calling Marshal.ReleaseComObject or the new 7.2.1  IERUClosable::CloseObject() method.  Failure to do so will leave the usercontext active until IERUApplication::Close() is called to close all objects created in the current thread.  

eRoom.eRoomAPI Primary Interop Assembly


We recommend that you use the eRoom Primary Interop assembly when accessing eRoom SAAPI from Microsoft.NET. This allows eRoom types to be passed between .NET components. This is a strong named assembly. By default, the eRoom.eRoomAPI.dll file is installed in the c:\Program Files\eRoom\eRoom Server directory with eRoom release 7.2.1.

You must register the eRoom Primary Interop assembly (eRoom.eRoomAPI.dll) with Global Assemble Cache (GAC) before it can be referenced from your .NET applications. If this is already registered, you should unregister eRoom.eRoomAPI.dll and re-register it.

To unregister eRoom’s Primary Interop Assembly, execute the following command:
 

gacutil /u c:\program files\eRoom\eRoom Server\eRoom.eRoomAPI.dll

To register eRoom’s Primary Interop Assembly, execute the following command:
 

gacutil /i c:\program files\eRoom\eRoom Server\eRoom.eRoomAPI.dll

Note: You must re-register the eRoom Primary Interop assembly when you upgrade the eRoom server.

Strong named assemblies are required if the assembly is referenced by another assembly with a strong name. You must use strong names when the assembly is to be exposed to COM as a COM component, and when the assembly is to be installed to the GAC. Using strong named imports of eRoomAPI is recommended for these reasons.

The default Visual Studio mechanism for referencing a COM component produces a non-strong named import in both VB and C#. In C#, it is possible to give the import a strong name using properties on the reference. In VB there is no way to do this within the IDE.

Using eRoom.eRoom API Primary Interop Assembly


If you have not already registered the Primary Interop Assembly with GAC, see the instructions for doing so in the previous section.

To add a reference to the eRoom Primary Interop Assembly from MS Visual Studio .NET, select ”r;.NET” tab in the ”r;Project-References&ldots;” dialog. After clicking ”r;Browse&ldots;”, select the ”r;eRoom.eRoomAPI.dll” assembly from you eRoom server directory.

For Example:

 ”r;\Program Files\eRoom\eRoom Server\eRoom.eRoomAPI.dll”

After adding a reference to your application, you access eRoom.eRoomAPI types by adding a "using" statement to classes and modules. For example,  ”r;using eRoom.eRoomAPI”

Using System.Array and IERUCollection for VARIANT parameters


SAAPI uses the VARIANT datatype containing SAFEARRAY for a number of properties and method input and outputs. Note that SAAPI arrays are always returned as 1-based SAFEARRAYS. SAAPI will accept either an object containing a SAFEARRAY or an ERUCollectionClass() object for parameters that accept a SAFEARRAY.  

// Using System.Array for SAFEARRAY

System.Array acells = (System.Array)query.GetRowCells(resultNumber);
for(int j = 1;j <= acells.Length;j++ )
{
Response.Write ( "cell "+ j + "="+ acells.GetValue(j) + "<br>");
}

See the TestNetWebApp example--referred to in the Using SAAPI in .NET ASPX Pages section below--for more System.Array and ERUCollectionClass() examples.)

Releasing the ERUUserContext After Impersonation


If your code uses IERUApplication::LoginUser() or new ERUUserContextClass(), and its ImpersonateUser() method to establish a eRoom user context under .NET, you must explicitly destroy the underlying storage objects to return to the previous user context. This can be done by calling the Microsoft.NET Marshal.ReleaseComObject() method or calling IERUClosable::CloseObject() method (added in 7.2.1). This will release the eRoom user context associated with the object and return you to the previous user context (if any).

Note that you do not need to explicitly release the user context with ::ReleaseComObject or ::CloseObject() if you don’t plan to return to the previous user context before calling ERUNetApplication::Close(). When writing libraries/helper functions that establish a user context temporarily, you must release the user context immediately to ensure that the caller returns to their preexisting user context.

Building .NET Applications


Example .Net Application

SAAPI must be called from an Single Thread Apartment (STA) thread. For a simple, one thread application, such as a console or form, you can use the STAThread attribute. In C#, you can achieve this as follows:
 

using System;
using System.Threading;
using System.Diagnostics;
using eRoomAPI;

namespace DotNetSAAPISample
{

class SAAPISample
{

[STAThread]
static void Main(string[] args)
{

new SAAPISample().DoWork();

}
void DoWork()
{

IERUApplication eRoomApplication = null;
try
{

ApartmentState apartmentState =  Thread.CurrentThread.ApartmentState;
if (apartmentState != ApartmentState.STA)
throw new ApplicationException("The SAAPISample.DoWork
+ method must be invoked from an STA thread.);

eRoomApplication = new ERUNetApplicationClass();
Console.WriteLine(

"The eRoom Server version is "
+ eRoomApplication.VersionNumber + ".");

}
finally
{

if(eRoomApplication != null)
eRoomApplication.Close();

}

}

}

}

VB.Net projects are STAThread by default.

Note that the test of Thread.CurrentThread.ApartmentState is redundant with the use of STAThread attribute. Where the Thread.CurrentThread.ApartmentState test is especially important is when the code using eRoom objects is within a .dll or other method where the developer of the eRoom code cannot guarantee that the calling code is an STA thread.

One common example of this is .Net WebMethod, such as a .asmx page. The AspCompat attribute that works to give an .aspx page a STA threading at invocation of the Page_Load event does not work with .asmx pages. Consequently, an application using SAAPI from a WebMethod needs to spawn a second STA thread, or use a thread pooler that can return an STA thread. In this case, it is a good idea to instantiate all eRoom objects in a try block and call IERUApplication.Close() from a finally block. This is true for C#. If there is any doubt of your threading model, be certain to programmatically verify that you are running in an STA.

There are various ways to spawn an STA thread or construct a thread pooler. Consult Microsoft.NET documentation or MSDN for details on .NET programming.

Using SAAPI in .NET ASPX Pages


You cannot write eRoom extensions directly as ASPX pages, but you can use eRoom SAAPI in .NET ASPX pages within some guidelines. See Requirements and Restrictions.

Always use AspCompat=true

You must use AspCompat=true in your ASPX pages. For example:

<%@ Page Language="vb" AspCompat=true AutoEventWireup="false" Codebehind="WebForm1.aspx.vb" Inherits="TestNetWebAppp.WebForm1"%>

Note: Using AspCompat=true does not cause all page processing to use an STA. Only processing in or after the Page_Load event, and before or during Page_Unload, is STA. Never instantiate eRoom objects from code that executes globally to the module. See ”r;correct” and ”r;incorrect” examples below.

Always call ERUNetApplication::Close() before returning from your code

You must call ERUNetApplication::Close() while on the same thread that was used to acquire a SAAPI object. This allows eRoom to dispose of underlying storage objects while on the same thread that created the objects. There should be no execution path (including errors) that doesn’t call ::Close() just prior to returning. This is best accomplished by using Page_Unload to call ERUNetApplication::Close(). Alternately, use a finally{} block or error handlers to ensure that all paths call ::Close() prior to returning control.

Example .NET ASPX Web Application

Please refer to the ”r;TestNetWebApp” eRoom sample for a complete C# ASP.NET example. The sample is located in the ...\Toolkit\Samples\MSDotNet\TestNetWebApp directory.

Installing the TestNetWebAppSample

  1. Register the eRoom Primary Interop Assembly with the Global Assembly Cache Utility. (See the section eRoom.eRoomAPI Primary Interop Assembly above for more information.) Note: You must re-register the eRoom Primary Interop Assembly when you upgrade to the eRoom server.

  2. On the eRoom Server, copy the contents of TestNetWebApp.zip to your IIS server (ie. \INetPub\wwwroot).

  3. On the Windows Start menu, go to Settings > Control Panel > Administrative Tools and double click on Internet Services Manager.

  4. Select the Default Web Site, then right click on the TestNetWebApp folder and choose Properties.

  5. On the Directory tab, click the Create button in the Application Settings section, then select Low (IIS Process) from the Application Protection menu.

  6. On the Directory Security tab, click the Edit button in the Anonymous access and authentication control section. On the Authentication Methods dialog, make sure that Anonymous access is selected, and all options under Authenticated acess are deselected. Click OK to close the Authentication Methods dialog.

  7. Click OK to close the TestNetWebApp Properties dialog.

  8. On the Windows Start menu, go to Settings > Control Panel > Administrative Tools and double click on Local Security Policy. Under Local Policies, select User Rights Assignment. On the Policy list, double-click on Act as par of the operating system.

  9. On the Local Security Policy Setting dialog, verify that the Effective Policy Setting is enabled and click OK.

  10. On the Select Users or Groups dialog, choose ASPNET from the Name list, click Add, then click OK.

  11. Now you should be able to run the TestNetWebApp sample. Open your browser and go to the following URL: http://servername/TestNetWebApp/WebForm1.aspx

  12. Enter an eRoom Name and Password and log into eRoom. Select a room from the User’s Member eRooms list and click OK. You should see a list of the items in the Home Page for the eRoom.

Correct and Incorrect Examples

Below you will find the important excerpts from the ”r;TestNetWebApp” example. This C# Web application uses a codebehind class to manage the eRoom application object and call IERUApplication::Close() in Page_Unload().

Correct Examples:

File 1 of 2: (TestNetWebApp\WebForm1.aspx)
 

<%@ Page language="c#" AspCompat=true Codebehind="WebForm1.aspx.cs"

AutoEventWireup="false" Inherits="TestNetWebApp.WebForm1" %>

!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"

<HTML>

<HEAD>

<title>WebForm1</title>

<meta name="GENERATOR" Content="Microsoft Visual Studio 7.0">

<meta name="CODE_LANGUAGE" Content="C#">

<meta name="vs_defaultClientScript" content="JavaScript">

<meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5">

</HEAD>

<body MS_POSITIONING="GridLayout">

<form id="Form1" method="post" runat="server">

<asp:Button id="OKButton" style="Z-INDEX: 103; LEFT: 230px; POSITION: absolute; TOP: 100px" runat="server" Width="100px" Height="28px" Text="OK"></asp:Button>

</form>

</body>

</HTML>

 

File 2 of 2: (TestNetWebApp\WebForm1.aspx.cs/ application)
 

// Another example using system using statements omitted for clarity.

using System.Threading;

using eRoomAPI;

using System.Runtime.InteropServices;  // needed for COMException and ReleaseComObject
 

namespace TestNetWebApp

{

/// <summary>

/// Summary description for WebForm1.

/// </summary>

public class WebForm1 : System.Web.UI.Page

{

// Declare an interface for our application object

IERUApplication eRoomApplication;

protected System.Web.UI.WebControls.Button OKButton;

private void Page_Load(object sender, System.EventArgs e)

{

// Create the eRoom NET application object

eRoomApplication = new ERUNetApplicationClass();

Response.Write("Page_Load: The eRoom Server version is "

+ eRoomApplication.VersionNumber + ".<br>");

}

private void Page_Unload(object sender, System.EventArgs e)

{

If (eRoomApplication != null)

{

// release eRoom objects while still on same execution thread

eRoomApplication.Close();

eRoomApplication = null;

}

}
 

private void InitializeComponent()

{    

this.OKButton.Click += new System.EventHandler(this.OKButton_Click);

this.Unload += new System.EventHandler(this.Page_Unload);

this.Load += new System.EventHandler(this.Page_Load);

}
 

private void OKButton_Click(object sender, System.EventArgs e)

{

try {

// access the eRoom site via SAAPI

IERUUserContext uc = new ERUUserContextClass();
 

// Establish user context as builtin site admin user.

// Alternately, you could determine the loggedin eRoom user from // session cookies.

uc.ImpersonateUser (eRoomAPI.ERUBuiltinUserID.erUserSiteAdminsitrator,

eRoomAPI.ERUParameterType.erParamTypeID);

// Access the eRoom site object

IERUSite site = uc.Site;

Response.Write ("Site=" + site.Name);

}

catch (Exception e) {

// handler error here

}

finally

{

// revert to previous user

If (uc !=null)

{

// Release the user context NOW.

//   WARNING: Failure to release user context will result

//     in this user context remaining active for the caller.

// Note: eRoom 7.2.1 adds IERUClosable;

//   You can use Marshal.ReleaseObject for same result.

// IERUClosable closable = (IERUClosable)uc;

// closable.CloseObject();

Marshal.ReleaseComObject(uc);

uc=null;

}

}

}

}

}

In the correct example, the ERUNetApplicationClass() is instantiated in Page_Load and the Close() method is called in Page_Unload.

Incorrect example:

<%@ Page Language="C#" AspCompat=true %>

<%@ Import namespace="eRoomAPI" %>

<script runat="server">

IERUApplication eRoomApplication = new ERUNetApplicationClass();

public void Page_Load()

{

}

</script>

Note that in the incorrect example, the ERUNetApplicationClass is instantiated at the module level (i.e., in the page constructor) before Page_Load.

SAAPI Must be called from a Single Threaded Apartment (STA)

Calls to eRoomAPI (SAAPI) must be from an STA thread.

You can verify that you are running in an single threaded apartment (STA) by using the following Microsoft.Net code:

ApartmentState apartmentState = Thread.CurrentThread.ApartmentState;
if (apartmentState != ApartmentState.STA)

<throw new System.Exception("Must be run in STA!");

Note: To access System.Threading, you must add the appropriate reference to the top of your module:

Visual Basic:

Imports System.Threading

C#:

using System.Threading;

Troubleshooting Errors and Problems


There a few specific errors that are returned by SAAPI when working in the .NET environment.

EROOM_E_SAAPI_OBJ_CLOSED (H8004039B)  -- You are trying to access a SAAPI object after calling ERUNetApplication::Close() method. You shouldn’t access SAAPI objects after calling ::Close().

EROOM_E_SAAPI_OBJ_WRONG_THREAD_OWNER (H8004039C) &endash;- You are trying to access an object that was created on a different thread. You can only access SAAPI objects on the thread in which they were created.

eTrace.exe is eRoom’s debug tracing facility. Running eTrace with levels Server=2, Storage=3 will show when facility and site connections are aquired and released.

You may trace your own debugging information to eTrace’s ”r;Custom” subsystem by using the ERUTraceMessageClass().

For example:

IERUTraceMessage etrace= new ERUTraceMessageClass();
 

etrace.Trace (3, ”r;SomeContext::Something happened!”);
 

etrace.TraceError (”r;SomeContext::Something Bad Happend!”);

Output traced with ::Trace() method will appear in eTrace.exe if the ”r;Custom” subsystem’s trace level is greater than or equal to the traced level.  ::TraceError() will always trace regardless of trace level.