VSTO

Build Office-Based Solutions Using WPF, WCF, And LINQ

Andrew Whitechapel

Code download available at:VSTONet2007_12.exe(419 KB)

This article is based on a prerelease version of Visual Studio 2008. All information herein is subject to change.

This article discusses:

  • How VSTO makes Office development more powerful
  • Using WCF, WPF, and LINQ in Office solutions
  • Easily adding advanced features to your Office apps
  • Building services the easy way
This article uses the following technologies:
VSTO, WPF, WCF, LINQ

Contents

The Sample Solution
Building the WCF Service
The WPF UserControl
Connecting the WPF UserControl to VSTO
Consuming the WCF Service in the VSTO Solution
Security
Using LINQ, XLinq, and Lambda Expressions in VSTO
The Future of Office-Based Apps

Visual Studio® 2008 introduces an array of new features aimed at a wide range of customer solution types. Now you can build a Visual Studio Tools for Office (VSTO) solution that uses Windows® Presentation Foundation (WPF), Windows Communication Foundation (WCF) and language integrated query expressions (LINQ), which I'll show you shortly.

The new technologies provide opportunities for building exciting solutions with behavior that was previously difficult or impossible to achieve. For example, though Microsoft® Office Excel® 2007 has powerful charting features, you can build an even richer experience when you combine the Excel calculation engine with enhanced UI and data visualization using WPF 3D animated graphics.

As Office evolves into a true development platform, office-based solutions are becoming increasingly sophisticated, less document-focused, and more loosely coupled. The need for a support framework and a design-time toolset for building service-oriented solutions that connect a rich Office client with powerful server-side functionality and remote data is neatly filled by WCF. Visual Studio 2008 provides a simple GUI wizard that lets you consume WCF services without having to worry about service metadata, protocols, or XML configuration.

LINQ enables developers to build more intuitive, greatly simplified code for querying data. One of the LINQ features Office developers will especially appreciate is the use of extension methods to support the traditional Office object model pattern of methods that have optional or explicit reference parameters.

With Visual Studio 2008, you can build a solution that incorporates the native capabilities of an Office client application combined with the sophisticated UI capabilities of WPF that's connected to remote data and services via WCF and uses the RAD features of LINQ to manipulate that data.

The Sample Solution

The solution I'll be building is deliberately simple so that I can focus on the individual technologies and integration issues rather than the business functionality. Figure 1 gives you an idea of the user experience. There are two processes involved: a standalone WCF service hosted in a console application and a VSTO add-in running in Word 2007. The add-in offers a custom task pane, which includes a WPF custom control. As the user clicks the buttons in the control, the add-in responds by calling into the WCF service to fetch some XML data. The add-in then processes this data in a variety of ways, using LINQ and XLinq, including lambda expressions and expression trees, and formats text that it then inserts into the active document. If you want to work through the sample solution yourself, download and install the Visual Studio 2008 Beta 2 release, available at msdn2.microsoft.com/vstudio/aa700831.

Figure 1 Runtime Behavior of the Sample Solution

Figure 1** Runtime Behavior of the Sample Solution **(Click the image for a larger view)

The point of this sample is not to show the specific runtime functionality; instead, the main idea is to demonstrate that all the new Visual Studio 2008 technologies (WPF, WCF, and LINQ) work seamlessly with Office solutions. Another goal of this sample solution is to provide food for thought for building exciting new user experiences.

Figure 2 illustrates the architecture of the solution. Using WCF and LINQ within a VSTO application offers a lightweight approach and is no different than using these features in a Windows Forms or console application. Using WPF is more interesting, though, as you'll see.

Figure 2 Architecture of the Sample Solution

Figure 2** Architecture of the Sample Solution **(Click the image for a larger view)

One aspect of VSTO is the ability to build solutions that use managed controls within native Office UI windows. VSTO supports placing managed (Windows Forms) controls in arbitrary Windows Forms dialogs, in app-level custom task panes, in the document actions pane, in Outlook® custom form regions, and on the surface of a Word or Excel document. The Microsoft .NET Framework 3.5 (shipping with Visual Studio 2008) includes the WindowsFormsIntegration.dll, which contains the Windows Forms Integration ElementHost class. This provides a bridge between Windows Forms and WPF. Thus, VSTO also supports placing WPF controls in all the places you can put Windows Forms controls.

