InfiniTec - Henning Krauses Blog

Don't adjust your mind - it's reality that is malfunctioning

Custom Forms with Outlook Web Access 2007

In my first article about custom forms for Outlook WebAccess I wrote about the difficulties that are associated with the development of custom forms. Thanks to a hint from reader Ciusso I was able to come up with another solutions that makes the whole stuff a lot easier. Here is a walkthrough to create custom forms the easy way – with support for standard postback, AJAX… the whole enchilada. As a prerequisite, download the EWS Managed API. It’s much simpler to use than the Exchange Web Services.

First, start with a new WebProject. If you are developing directly on an Exchange 2007 server (which is hopefully a test machine and not production – don’t do that!), the new solution can be created directly under the directory for custom OWA forms: %ProgramFiles%\Microsoft\Exchange Server\ClientAccess\Owa\forms. This simplifies development because no files have to be deployed after each build.

image

The problems I outlined in the first article are caused by an HTTP module, which is defined in the web.config file of Outlook WebAccess. The web.config file of Outlook Web Access is located in the folder %ProgramFiles%\Microsoft\Exchange Server\ClientAccess\Owa:

image

When you open the web.config file, you’ll see that Outlook Web Access adds two assemblies, one HTTP module and two HTTP handlers.

 image

Since the custom form is located beneath the OWA folder, it inherits all its configuration settings from the Outlook Web Access configuration. The trick is to remove the artifacts introduced  by Outlook Web Access. For the HTTP module and the two HTTP handlers this is an easy task: As shown in the next picture, just add “remove” tags for each artifact.

image

Things get more complicated with the assemblies, however. The two assemblies can not be removed via “remove tags”. It seems to be necessary to clear the entire collection of assemblies with a “clear” tag. But this has a nasty side effect: The assembly of the custom form is also removed and needs to be re-added.

image

The next step is to configure the web application in the IIS Manager: Open the IIS snapin and navigate to the owa virtual directory and then select the directory of the custom form:

image

Open the properties of the CustomForm directory configure a Web Applicaton by clicking on the “Create” button. Ensure that the application pool is MSExchangeOWAAppPool.

image

Next, ensure that the ASP.NET version of the application is set to 2.0:

image

Close the dialog and return to Visual Studio.

The custom form will not be loaded by Outlook Web Access until it finds a registry.xml describing a mapping from an item class to the custom forms. Here is the registry.xml from this example:

   1: <Registry xmlns="http://schemas.microsoft.com/exchange/2004/02/formsregistry.xsd" Name="PremiumExtensions" InheritsFrom="Premium" IsRichClient="true">
   2:   <Experience Name="Premium">
   3:     <Client Application="MSIE" MinimumVersion="6" Platform="Windows NT" />
   4:     <Client Application="MSIE" MinimumVersion="6" Platform="Windows 2000" />
   5:     <Client Application="MSIE" MinimumVersion="6" Platform="Windows 98; Win 9x 4.90" />
   6:     <ApplicationElement Name="Item">
   7:       <ElementClass Value="IPM.Note.CustomClass">
   8:         <Mapping Action="New" Form="https://w2k3x64/owa/forms/CustomForm/EditItem.aspx"/>
   9:         <Mapping Action="Open" Form="https://w2k3x64/owa/forms/CustomForm/EditItem.aspx"/>
  10:         <Mapping Action="Preview" Form="https://w2k3x64/owa/forms/CustomForm/ViewItem.aspx"/>
  11:         <Mapping Action="Reply" Form="https://w2k3x64/owa/forms/CustomForm/EditItem.aspx"/>
  12:         <Mapping Action="ReplyAll" Form="https://w2k3x64/owa/forms/CustomForm/EditItem.aspx"/>
  13:         <Mapping Action="Forward" Form="https://w2k3x64/owa/forms/CustomForm/EditItem.aspx"/>
  14:       </ElementClass>
  15:     </ApplicationElement>
  16:     <ApplicationElement Name="PreFormAction">
  17:       <ElementClass Value="IPM.Note.CustomClass">
  18:         <Mapping Action="Open" Form="Microsoft.Exchange.Clients.Owa.Premium.Controls.CustomFormRedirectPreFormAction,Microsoft.Exchange.Clients.Owa"/>
  19:         <Mapping Action="Preview" Form="Microsoft.Exchange.Clients.Owa.Premium.Controls.CustomFormRedirectPreFormAction,Microsoft.Exchange.Clients.Owa"/>
  20:         <Mapping Action="Reply" Form="Microsoft.Exchange.Clients.Owa.Premium.Controls.CustomFormRedirectPreFormAction,Microsoft.Exchange.Clients.Owa"/>
  21:         <Mapping Action="ReplyAll" Form="Microsoft.Exchange.Clients.Owa.Premium.Controls.CustomFormRedirectPreFormAction,Microsoft.Exchange.Clients.Owa"/>
  22:         <Mapping Action="Forward" Form="Microsoft.Exchange.Clients.Owa.Premium.Controls.CustomFormRedirectPreFormAction,Microsoft.Exchange.Clients.Owa"/>
  23:         <Mapping Action="New" Form="Microsoft.Exchange.Clients.Owa.Premium.Controls.CustomFormRedirectPreFormAction,Microsoft.Exchange.Clients.Owa"/>
  24:       </ElementClass>
  25:     </ApplicationElement>
  26:   </Experience>
  27: </Registry>

