Welcome to X4U
Minimize 

Hello visitor.

This is the personal homepage of Helmut Obertanner.
I'm a computer freak and electronic enthusiast and I live in Munich in Germany - the hometown of the famous Oktoberfest.
Why the name X4U? In my case it's a synonym for everything is possible.
I'm an experienced full time C# developer and doing so since .Net Framework 1.0.
As technical enthusiast I started Office development using .Net technologies.
As I searched answers and found help in the Internet I decided to share my knowledge in newsgroups and forums too. For participation, helping other developers, writing how-to's and articles and doing consulting and trainings I received the Microsoft MVP Award for the 3rd time.

As Outlook C# specialist I founded the website Outlooksharp.de
Beside software development, I'm building electronic devices for customers.
Prototypes with SMD PCBs and complete electronic devices with USB connections are my typical portfolio.
You got an Idea and have no plan how to put it to work - give me a call and I tell you if I can help.
A complete solution starts from defining the requirements, design the schematic and PCB, build a functional prototype, program the firmware, program the windows software, design and build a housing prototype.
Usually I work with very cheap PIC 18F Microcontrollers, these multifunctional devices are programmed in C language. 

I'm located in Germany with satisfied customers around the globe.
Enjoy the content of my site and feel free to give me your feedback.
You're welcome.
X4U Blog
 
Dec29

Written by:Helmut Obertanner
Tue, 29 Dec 2009 19:16:18 GMT 

Recently I had a customer request helping forcing business rules when saving Outlook Contact Items. Luckily I programmed a sample on how to do this some time ago and published it on Outlookcode.com. While this sample was programmed using a shared Add-in I decided to give it a refresh and update this sample using VSTO.

Inspector Wrapper

It all begins with an Inspector Wrapper template. I blogged about it and you can read it here: http://www.outlooksharp.de/Home/tabid/36/EntryId/44/Outlook-Inspector-Wrapper-explained.aspx.

The base for this code sample is to handle each Outlook Item individually. When an Item is opened within Outlook it is monitored by the Inspector. With the Wrapper code it is easy to handle all the Inspectors gracefully. The Item behind the Inspector is exposed by the Inspector.CurrentItem property. In this scenario we are enforcing business rules for Contacts.
A ContactItem exposes some Events that can be used to implement a small Framework that checks if all rules are met before a contact form is closed, or when someone is going to save the contact. We need to monitor the write and the close events of all contact items. The write and the close events of the ContactItem have a Cancel Property that is passed by reference. When setting the Cancel property to true, the operation is interrupted. First create a new Outlook Add-in solution.

Picture 1: Create an Outlook Add-in

After creating the Add-in go on and implement the Inspector Wrapper code to handle all Items individually. The base class called InspectorWrapper.cs implements all Inspector events and handles them individually. If you want to handle ContactItems you need to derive a class of it and implement the code to handle ContactItems events and businesslogic here.
Below is the code for the InspectorWrapper base class:

///<summary>

/// Eventhandler used to correctly clean up resources

///</summary>

///<param name="id">The unique id of the Inspector instance</param>

internaldelegatevoidInspectorWrapperClosedEventHandler(Guid id);

 

///<summary>

/// The base class for all InspectorWrappers

///</summary>