One of the interesting design decisions you get to make here is where to handle events. For example, when the user clicks one of the WPF Button controls, you can handle this at multiple levels: in the WPF UserControl, in the Windows Forms UserControl, or in the add-in. Where you handle the event, and whether you bubble it up further, depends a lot on how reusable you want the various controls to be.

Building the WCF Service

The way I went about starting to build the project is I began with a simple console application called ImageServiceHost. My next step was to add a WCF Service item to the project, which I decided to call ImageService. It is important for you to understand that adding a Windows Communication Foundation Service to the project in Visual Studio causes starter code to automatically be generated for the service, including an interface that represents the service contract and class that implements that interface. Configuration entries that specify behaviors and endpoints for the service are provided in a standard app.config file.

I had to change the service contract and its implementation in order to remove the wizard-generated DoWork method and replace it with a new method, called GetDefinition, that takes a keyword string and returns a string of XML data:

[ServiceContract()] public interface IImageService { [OperationContract] string GetDefinition(string myValue); }

I could get the XML data from anywhere; realistically, the WCF service would be running on a server and could perhaps source its data from a server-side database or some line-of-business (LOB) system such as SAP or Siebel. In my sample, however, I will run the service on the local machine and source the data from a static XML string resource.

The XML data source I'll create will contain a number of item elements, each of which represents a simple mapping of a keyword to a definition:

<Dictionary> <Item> <Key>Frangipani</Key> <Definition>Some description goes here.</Definition> </Item> <Item> <Key>Toucan</Key> <Definition>...etc</Definition> </Item> </Dictionary>

If I add this XML data file to the project resources, I can implement the service constructor to load the XML data from resources. Then, I can implement the GetDefinition contract method to return the item that matches the keyword the user requested. At the same time, I echo the return string to the console window. Note that the sample code is simplified and does not include the exception handling you would normally incorporate:

public string GetDefinition(string keyword) { XmlDocument xmlDoc = new XmlDocument(); xmlDoc.LoadXml(Properties.Resources.ImageData); String xPath = String.Format( "descendant::Item[Key='{0}']", keyword); XmlNode node = xmlDoc.DocumentElement.SelectSingleNode(xPath); String nodeXml = node.OuterXml; Console.WriteLine(nodeXml); return nodeXml; }

Next, the console application simply needs to start and stop the service.

static void Main(string[] args) { ServiceHost s = new ServiceHost(typeof(ImageService)); s.Open(); Console.WriteLine("press ENTER to stop the service"); Console.ReadLine(); s.Close(); }

With the service now defined, implemented, and configured, I can build this piece of the solution and start it running. I've set it up so that the service runs in the console application, waiting for incoming calls, until the user presses Enter in the console window.

The WPF UserControl

There are two obvious ways you can use WPF in an application: to enhance the user interface and to provide sophisticated data visualization. My example focuses on the rich UI capabilities. The important point here is how easily you can incorporate WPF controls in VSTO solutions. I could demonstrate that with an extremely simple WPF control—perhaps a grid with a button on it—but it would be a shame not to take advantage of the sophisticated graphics capabilities of WPF, so I'll use a custom WPF UserControl that provides a fish-eye animation behavior. I've based it on the HyperBar sample for Microsoft Expression BlendTM (available at blogs.msdn.com/expression/articles/516599.aspx). It's also based on Paul Tallett's FishEyePanel described at codeproject.com/WPF/Panels.asp. The major difference I'll introduce is to put the WPF control in a non-WPF window so you can see it hosted in a native Office window.

To begin, I'll create a Windows WPF UserControl Library project—this produces some starter XAML and the corresponding C# codebehind. The first thing to do is change the UserControl class name from UserControl1 to something more meaningful—FishEyeControl. This FishEyeControl will contain a collection of buttons managed by a custom panel, named FishEyePanel, which derives from System.Windows.Controls.Panel. In FishEyePanel, I set up event handlers for three mouse events (MouseMove, MouseEnter, MouseLeave) so I can invalidate the panel and cause it to be re-rendered whenever the user moves the mouse over it. Here is one of the mouse event handlers:

