Monday, September 07, 2009

Best practice around Zenoss template bindings

After working with Zenoss Core for a few years now, I wanted to pass along a tip that would have saved me a lot of time had I known it in the beginning: do not bind performance templates to individual devices, or make local modified copies of templates on devices. Once you do this, the device no longer inherits template bindings from the parent device class, but you have no good way to know that this condition exists unless you drill into the device and examine its template bindings.

When you move a device from one class to another, if it lacks local bindings or overridden templates, it will inherit the bindings of the new class. However, this does not occur if you have made any local changes as above. Also, if you bind new performance templates to its parent, it ignores these new bindings if you have made any local changes. I highly recommend that if you need local changes to a device, make a new device class and make the template binding changes to the class. Then, move the device to the new class, and it will pick up the new bindings.

To clear this condition on a device once you have made local changes, you must reset bindings to be those of its container. On the device, go to More | Templates, then use the drop-down arrow next to "Performance templates for device," and finally Reset Bindings.

Updating the UI from a BackgroundWorker in WPF

This approach enabled me to flexibly send messages to objects on the UI thread while processing from a BackgroundWorker object in WPF. Otherwise, the UI thread doesn't update if the BackgroundWorker code tries to manipulate its objects during processing. First I created this UIUpdater:
public delegate void UpdateTextDelegate(string message);

///
/// Sends messages to the UI thread via the dispatcher.
///

class UIUpdater
{
#region ctor
///
/// Initializes a new instance of the class.
///

/// The dispatcher in scope for the UI.
/// Function that will apply the message to the appropriate control(s).
public UIUpdater(Dispatcher uiDispatcher, UpdateTextDelegate uiUpdateDelegate)
{
if (default(Dispatcher) == uiDispatcher) throw new ArgumentNullException("uiDispatcher");
if (default(UpdateTextDelegate) == uiUpdateDelegate) throw new ArgumentNullException("uiUpdateDelegate");

dispatcher = uiDispatcher;
updateTextDelegate = uiUpdateDelegate;
}
#endregion

#region member variables
Dispatcher dispatcher = default(Dispatcher);
UpdateTextDelegate updateTextDelegate = default(UpdateTextDelegate);
#endregion

#region methods
///
/// Sends the message.
///

/// The message.
public void SendMessage(string message)
{
if (default(string) == message) throw new ArgumentNullException("message"); // allow string.Empty
dispatcher.BeginInvoke(
DispatcherPriority.Normal,
(ThreadStart)delegate() {
updateTextDelegate(message);
});
}
#endregion
}

To use it, in the BackgroundWorker_DoWork event handler, initialize it and pass it into your objects that implement the background work. Give it a reference to your dispatcher and a delegate that will handle updates from the background thread:
UIUpdater updater = new UIUpdater(mainWindow.Dispatcher, new UpdateTextDelegate(UpdateStatus));

DoMyWork(updater);
Then from the code doing the background work, you can call the SendMessage delegate to safely pass along the message:
updater.SendMessage("Deleting all items from the " + listName + " list...");

Programmatically creating a recursive view with WSS web services

I had a project where I wanted to do a few things:
  • Create a view using the Windows SharePoint Services' web services
  • Make this view recursive
  • Set the display style to the alternating line style
What I discovered is that there is not feature parity between the WSS object model and the web service. Also, while it is possible to create the view and make it recursive, there is no support in the WSS 3.0 web services for setting the style on a view.

To create a recursive view:

Add Web Reference

First, add a web reference to the Views web service. You can do this by adding an ASMX web reference in your project to http://yourSharePointSiteUrl/resources/_vti_bin/views.asmx.

Create the Client
SharePointViewService.Views viewClient = new MyProject.SharePointViewService.Views();
viewClient.Credentials = System.Net.CredentialCache.DefaultCredentials;
Create the View via the Web Service
When working with the WSS web services, the basic model is to send the XML fragments to the method call and then parse the result for exceptions.
XmlNode result = viewClient.AddView(listName, viewName, viewFieldsNode, queryNode, rowLimitNode, type, false); // the last parameter: isDefaultView
Here are examples of the parameter values for the above:

listName: "My List"
viewName: "My View"
viewFieldsNode (outer XML):
<ViewFields><FieldRef Name="LinkTitle" />
<FieldRef Name="Item_x0020_Number" />
<FieldRef Name="Description" />
<FieldRef Name="Release" />
<FieldRef Name="Module" />
</ViewFields>
queryNode (outer XML):
<Query>
<OrderBy>
<FieldRef Name="Item_x0020_Number" />
</OrderBy>
<GroupBy Collapse="TRUE" GroupLimit="100">
<FieldRef Name="Release" />
<FieldRef Name="Module" />
</GroupBy>
</Query>
rowLimitNode (outer XML):
<RowLimit Paged="TRUE">100</RowLimit>
type: "HTML"

If exceptions occur, you will have meaningful data in the return value from the AddView method call.

Update the View to Set the Recursive Property
You cannot create the view as a recursive view--the key is that you must follow up the add call with an update call to set this property.
XmlDocument vp = new XmlDocument();
vp.AppendChild(vp.CreateElement("View"));
vp.DocumentElement.SetAttribute("Scope", "Recursive");
viewClient.UpdateView(addView.ListName, viewResult.Name, vp, default(XmlNode), default(XmlNode), default(XmlNode), default(XmlNode), default(XmlNode));
I sincerely hope this helps someone, as the documentation for the WSS web services, particularly the Views web service, is significantly lacking.