internalabstractclassInspectorWrapper {

 

///<summary>

/// Event notifier for the InspectorWrapper.Closed event.

/// Is raised when an Inspector has been closed.

///</summary>

publiceventInspectorWrapperClosedEventHandler Closed;

 

///<summary>

/// The unique Id the identifies the Inspector Window

///</summary>

publicGuid Id { get; privateset; }

 

///<summary>

/// The Outlook Inspector Instance

///</summary>

public Outlook.Inspector Inspector { get; privateset; }

 

///<summary>

/// .ctor

///</summary>

///<param name="inspector">The Outlook Inspector instance that should be handled</param>

public InspectorWrapper(Outlook.Inspector inspector) {

Id = Guid.NewGuid();

Inspector = inspector;

// register for Inspector events here

((Outlook.InspectorEvents_10_Event)Inspector).Close += new Outlook.InspectorEvents_10_CloseEventHandler(Inspector_Close);

((Outlook.InspectorEvents_10_Event)Inspector).Activate += new Outlook.InspectorEvents_10_ActivateEventHandler(Activate);

((Outlook.InspectorEvents_10_Event)Inspector).Deactivate += new Outlook.InspectorEvents_10_DeactivateEventHandler(Deactivate);

((Outlook.InspectorEvents_10_Event)Inspector).BeforeMaximize += new Outlook.InspectorEvents_10_BeforeMaximizeEventHandler(BeforeMaximize);

((Outlook.InspectorEvents_10_Event)Inspector).BeforeMinimize += new Outlook.InspectorEvents_10_BeforeMinimizeEventHandler(BeforeMinimize);

((Outlook.InspectorEvents_10_Event)Inspector).BeforeMove += new Outlook.InspectorEvents_10_BeforeMoveEventHandler(BeforeMove);

((Outlook.InspectorEvents_10_Event)Inspector).BeforeSize += new Outlook.InspectorEvents_10_BeforeSizeEventHandler(BeforeSize);

// Only Outlook 2007((Outlook.InspectorEvents_10_Event)Inspector).PageChange += new Outlook.InspectorEvents_10_PageChangeEventHandler(PageChange);

 

// Initialize is called to give the derived Wrappers a chance to do initialization

Initialize();

}

 

///<summary>

/// Eventhandler for the Inspector close event

///</summary>

privatevoid Inspector_Close() {

// call the Close Method - the derived classes can implement cleanup code

// by overriding the Close method

Close();

// unregister Inspector events

((Outlook.InspectorEvents_10_Event)Inspector).Close -= new Outlook.InspectorEvents_10_CloseEventHandler(Inspector_Close);

((Outlook.InspectorEvents_10_Event)Inspector).Activate -= new Outlook.InspectorEvents_10_ActivateEventHandler(Activate);

((Outlook.InspectorEvents_10_Event)Inspector).Deactivate -= new Outlook.InspectorEvents_10_DeactivateEventHandler(Deactivate);

((Outlook.InspectorEvents_10_Event)Inspector).BeforeMaximize -= new Outlook.InspectorEvents_10_BeforeMaximizeEventHandler(BeforeMaximize);

((Outlook.InspectorEvents_10_Event)Inspector).BeforeMinimize -= new Outlook.InspectorEvents_10_BeforeMinimizeEventHandler(BeforeMinimize);

((Outlook.InspectorEvents_10_Event)Inspector).BeforeMove -= new Outlook.InspectorEvents_10_BeforeMoveEventHandler(BeforeMove);

((Outlook.InspectorEvents_10_Event)Inspector).BeforeSize -= new Outlook.InspectorEvents_10_BeforeSizeEventHandler(BeforeSize);

// Only Outlook 2007((Outlook.InspectorEvents_10_Event)Inspector).PageChange -= new Outlook.InspectorEvents_10_PageChangeEventHandler(PageChange);

// clean up resources and do a GC.Collect();

Inspector = null;

GC.Collect();

GC.WaitForPendingFinalizers();

// raise the Close event.

if (Closed != null) Closed(Id);

}

 

///<summary>

/// Method is called after the internal initialization of the Wrapper

///</summary>

protectedvirtualvoid Initialize() { }

 

///<summary>

/// Method gets called when another Page of the Inspector has been selected

///</summary>

///<param name="ActivePageName">The active page name by reference</param>

protectedvirtualvoid PageChange(refstring ActivePageName) { }

 

///<summary>

/// Method gets called before the Inspector is resized

///</summary>

///<param name="Cancel">To prevent resizing set Cancel to true</param>

protectedvirtualvoid BeforeSize(refbool Cancel) { }

 

///<summary>

/// Method gets called before the Inspector is moved around

///</summary>

///<param name="Cancel">To prevent moving set Cancel to true</param>

protectedvirtualvoid BeforeMove(refbool Cancel) { }

 

///<summary>

/// Method gets called before the Inspector is minimized

///</summary>

///<param name="Cancel">To prevent minimizing set Cancel to true</param>

protectedvirtualvoid BeforeMinimize(refbool Cancel) { }

 

///<summary>

/// Method gets called before the Inspector is maximized

///</summary>

///<param name="Cancel">To prevent maximizing set Cancel to true</param>

protectedvirtualvoid BeforeMaximize(refbool Cancel) { }

 

///<summary>

/// Method gets called when the Inspector is deactivated

///</summary>

protectedvirtualvoid Deactivate() { }

 

///<summary>

/// Method gets called when the Inspector is activated

///</summary>

protectedvirtualvoid Activate() { }

 

///<summary>

/// Derived classes can do a cleanup by overriding this method.

///</summary>

protectedvirtualvoid Close() { }

 

///<summary>

/// This Fabric method returns a specific InspectorWrapper or null if not handled.

///</summary>

///<param name="inspector">The Outlook Inspector instance</param>

///<returns>Returns the specific Wrapper or null</returns>

publicstaticInspectorWrapper GetWrapperFor(Outlook.Inspector inspector) {

 

// retrieve the message class using late binding

string messageClass = (string)inspector.CurrentItem.GetType().InvokeMember("MessageClass", BindingFlags.GetProperty, null, inspector.CurrentItem, null);

 

// depending on a messageclass you can instantiate different Wrappers

// explicitely for a given MessageClass

// using a switch statement

switch (messageClass) {

case"IPM.Contact":

returnnewContactItemWrapper(inspector);

case"IPM.Journal":

returnnewContactItemWrapper(inspector);

case"IPM.Note":

returnnewMailItemWrapper(inspector);

case"IPM.Post":

returnnewPostItemWrapper(inspector);

case"IPM.Task":

returnnewTaskItemWrapper(inspector);

}

 

// or check if the messageclass begins with a specific fragment

//if (messageClass.StartsWith ("IPM.Contact.XXXX")){

// return new CustomItemWrapper(inspector);

//}

 

// or check the interface type of the Item

if (inspector.CurrentItem is Outlook.AppointmentItem) {

returnnewAppointmentItemWrapper(inspector);

}

 

// or check the interface type of the Item

if (inspector.CurrentItem is Outlook.ContactItem) {

returnnewContactItemWrapper(inspector);

}

 

// no wrapper found

returnnull;

}

}

 