public class FishEyePanel : Panel { public FishEyePanel() { this.MouseMove += new MouseEventHandler(FishEyePanel_MouseMove); } private void FishEyePanel_MouseMove(object sender, MouseEventArgs e) { this.InvalidateArrange(); } }

To connect the FishEyePanel to the FishEyeControl, I had to set up the ItemsControl property on the FishEyeControl, like so:

<UserControl x:Class="WPFFishEye.FishEyeControl" xmlns:uc="clr-namespace:WPFFishEye"> <Grid> <ItemsControl> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <uc:FishEyePanel/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> </ItemsControl> </Grid> </UserControl>

Next, I set up some data resources for the panel. Instead of trying to set up a more realistic Web service or LOB data source, I'll just use local files as resources. In this sample, I'll use a simple list of .jpg files as the images on the buttons. These JPGs are in external files, so I need a little code to take the simple file name (such as Frangipani.jpg) and convert it to a fully qualified path that will be valid at run time. For this, I can define an implementation of IValueConverter. This is a common technique for performing custom conversion during data binding. The IValueConverter interface defines two methods, but I only care about one—Convert. The data-binding engine calls this method when it propagates a value from the binding source to the binding target. In my implementation, I simply use the CodeBase of the current assembly and prepend this as the path to the image file name.

public class ImagePathConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { string path = Assembly.GetExecutingAssembly().CodeBase.Substring ("file:///".Length); path = Path.GetDirectoryName(path) + "\\Images\\"; return string.Format("{0}{1}", path, (string)value); } }

Then, back in the FishEyeControl.xaml, I connect these pieces in a ResourceDictionary for the control and use the ResourceDictionary in the ItemsControl (see Figure 3). Note that the DataTemplate for the items within the panel is set to be a Button with an Image, and the Image uses the ImagePathConverter during data binding.

Figure 3 Template for the Databound Button Controls

<UserControl.Resources> <ResourceDictionary> <XmlDataProvider x:Key="ItemImages" XPath="ItemImages/ItemImage"> <x:XData> <ItemImages > <ItemImage Image="Frangipani.jpg" Width="50"/> <ItemImage Image="Toucan.jpg" Width="50"/> <!-- etc --> </ItemImages> </x:XData> </XmlDataProvider> <uc:ImagePathConverter x:Key="ImagePathConverter" /> </ResourceDictionary> </UserControl.Resources> <Grid> <ItemsControl DataContext="{Binding Source={StaticResource ItemImages}}" ItemsSource="{Binding}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <uc:FishEyePanel/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemContainerStyle> <Style TargetType="{x:Type ContentPresenter}"> <Setter Property="ContentTemplate"> <DataTemplate> <Button Name="b1" Click="buttonClick"> <Image Source="{Binding Converter={StaticResource ImagePathConverter}, XPath=@Image}"/> </Button> </DataTemplate> </Setter> </Style> </ItemsControl.ItemContainerStyle> </ItemsControl> </Grid>

This definition also specifies the buttonClick event handler for each Button. Although I could handle the event within the WPF control itself, as well as within the hosting Windows Forms control or the add-in, I'll handle it only in the WPF control and the add-in. I implement the event handlers in FishEyeControl.xaml.cs by extracting the button Image name and firing the event again. The VSTO add-in will react to the event by invoking the WCF service, based on the Image name (see Figure 4).

Figure 4 Handling the FishEye Click Event

public partial class FishEyeControl : System.Windows.Controls.UserControl { public event FishEyeEvent FishEyeClickEvent; private void buttonClick(object sender, RoutedEventArgs e) { Button buttonSender = (Button)sender; Image buttonImage = (Image)buttonSender.Content; ImageSource imageSource = buttonImage.Source; String imageName = imageSource.ToString(); int lastSlash = imageName.LastIndexOf('/') + 1; String buttonName = imageName.Substring( lastSlash, imageName.Length - lastSlash - ".jpg".Length); FishEyeEventArgs fe = new FishEyeEventArgs(buttonName); if (FishEyeClickEvent != null) { FishEyeClickEvent(sender, fe); } } }

