SCSF

A new phase for the Acropolis project

The Acropolis Team has announced that Acropolis is going to enter a new phase.

David S. Platt Has Posted An Event Visualizer for CAB

David S. Platt has posted an Event Visualizer for CAB. I'll have to check it out.

Smart Client Software Factory – May 2007 Release is Live

(From Blaine Wastell's post and Mariano Szklanny's post)

You can now download the SCSF - May 2007 release. The new release provides support for .NET 3.0. The release is propagating to the download servers and should be available when you read this. If not, be patient and try a little later.

The release provides:

  • Windows Presentation Foundation interoperability CAB extensions  and View with presenter recipe (don't miss this post from Miguel Saez for more details)
  • Offline application blocks and recipes; the application blocks support asynchronous calls to WCF web services.
  • Richer ClickOnce Deployment capabilities
  • Enhanced guidance packages which includes code generation Visual Basic .NET 2005 (don't miss this post from Juan Arguello for more details)
  • Improved installation with a new dependency checker
  • Compatibility with Enterprise Library 3.1.

Here are instructions to install the factory. The team has created a new Getting Starting topic on Codeplex. This topic includes instructions to create a Hello World Application with the factory.

Check it out and let them know how you like it.

And this means that the team is going to start it's work on the Web Client Software Factory.

An Hierarchical CAB WorkItem Activation Service

The application I'm currently working on has a rather complex UI. It's something like two tabbed work spaces and an outlook-like work space, that make one workspace by itself, that can have several instances inside another tabbed workspace, that can have several instances inside another tabbed workspace. Imagine something like Visual Studio, inside a tab.

Each work space is controlled by its own work item and the same goes to every smart part.

With a UI like this, the work item activation service supplied by the CAB is of no use for me because I need to know what is the active work item at various levels.

What I came up with to solve my problem is the following implementation of the work item activation service:

public class HierarchicalWorkItemActivationService : IWorkItemActivationService
{
    private object syncroot = new object();
    private WorkItem activeWorkItem;
    private WorkItem hostingWorkItem;

    public HierarchicalWorkItemActivationService()
    {
    }

    [ServiceDependency]
    public WorkItem HostingWorkItem
    {
        set
        {
            Debug.Assert(value != null);
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }
            Debug.Assert(this.hostingWorkItem == null);
            if (this.hostingWorkItem != null)
            {
                throw new InvalidOperationException("HostingWorkItem already set.");
            }
            this.hostingWorkItem = value;
        }
    }

    public void ChangeStatus(WorkItem item)
    {
        lock (syncroot)
        {
            if (item == this.hostingWorkItem)
            {
                WorkItem hostingParentWorkItem = this.hostingWorkItem.Parent;
                if (hostingParentWorkItem != null)
                {
                    IWorkItemActivationService parentWorkItemActivationService = hostingParentWorkItem.Services.Get<IWorkItemActivationService>();
                    if (parentWorkItemActivationService != null)
                    {
                        parentWorkItemActivationService.ChangeStatus(this.hostingWorkItem);
                    }
                }
            }
            else
            {
                if (item.Status == WorkItemStatus.Active)
                {
                    if (item != this.activeWorkItem)
                    {
                        if (this.activeWorkItem != null && this.activeWorkItem.Status != WorkItemStatus.Terminated)
                        {
                            this.activeWorkItem.Deactivate();

                            // If the activeWorkItem is still active deactivates the item and aborts.
                            if (this.activeWorkItem.Status == WorkItemStatus.Active)
                            {
                                item.Deactivate();
                                return;
                            }
                        }
                        this.activeWorkItem = item;
                    }
                }
            }
        }
    }
}

Unit Testing With CAB

For those who like to unit test their software and are developing CAB applications, the SCSF comes with sample applications with unit tests. These unit tests are built on top of an useful set of testable classes which one of them is the TestableRootWorkItem.

Work Spaces and Smart Part Info Provider

As I understand it, the MVP (Model/View/Presenter) design pattern states that all presentation decisions are the Presenter's responsibility.

In the CAB world, this should include the Smart Part Info.

But the IWorkspace interface only allows you to provide the Smart Part Info (a class that implements the ISmartPartInfo interface and each work space uses a specific one), or the the smart part (the View in the MVP design pattern) should know what information to provide to the work space it lives in by implementing the ISmartPartInfoProvider interface.

To enable the presenter to provide the smart part info changes need to be made to the IWorkspace interface and its implementations. New Show and ApplySmartPartInfo methods need to be added:

/// <summary>
/// Applies the smartPartInfo to the smartPart.

/// </summary>
void ApplySmartPartInfo(object smartPart, ISmartPartInfoProvider smartPartInfoProvider);

/// <summary>
/// Shows SmartPart using the given SmartPartInfo
/// </summary>
/// <param name="smartPart">Smart part to show.</param>
/// <param name="smartPartInfoProvider"></param>
void Show(object smartPart, ISmartPartInfoProvider smartPartInfoProvider);

And it should be implemented in all the classes that implement IWorkspace:

/// <summary>
/// Applies the smartPartInfo to the smartPart.
/// </summary>
public void ApplySmartPartInfo(object smartPart, ISmartPartInfoProvider smartPartInfoProvider)
{
    Microsoft.Practices.CompositeUI.Utility.Guard.ArgumentNotNull(smartPart, "smartPart");
    Microsoft.Practices.CompositeUI.Utility.Guard.ArgumentNotNull(smartPartInfoProvider, "smartPartInfoProvider");
    ThrowIfUnsupportedSP(smartPart);
    ThrowIfSmartPartNotShownPreviously((TSmartPart)smartPart);

    TSmartPart typedSmartPart = (TSmartPart)smartPart;
    TSmartPartInfo typedSmartPartInfo = GetSupportedSPI(smartPartInfoProvider.GetSmartPartInfo(typeof(TSmartPartInfo)));
    Microsoft.Practices.CompositeUI.Utility.Guard.ArgumentNotNull(typedSmartPartInfo, "typedSmartPartInfo");

    OnApplySmartPartInfo(typedSmartPart, typedSmartPartInfo);
}

/// <summary>
/// Shows SmartPart using the given SmartPartInfo
/// </summary>
/// <param name="smartPart">Smart part to show.</param>
/// <param name="smartPartInfoProvider"></param>
public void Show(object smartPart, ISmartPartInfoProvider smartPartInfoProvider)
{
    Microsoft.Practices.CompositeUI.Utility.Guard.ArgumentNotNull(smartPart, "smartPart");
    Microsoft.Practices.CompositeUI.Utility.Guard.ArgumentNotNull(smartPartInfoProvider, "smartPartInfoProvider");
    ThrowIfUnsupportedSP(smartPart);
       
    TSmartPart typedSmartPart = (TSmartPart)smartPart;

    if (smartParts.Contains(typedSmartPart))
    {
        ApplySmartPartInfo(typedSmartPart, smartPartInfoProvider);
        Activate(typedSmartPart);
    }
    else
    {
        TSmartPartInfo typedSmartPartInfo = GetSupportedSPI(smartPartInfoProvider.GetSmartPartInfo(typeof(TSmartPartInfo)));
        Microsoft.Practices.CompositeUI.Utility.Guard.ArgumentNotNull(typedSmartPartInfo, "typedSmartPartInfo");
        smartParts.Add(typedSmartPart);
        OnShow(typedSmartPart, typedSmartPartInfo);
    }
}

Enhancing CAB’s ManagedObjectCollection

For those who don't know, the ManagedObjectCollection registers its elements using the objects running type as part of the key.

Recently, I had the need to add and retrieve a component to the WorkItem's Items collection independently of its running. I only wanted to retrieve the component (using a ComponentDependency) with a specified name. Needless to say that I couldn't, because I was trying to retrieve an object, and that was not the running type of the object added to the WorkItem.

I started thinking of the ability of the ServiceCollection to add items registering them with a different type would be very helpfully to the ManagedObjectCollection. So, decided to add some changes.

To be able to build elements and register them with a different type, I had to change the Build method: 

private TItem Build(Type typeToBuild, Type typeToRegisterAs, string idToBuild, object item)
{
	if (idToBuild != null && Contains(idToBuild, SearchMode.Local, true))
		throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
			Properties.Resources.DuplicateID, idToBuild));

	if (item != null && Object.ReferenceEquals(item,
		locator.Get(new DependencyResolutionLocatorKey(typeof(WorkItem), null))))
		throw new ArgumentException(Properties.Resources.CannotAddWorkItemToItself, "item");

	if (item == null)
		item = BuildFirstTimeItem(typeToBuild, typeToRegisterAs, idToBuild, null);
	else if (!container.Contains(item))
		item = BuildFirstTimeItem(typeToBuild, typeToRegisterAs, idToBuild, item);
	else
		BuildRepeatedItem(typeToBuild, typeToRegisterAs, idToBuild, item);

	return (TItem)item;
}