Next you can see the ContactItemWrapper class. It is derived of the InspectorWrapper and used to handle all contacts in your application. Nothing fancy. How can you enforce business rules there? If you study the code you can see that in the Initialize method we register for the open, write and close events of the ContactItem. In C# the close event needs a special syntax, because there's already a close method for the ContactItem. To register for the event you need to cast directly to the ItemEvents_10_Event interface. After registering the events a method SetupBusinessRules is called. We discuss this in detail later – here is the code of the ContactItemWrapper class.

internalclassContactItemWrapper : InspectorWrapper {

 

///<summary>

/// .ctor

///</summary>

///<param name="inspector">The Outlook Inspector instance that should be handled</param>

public ContactItemWrapper(Outlook.Inspector inspector)

: base(inspector) {

}

 

///<summary>

/// The Object instance behind the Inspector (CurrentItem)

///</summary>

public Outlook.ContactItem Item { get; privateset; }

 

///<summary>

/// Method is called when the Wrapper has been initialized

///</summary>

protectedoverridevoid Initialize() {

// Get the Item of the current Inspector

Item = (Outlook.ContactItem)Inspector.CurrentItem;

 

// Register for the Item events

Item.Open += new Outlook.ItemEvents_10_OpenEventHandler(Item_Open);

Item.Write += new Outlook.ItemEvents_10_WriteEventHandler(Item_Write);

((Outlook.ItemEvents_10_Event)Item).Close += new Microsoft.Office.Interop.Outlook.ItemEvents_10_CloseEventHandler(Item_Close);

 

 

SetupBusinessRules();

}

 

///<summary>

/// This Method is called when the Item is going to be closed

///</summary>

///<param name="Cancel"></param>

void Item_Close(refbool Cancel) {

if (!Item.Saved) {

Cancel = !Validate();

}

}

 

///<summary>

/// This Method is called when the Item is saved.

///</summary>

///<param name="Cancel">When set to true, the save operation is cancelled</param>

void Item_Write(refbool Cancel) {

if (!Item.Saved) {

Cancel = !Validate();

}

}

 

///<summary>

/// This Method is called when the Item is visible and the UI is initialized.

///</summary>

///<param name="Cancel">When you set this property to true, the Inspector is closed.</param>

void Item_Open(refbool Cancel) {

//TODO: Implement something

}

 

 

///<summary>

/// The Close Method is called when the Inspector has been closed.

/// Do your cleanup tasks here.

/// The UI is gone, can't access it here.

///</summary>

protectedoverridevoid Close() {

// unregister events

Item.Write -= new Outlook.ItemEvents_10_WriteEventHandler(Item_Write);

Item.Open -= new Outlook.ItemEvents_10_OpenEventHandler(Item_Open);

((Outlook.ItemEvents_10_Event)Item).Close -= new Microsoft.Office.Interop.Outlook.ItemEvents_10_CloseEventHandler(Item_Close);

 

_BusinessRules.Clear();

_BusinessRules = null;

 

// required, just stting to NULL may keep a reference in memory of the Garbage Collector.

Item = null;

GC.Collect();

GC.WaitForPendingFinalizers();

}

 

 

#region business rule check methods

 

List<X4UBusinessRule> _BusinessRules = newList<X4UBusinessRule>();

 

privatevoid SetupBusinessRules() {

_BusinessRules.Add(newX4UBusinessRule("LastNameUppercase", "The Office Location is required and must be Uppercase.", this.BusinessRuleCheck_OfficeLocationRequiredAndUppercase));

_BusinessRules.Add(newX4UBusinessRule("CompanyName3Letters", "The Company Name must have a minimum length of 3.", this.BusinessRuleCheck_CompanyNameMinimum3Letters));

}

 

publicbool Validate() {

// if every business rule is valid we return true

if (_BusinessRules.TrueForAll(X4UBusinessRule.IsRuleValid)) returntrue;

 

// no ?

// show a warning to the user and

StringBuilder message = newStringBuilder(500);

message.AppendLine("You can't save this Item, because the following requirements are not met:");

foreach (X4UBusinessRule rule in _BusinessRules) {

if (!rule.IsValid()) message.AppendLine(rule.Description);

}

 

// display a message to the user what's wrong.

MessageBox.Show (newOutlookWin32Window(Inspector), message.ToString());

 

returnfalse;

}

 

publicbool BusinessRuleCheck_OfficeLocationRequiredAndUppercase() {

return (!string.IsNullOrEmpty (Item.OfficeLocation) && Item.OfficeLocation == Item.OfficeLocation.ToUpper());

}

 

publicbool BusinessRuleCheck_CompanyNameMinimum3Letters() {

return (Item.CompanyName != null && Item.CompanyName.Length > 2);

}

 

#endregion

 

}

 