Next I define a custom EventArgs type so that when I refire the event I can send the simple name of the button Image out to the listener as shown in the following,

public delegate void FishEyeEvent(object source, FishEyeEventArgs e); public class FishEyeEventArgs : EventArgs { public String ButtonName; public FishEyeEventArgs(String name) { ButtonName = name; } }

Recall that I'm invalidating the FishEyePanel when the user moves the mouse over it. I also want to control how the rendering behaves so that I can provide the fish-eye animation behavior. To take control of rendering, I must implement two overrides, MeasureOverride and LayoutOverride, which implement a two-pass layout system. During the measurement pass, the parent (FishEyeControl) calls the child (FishEyePanel) to find out how much space the child needs. The standard behavior here is for the child control to query its own children to find out how much space they need and then pass the result back up to the parent. WPF uses a model where controls can be nested almost indefinitely, so in this context, the entire tree is queried for its space requirements and the information is passed back up to the ultimate parent. In the layout pass, a similar recursion takes place, where the parent decides on the sizing of its children and passes this down, and each child in turn passes the information down to its children. This calls the ArrangeOverride method, where I can tell the children their size and lay them out.

In my implementation of MeasureOverride, I use the UIElement.DesiredSize property to try to give the children all the space they need. In ArrangeOverride, I add scale transforms and translate transforms to every child, and calculate how wide they need to be. In this sample, I scale the child button that is directly under the mouse to be larger than the others. The two buttons on either side of this are smaller, but still larger than all the other buttons. The remaining buttons are equal in size across the space left over. For further details, you can examine the code supplied in the download that accompanies this article on the MSDN® Magazine Web site (msdn.microsoft.com/msdnmag/code07.aspx). The net result is that with each mouse movement, I adjust the size of the buttons to provide the familiar fish-eye effect.

Connecting the WPF UserControl to VSTO

The basic VSTO solution is a Word 2007 add-in called WordImageSelecter. Once the initial add-in project is created, I can add the WPF UserControl project to the solution. This allows me to take advantage of the enhanced design-time support in Visual Studio 2008 for integrating WPF controls within Windows Forms projects.

The next step is to create a simple Windows Forms UserControl within the add-in project. This will eventually host the custom WPF UserControl. In Visual Studio 2008, any WPF UserControl in any project in your solution will show up in the ToolBox, so you can drag and drop it directly onto the Windows Forms UserControl design surface, as shown in Figure 5.

Figure 5 WPF Control Windows Forms Design Surface

Figure 5** WPF Control Windows Forms Design Surface **

This action generates all the code needed to host the WPF control in the Windows Forms control, specifically via an ElementHost object. The ElementHost is designed for integrating Windows Forms and WPF. In this example, the Windows Forms UserControl hosts the ElementHost, which in turn hosts the WPF UserControl. This also adds the required references to the custom WPFFishEye.dll, as well as to the standard PresentationCore, PresentationFramework, UIAutomationProvider, WindowsBase, and WindowsFormsIntegration DLLs. The Windows Forms control is very simple—its only purpose is to host the WPF control, so I don't need to build in any significant functionality. Note that I expose the hosted WPF FishEyeControl as a property so that I can sink the FishEyeEvent exposed by the FishEyeControl (as shown in Figure 6).

Figure 6 Initialize the Control

private System.Windows.Forms.Integration.ElementHost elementHost; private WPFFishEye.FishEyeControl fishEye; public WPFFishEye.FishEyeControl FishEye { get { return fishEye; } } private void InitializeComponent() { this.elementHost = new System.Windows.Forms.Integration.ElementHost(); this.fishEye = new WPFFishEye.FishEyeControl(); this.elementHost.Dock = System.Windows.Forms.DockStyle.Fill; this.elementHost.Child = this.fishEyeControl; this.Controls.Add(this.elementHost); }