The mapping above is for the message class “IPM.Note.CustomClass”. It seems that it’s necessary to specify a full-qualified name for the address of the formulars – relative path will not work.

Now, add a reference to the Managed API and the System.DirectoryServices.AccountManagement. Create a new form named “ViewItem.aspx”.

For the sake of simplicity, just add this fragment to the page below the scriptmanager reference:

   1: <div>
   2:     <asp:Label runat="server" ID="Label1" Text="Subject: " />
   3:     <asp:Label runat="server" ID="SubjectLabel" Text="Subject" />
   4: </div>

In the code behind file, add the following code:

   1: using System;
   2: using System.DirectoryServices.AccountManagement;
   3: using System.Net;
   4: using System.Security.Principal;
   5: using System.Web;
   6: using System.Web.UI;
   7: using Microsoft.Exchange.WebServices.Data;
   8:  
   9: namespace CustomForm
  10: {
  11:     public partial class ViewItem : Page
  12:     {
  13:         static ViewItem()
  14:         {
  15:             ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, errors) => true;
  16:         }
  17:  
  18:         protected string OwaItemId
  19:         {
  20:             get { return HttpUtility.UrlEncode(Request["id"]); }
  21:         }
  22:  
  23:         protected string ContentClass
  24:         {
  25:             get { return Request["t"]; }
  26:         }
  27:  
  28:         protected string State
  29:         {
  30:             get { return Request["s"]; }
  31:         }
  32:  
  33:         protected string Action
  34:         {
  35:             get { return Request["a"]; }
  36:         }
  37:  
  38:         protected void Page_Load(object sender, EventArgs e)
  39:         {
  40:             Item item = LoadItem();
  41:             SubjectLabel.Text = item.Subject;
  42:         }
  43:  
  44:         private Item LoadItem()
  45:         {
  46:             using (((WindowsIdentity) HttpContext.Current.User.Identity).Impersonate())
  47:             {
  48:                 string emailAddress = UserPrincipal.Current.EmailAddress;
  49:                 var exchangeService = new ExchangeService(ExchangeVersion.Exchange2007_SP1)
  50:                                           {
  51:                                               Url = new Uri("https://localhost/ews/exchange.asmx"),
  52:                                               UseDefaultCredentials = true
  53:                                           };
  54:                 ServiceResponseCollection<ConvertIdResponse> convertIds =
  55:                     exchangeService.ConvertIds(new[] {new AlternateId(IdFormat.OwaId, OwaItemId, emailAddress)}, 
  56:                         IdFormat.EwsId);
  57:                 ServiceResponseCollection<GetItemResponse> items =
  58:                     exchangeService.BindToItems(new[] {new ItemId(((AlternateId) convertIds[0].ConvertedId).UniqueId)},
  59:                                                 new PropertySet(BasePropertySet.FirstClassProperties));
  60:                 return items[0].Item;
  61:             }
  62:         }
  63:     }
  64: }

In the static constructor, SSL certificate checking is effectively disabled. Since this example binds to the Exchange Server with the hostname "localhost”, certificate would fail regardless of the certificate used.

All the heavy lifting is done in the LoadItems method. In the first line the code impersonates the user logged on to OWA. The next step requires a prerequisite: The website gets the OWA id of the item. this id cannot directly be used with the Exchange Web Services (or the EWS Managed API, for that matter). The ExchangeService.ConvertIds method is used here. But that method requires the primary email address of the user the OWA id belongs to. For simple environments (read: the mail attribute of the user object in Active Directory contains the primary email address), the method used here can be used. The System.DirectoryServices.AccountManagement.UserPrincipal class was introduced with .NET 3.5 and offers a very simple way to get the required information. After the ID has been converted to an EWS id, the item can finally be loaded from the store. In this example, only the subject of the item is displayed on the form.

Finally, you need to restart the IIS – contrary to what to official documentation states, Outlook Web Access only scans for new custom forms on startup and not periodically. You need to keep this in mind when deploying your solution. After the IIS has been restarted, you can check wether the custom form has been loaded by taking a look at the EventLog. If the form was loaded successfully, you’ll find an entry similar to this one:

image

The form created here will be used to preview every item with the message class of IPM.Post.Custom.

The solution containing the relevant files for this example is attached to this post. The file has a digital signature to ensure it’s not modified.


Technorati:

Posted by Henning Krause on Friday, June 12, 2009 11:31 PM, last modified on Wednesday, December 8, 2010 11:35 AM
Permalink | Post RSSRSS comment feed

Comments (3) -

On 7/8/2009 8:51:15 AM Valdo Slovakia wrote:

Valdo

Nice article!
Can you please explain in more depth why is the tweaking in the web.config necessary and whether it affects the standard owa functionality? Only that information was not clear.
I have developed something similar, but in my case the application is not the part of owa but a different virtual directory.

On 7/8/2009 8:21:20 PM hkrause wrote:

hkrause

I wrote about the problems in a previous post (www.infinitec.de/.../...ostback-functionality.aspx). Basically, the Exchange module intercepts all calls made to the web applications and blocks some file types, like .ashx and .axd files. This breaks the standard ASPX page lifecycle.

On 12/8/2010 11:35:20 AM web designing United States wrote:

web designing

Can you please explain in more depth why is the tweaking in the web.config necessary and whether it affects the standard owa functionality? Only that information was not clear.
I have developed something similar, but in my case the application is not the part of owa but a different virtual directory.