Defining the business rules

First a class is defined that should describe a business rule. It has a name for identification and a description that should be displayed to the user if the rule is not validated successfully. It also has a pointer to a function that checks the business rule and returns true if it's valid. The Signature of such a rule check method is defined as a delegate.

///<summary>

/// Delegate declares the Method signature that checks the BusinessRule and returns true or false

///</summary>

///<returns>True if the rule is ok</returns>

publicdelegateboolBusinessRuleCheckMethod();

 

In the constructor of the business rule you setup the name and description and the pointer to that method that validates the rule. There's another static method IsRuleValid that just call the function and returns the result. It is defined as you can see next.

///<summary>

/// This class defines a business rule

///</summary>

publicclassX4UBusinessRule {

 

///<summary>

/// The Name of the business rule

///</summary>

publicstring Name { get; privateset; }

 

///<summary>

/// A description displayed to the user if the rule is not met

///</summary>

publicstring Description { get; privateset; }

 

///<summary>

/// A function pointer that points to the method that checks if the business rule is valid

///</summary>

publicBusinessRuleCheckMethod IsValid { get; privateset; }

 

///<summary>

/// .ctor

///</summary>

///<param name="name">The name</param>

///<param name="description">The description</param>

///<param name="ruleValidator">Function pointer to the validation method</param>

public X4UBusinessRule(string name, string description, BusinessRuleCheckMethod ruleValidator) {

IsValid = ruleValidator;

Name = name;

Description = description;

}

 

///<summary>

/// This method executes the method that the rule points to and returns the result

///</summary>

///<param name="rule">The businesrule to check</param>

///<returns>True if the rule is valid</returns>

publicstaticbool IsRuleValid(X4UBusinessRule rule) {

return rule.IsValid();

}

}