private object BuildFirstTimeItem(Type typeToBuild, Type typeToRegisterAs, string idToBuild, object item)
{
    item = builder.BuildUp(locator, typeToBuild, NormalizeID(idToBuild), item);

    if (typeToRegisterAs != typeToBuild)
    {
        locator.Add(new DependencyResolutionLocatorKey(typeToRegisterAs, idToBuild), item);
        locator.Remove(new DependencyResolutionLocatorKey(typeToBuild, idToBuild));
    }

    return item;
}

private void BuildRepeatedItem(Type typeToBuild, Type typeToRegisterAs, string idToBuild, object item)
{
    locator.Add(new DependencyResolutionLocatorKey(typeToRegisterAs, NormalizeID(idToBuild)), item);
}

And, to assure compability with the existing methods:

private TItem Build(Type typeToBuild, string idToBuild, object item)
{
    return Build(typeToBuild, typeToBuild, idToBuild, item);
}

Now, all I needed was to add the new methods to add elements:

public void Add(Type typeToRegisterAs, TItem item)
{
    Add(typeToRegisterAs, item, null);
}

public void Add(Type typeToRegisterAs, TItem item, string id)
{
    Guard.ArgumentNotNull(item, "item");
    Guard.TypeIsAssignableFromType(typeof(TItem), typeToRegisterAs, "typeToBuild");

    Build(typeToRegisterAs, item.GetType(), id, item);
}