Now, I must connect the Windows Forms control to a custom task pane in Word. To do this, I need to implement the ThisAddIn_Startup method to create the custom task pane, specifying the Windows Forms control as the control to host, and sink the custom FishEyeClickEvent:

private WFControl wfControl; private void ThisAddIn_Startup(object sender, System.EventArgs e) { Microsoft.Office.Tools.CustomTaskPane taskPane= this.CustomTaskPanes.Add(new WFControl(), "ImageSelecter"); wfControl = (WFControl)taskPane.Control; wfControl.FishEye.FishEyeClickEvent += new FishEyeEvent(FishEye_FishEyeClickEvent); taskPane.Visible = true; }

In the implementation of the FishEyeClickEvent handler, for now I can simply display a message box. If you're following along, you could test the add-in at this point because the custom task pane and WPF control pieces are complete:

private void FishEye_FishEyeClickEvent(object source, FishEyeEventArgs e) { System.Windows.Forms.MessageBox.Show(e.ButtonName); }

You can use this same general technique for placing WPF controls anywhere in VSTO solutions you are allowed to put Windows Forms controls—in arbitrary Windows Forms dialogs, in app-level custom task panes, in the document actions pane, in Outlook custom form regions, and on the surface of a Word or Excel document. Note that for a Word or Excel document, you can use the regular design-time support for building a Windows Forms control that incorporates your WPF control, but then you have to stop short of actually putting the control on the document surface at design time, because the special VSTO Excel and Word designers don't support it.