Next we take a look on setting up the rule check in the ContactItemWrapper class. Just for reference there are two business rule check methods defined as you can see here:

publicbool BusinessRuleCheck_OfficeLocationRequiredAndUppercase() {

return (!string.IsNullOrEmpty (Item.OfficeLocation) && Item.OfficeLocation == Item.OfficeLocation.ToUpper());

}

 

publicbool BusinessRuleCheck_CompanyNameMinimum3Letters() {

return (Item.CompanyName != null && Item.CompanyName.Length > 2);

}

 

The first method checks if the OfficeLocation property of the Item is not empty and uppercase. The second checks if the minimum length of the CompanyName property is 3 letters. You can define any kind of check – just make sure you return true when the rule is validated successfully.

Validation

How and when are the rules validated? For that we define a Validate method that is called in the write and close events.
The rules are stored in a list and so we can use the TrueForAll statement to check all rules at once. If one of the rules fails, we display a message to the user and return the cause of the mistake. Here is the Validate method.

publicbool Validate() {

// if every business rule is valid we return true

if (_BusinessRules.TrueForAll(X4UBusinessRule.IsRuleValid)) returntrue;

 

// no ?

// show a warning to the user and

StringBuilder message = newStringBuilder(500);

message.AppendLine("You can't save this Item, because the following requirements are not met:");

foreach (X4UBusinessRule rule in _BusinessRules) {

if (!rule.IsValid()) message.AppendLine(rule.Description);

}

 

// display a message to the user what's wrong.

MessageBox.Show (newOutlookWin32Window(Inspector), message.ToString());

 

returnfalse;

}

 

This is easy to implement. The Validate method is executed in the Item.Write and in the Item.Close event. You can see that in this method the Saved state of the Item is checked. When the Item isn't modified there's no need for a rule check. If the Validate method returns false, the write or close operation is canceled. The event handlers are defined as you can see here:

///<summary>

/// This Method is called when the Item is going to be closed

///</summary>

///<param name="Cancel"></param>

void Item_Close(refbool Cancel) {

if (!Item.Saved) {

Cancel = !Validate();

}

}

 

///<summary>

/// This Method is called when the Item is saved.

///</summary>

///<param name="Cancel">When set to true, the save operation is cancelled</param>

void Item_Write(refbool Cancel) {

if (!Item.Saved) {

Cancel = !Validate();

}

}

 

How to setup the business rule with function pointers? This is done using a single line of code.
Just add a new Instance of a BusinessRule class to the list and pass the name, description and the method as parameters to the constructor.

List<X4UBusinessRule> _BusinessRules = newList<X4UBusinessRule>();

 

privatevoid SetupBusinessRules() {

_BusinessRules.Add(newX4UBusinessRule("OfficeLocationRequiredAndUppercase", "The Office Location is required and must be uppercase.", this.BusinessRuleCheck_OfficeLocationRequiredAndUppercase));

_BusinessRules.Add(newX4UBusinessRule("CompanyName3Letters", "The Company Name must have a minimum length of 3.", this.BusinessRuleCheck_CompanyNameMinimum3Letters));

}

The code is executed whenever you modify or create a contact form and you try to save or close the form. When a rule is not valid, a MessageBox is displayed to the user as you can see in the screenshot.

Picture 2: MessageBox showing that business rules are not valid

Sample code

You can download the code sample for this solution here:
Visual Studio 2010

References

Outlook Inspector Object: http://msdn.microsoft.com/en-us/library/bb176679.aspx
Outlook ContactItem Object: http://msdn.microsoft.com/en-us/library/bb176651.aspx
InspectorWrapper: http://www.outlooksharp.de/Home/tabid/36/EntryId/44/Outlook-Inspector-Wrapper-explained.aspx

 


 

Tags:

Your name:
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Security Code
Enter the code shown above in the box below
Add Comment  Cancel 
X4U Blog
 
Home  |  Hardware  |  Software  |  Services  |  Resources  |  Impressum