public void Add<TTypeToRegisterAs>(TTypeToRegisterAs item)
    where TTypeToRegisterAs : TItem
{
    Add<TTypeToRegisterAs>(item, null);
}

public void Add<TTypeToRegisterAs>(TTypeToRegisterAs item, string id)
    where TTypeToRegisterAs : TItem
{
    Guard.ArgumentNotNull(item, "item");

    Build(item.GetType(), typeof(TTypeToRegisterAs), id, item);
}

public TItem AddNew(Type typeToBuild, Type typeToRegisterAs)
{
    Guard.TypeIsAssignableFromType(typeToBuild, typeToRegisterAs, "typeToBuild");

    return AddNew(typeToBuild, typeToRegisterAs, null);
}

public TItem AddNew(Type typeToBuild, Type typeToRegisterAs, string id)
{
    Guard.TypeIsAssignableFromType(typeToBuild, typeof(TItem), "typeToBuild");
    Guard.TypeIsAssignableFromType(typeToBuild, typeToRegisterAs, "typeToRegisterAs");

    return Build(typeToBuild, typeToRegisterAs, id, null);
}

public TTypeToBuild AddNew<TTypeToBuild, TTypeToRegisterAs>()
    where TTypeToRegisterAs : TItem
    where TTypeToBuild : TTypeToRegisterAs
{
    return (TTypeToBuild)Build(typeof(TTypeToBuild), typeof(TTypeToRegisterAs), null, null);
}

public TTypeToBuild AddNew<TTypeToBuild, TTypeToRegisterAs>(string id)
    where TTypeToRegisterAs : TItem
    where TTypeToBuild : TTypeToRegisterAs
{
    return (TTypeToBuild)Build(typeof(TTypeToBuild), typeof(TTypeToRegisterAs), id, null);
}