That said, programmatically adding a hosted WPF control to a VSTO document solution is really no effort at all. The VSTO runtime infrastructure already supports any arbitrary Windows Forms control, including controls that host WPF controls. The runtime is already in place and has been since Visual Studio 2005. So, the only programmatic task you have left is to instantiate the outer Windows Forms control (that's hosting your WPF control, via ElementHost) and add it to the solution.

private void Sheet1_Startup(object sender, System.EventArgs e) { string controlName = "MyWfControl"; WFControl wfControl = new WFControl(); wfControl.Tag = Controls.AddControl(wfControl, 50,50, 208,250, controlName); wfControl.Name = controlName; }

Given the nature of WPF hosting within VSTO solutions, it should be clear that one of the limitations is that you cannot use Windows Vista AeroTM Glass in the normal way. That is, if your WPF control uses Aero Glass, it will only be transparent to the underlying Windows Forms hosting control, not to the document surface or native Office window. Of course, you can mitigate this by specifying a background image as part of your control; then any Aero Glass controls you lay on top can be transparent to that image.

Consuming the WCF Service in the VSTO Solution

There are several ways to develop the necessary client-side code to consume a WCF service. You can generate metadata exchange information and client proxy code from the compiled service assembly or from the running service itself. You can execute svcutil.exe manually to generate the code, or you can use the Add Service Reference wizard in Visual Studio 2008.

With Visual Studio 2005, you have to use svcutil manually. To do this, first start the service, then open a command window and navigate to the client project folder. Then run svcutil to generate the client-side proxy code. An example command line might look like this:

"C:\Program Files\Microsoft SDKs\Windows\v6.0\Bin\svcutil" /language:C# /config:app.config https://localhost:8080/ImageServiceHost/ImageService/mex /n:*,WordImageSelecter

This specifies the URL for the running service along with the metadata exchange endpoint address. It also indicates that C# should be used as the target language, that the configuration information should be output to a file named app.config, and that WordImageSelecter should be the namespace (in this sample, this is the namespace for the add-in project).

Once svcutil has generated the C# proxy code and the app.config, they must be added to the add-in project. Also, a reference to System.ServiceModel.dll must be added.

Visual Studio 2008 provides a graphical wizard you can use instead of running svcutil manually. To use the wizard, start the service, then right-click the add-in project in Solution Explorer and select the Add Service Reference option from the context menu. In the Add Service Reference dialog, enter the service URL and the target namespace, then click Go. This fetches the service contract and configuration information from the running service and populates the Services and Operations lists, as shown in Figure 7. Click OK to generate the proxy code.

Figure 7 Add Service Reference Dialog

Figure 7** Add Service Reference Dialog **(Click the image for a larger view)

Now you can add a proxy class field to the add-in class, initialize it in ThisAddIn_Startup, and invoke the WCF Service GetDefinition method through the proxy. In the FishEyeClickEvent handler, replace the message box with a call to GetDefinition, parse the returned XML to extract the Key and Definition, and insert these into the Word document. Note that after inserting the Key text, I move to the end of the insertion before adding the Definition text—this is so that I don't overwrite the previous selection (see Figure 8).

Figure 8 Using the WCF Service Client-Side Proxy

internal ImageServiceClient serviceClient; private void ThisAddIn_Startup(object sender, System.EventArgs e) { // Connect to the WCF service. serviceClient = new ImageServiceClient(); // (previously discussed code omitted for brevity). } private void FishEye_FishEyeClickEvent(object source, FishEyeEventArgs e) { //System.Windows.Forms.MessageBox.Show(e.ButtonName); // Invoke the WCF Service and parse the returned XML. XmlDocument xmlDoc = new XmlDocument(); xmlDoc.LoadXml( Globals.ThisAddIn.serviceClient.GetDefinition(e.ButtonName)); String keyText = xmlDoc.SelectSingleNode( "descendant::Item/Key").InnerText; String definitionText = xmlDoc.SelectSingleNode( "descendant::Item/Definition").InnerText; // Insert the Key text into the Word document. this.Application.Selection.InsertAfter( String.Format("{0}{1}{2}", Environment.NewLine, keyText, Environment.NewLine)); object titleStyle = Word.WdBuiltinStyle.wdStyleTitle; this.Application.Selection.set_Style(ref titleStyle); // Move to the end of the insertion. object gotoItem = Word.WdGoToItem.wdGoToLine; object gotoDirection = Word.WdGoToDirection.wdGoToLast; this.Application.Selection.GoTo( ref gotoItem, ref gotoDirection, ref missing, ref missing); // Insert the Definition text into the Word document. Word.Range insertionStart = this.Application.Selection.Range; this.Application.Selection.InsertAfter( String.Format("{0}{1}", definitionText, Environment.NewLine)); object headingStyle2 = Word.WdBuiltinStyle.wdStyleHeading2; this.Application.Selection.set_Style(ref headingStyle2); }

Security

On your development machine, security is set up for the add-in solution when you build it. When you deploy the solution, security will be set up as part of the publish operation. Office 2007 introduced new security features and streamlined the way security works for macros, ActiveX controls, and add-ins. All installed add-ins are now accessible through the Office Trust Center. VSTO also made very significant changes to its security model for Visual Studio 2008. The model no longer relies on the version-specific code access security (CAS) repository and is instead integrated into the ClickOnce model. VSTO also layers on additional security for both add-ins and document solutions that intelligently uses digital certificates and/or a registry-based store of the user's trust decisions. VSTO solutions always require FullTrust because they interop with unmanaged code (the Office host), and this requirement extends to the WPF and WCF pieces that form part of the solution on the client. With Visual Studio 2005 and with projects for Office 2003 in Visual Studio 2008, you need to set up security specifically for the client-side WCF .config file (the app.config for the WordImageSelecter.dll). This is because the .config file is an entry point into the code and must be explicitly trusted in CAS policy. When building projects for Office 2007 in Visual Studio 2008, this is no longer necessary because trust is granted to a solution, including any dependent assemblies and other ancillary files that form part of the solution. At this point, you can test the add-in again now that both the WPF and WCF pieces are in place.

Using LINQ, XLinq, and Lambda Expressions in VSTO

The final piece to explore is the use of LINQ in the context of a VSTO solution. With Visual Studio 2008, most projects automatically include references to System.Core and System.Xml.Linq, where most of the LINQ classes are defined. LINQ is a set of language extensions that allows you to perform SQL-like queries on an open-ended range of data sources in a type-safe manner. In the sample add-in, all the LINQ work is done in the FishEyeClickEvent handler, where I perform some simple analyses on the XML data returned from the WCF service. Although the example is simple, it illustrates how powerful the feature is.

To begin with, I can use XLinq to extract the Key and Definition from the XML string, instead of the more cumbersome XPath I used initially:

XElement item = XElement.Parse( Globals.ThisAddIn.serviceClient.GetDefinition(e.ButtonName)); String keyText = (string)item.Element("Key"); String definitionText = (string)item.Element("Definition");

Another feature of LINQ is implicitly typed local variables. Type inference is invoked by using the var keyword to instruct the compiler to infer the type of a variable from the expression in which it is used. Here, we'll use this technique to get the count of characters in the Definition text:

var chars = from c in definitionText select c; int charCount = chars.Count();

Next, we can use a lambda expression to get the number of four-letter words in the Definition text. A lambda expression is similar to an anonymous delegate:

List<string> definitionWords = new List<string>(definitionText.Split(new char[] { ' ' })); var fourLetterWords = definitionWords.FindAll( x => (x.Length == 4));

Besides using lambda expressions in place of anonymous delegates, you can also build an expression tree from lambda expressions. This allows you to treat code (the lambda expression) as if it were data. In the following example, I take the lambda expression y => (y.Length == 4) and map it to an Expression object so that I can output a LISP-style translation of the expression. In this example, this expression will produce the output "Equal Length 4":

Expression<Func<string, bool>> f = y => (y.Length == 4); BinaryExpression be = (BinaryExpression)f.Body; MemberExpression me = (MemberExpression)be.Left; ConstantExpression ce = (ConstantExpression)be.Right; this.Application.Selection.InsertAfter( String.Format("{0} chars, {1} four-letter words [{2} {3} {4}] {5}", charCount, fourLetterWords.Count, be.NodeType, me.Member.Name, ce.Value, Environment.NewLine));

The last feature to use is extension methods. Many people complain that developing code in C# for Office is painful because many methods in the Office object models take a large number of optional parameters and (especially with Word), in many cases, parameters must be passed explicitly by reference. This is not a problem with Visual Basic because Visual Basic takes care of the translation for you and hides the underlying complexity. For example, the code listed earlier to move the selection to the end of the previous insertion required calling the GoTo method. This method takes four parameters, all explicitly passed by reference, and includes two that are optional.

To mitigate this problem, I can write an extension method, which is a static method in a custom class. I can make it look like it extends one of the Office interfaces by specifying that interface as the type of the first parameter, which must also use the this keyword. This extension method can internally use the standard Office GoTo method:

public static class RangeExtender { public static void GoTo(this Word.Application wordApplication, Word.WdGoToItem gotoItem, Word.WdGoToDirection gotoDirection) { object what = gotoItem; object which = gotoDirection; object missing = Type.Missing; wordApplication.Selection.GoTo( ref what, ref which, ref missing, ref missing); } }

I can then use this extension method in place of the one defined by the Office object model:

this.Application.GoTo( Word.WdGoToItem.wdGoToLine, Word.WdGoToDirection.wdGoToLast);

The Future of Office-Based Apps

As more and more developers build Office-based solutions with managed code, it becomes increasingly important that features introduced in the .NET Framework and in new releases of Visual Studio work seamlessly within an Office context. In this article, you've seen how you can build a VSTO solution that uses WPF, WCF, and LINQ to utilize services from within an Office application. The example solution uses a simple WCF service to retrieve static data, parses the data with LINQ, and uses WPF to provide an enhanced UI. A more typical real-world solution would likely use a range of WCF services to work with multiple server-side LOB systems, and could use WPF to provide sophisticated data visualization in addition to UI elements. LINQ could be used either on the server, in the middle tier, or on the client to build intuitive, maintainable functionality for data querying and manipulation.

It is clear that the designer enhancements for WPF integration with Windows Forms and compiler enhancements for language-integrated querying make Visual Studio 2008 a compelling addition to your enterprise development toolbox.

Andrew Whitechapel spent many years as an architect consultant building enterprise solutions for a wide range of customers, and he is now Program Manager Technical Lead for the VSTO team at Microsoft.