Using Remoting with CAB

Anyone who has ttried to add a service implemented through Remoting to the WorkItem's services collection was sadly surprised that it can't be done.

But why?

When we use the following code:

WorkItem.Services.Add<IServiceContract>(serviceInstance);

It comes to something like this:

if (!typeof(IServiceContract).IsAssignableFrom(serviceInstance.GetType())
	throw new ArgumentException();

And how do we solve this problem?

easy: generics.

All we need to do is make some changes to the ServiceCollection class.

We change the Add<TService>(TService serviceInstance) method to look like this:

public void Add<TService>(TService serviceInstance) 
{ 
	Guard.ArgumentNotNull(serviceInstance, "serviceInstance"); 

	Build<TService>(serviceInstance); 
} 

At this point we need to refactor the Build(Type typeToBuild, Type typeToRegisterAs, object serviceInstance) method to have two entry points to the common its logic:

private object Build(Type typeToBuild, Type typeToRegisterAs, object serviceInstance) 
{ 
	Guard.TypeIsAssignableFromType(typeToBuild, typeToRegisterAs, "typeToBuild"); 

	return BuildImplementation(typeToBuild, typeToRegisterAs, serviceInstance); 
} 

private TService Build<TService>(TService serviceInstance) 
{ 
	return (TService)BuildImplementation(serviceInstance.GetType(), typeof(TService), serviceInstance); 
} 

private object BuildImplementation(Type typeToBuild, Type typeToRegisterAs, object serviceInstance) 
{ 
	if (locator.Contains(new DependencyResolutionLocatorKey(typeToRegisterAs, null), SearchMode.Local)) 
		throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
Properties.Resources.DuplicateService, typeToRegisterAs.FullName)); if (serviceInstance == null) serviceInstance = BuildFirstTimeItem(typeToBuild, typeToRegisterAs, null); else if (!container.Contains(serviceInstance)) serviceInstance = BuildFirstTimeItem(typeToBuild, typeToRegisterAs, serviceInstance); else BuildRepeatedItem(typeToRegisterAs, serviceInstance); return serviceInstance; }

Easy, isn't it? And I suspect that that's not the only improvement like this that we can get into CAB.

Oh! And why would we wanto to use Remoting? To use NMock2 in the unit tests, of course!

Updated: November 14th, 2006

Changed:

private TService Build<TService>(object serviceInstance)
{
	Guard.TypeIsCompatibleType<TService>(serviceInstance, "typeToBuild");
return (TService)BuildImplementation(serviceInstance.GetType(), typeof(TService), serviceInstance); }

Removed:

And add the TypeIsCompatibleType<T>(object providedInstance, string argumentName) method to the Guard class:

public static void TypeIsCompatibleType<T>(object providedInstance, string argumentName) 
{ 
	if (!(providedInstance is T)) 
		throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
Properties.Resources.TypeNotCompatible, typeof(T), providedInstance.GetType()),
argumentName); }

Fighting with CAB visualizers and visualizations

Anyone who has previously developed with CAB knows that documentation and information are scarce, but when one faces something like I recently faced, everything becomes even more difficult.


When I tried to use a visualizer with my application, I was faced with something very strange (to say the least).


(For those who don't know it, a visualizer is a CAB component that collects information about the WorkItems in the application and makes that information available to visualizations that will display it.)


Finding out that I needed a visualizer element in my configuration file it wasn't easy. But nothing prepared me to the fact that this element is just a collection of visualizations.


The configuration section looks something like this:


<visualizer>
<add type="visualization type"/>
...
<add type="visualization type"/>
</visualizer>

Would it be more logical to have something like this?

<visualizer type="visualizer type">
<visualizations>
<add type="visualization type"/>
...
<add type="visualization type"/>
<visualizations>
</visualizer>

We could even configure the type of the visualizer, which we can't with the current architecture.


And this pearl came out of PAG.