0 out of 1 rated this helpful - Rate this topic

User Interface Process (UIP) Application Block - Version 2.0

Retired Content
This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.
 

patterns & practices Developer Center

Microsoft Corporation

April 2004

Summary: Appendix A explains how to extend the UIP application block.

Contents

Customizing State
Customizing State Persistence
Customizing Views and View Management
Persisting Task Information Between User Sessions

In most cases you can use the UIP Application Block in its default configuration. However, the block is designed to be extensible, and you can customize certain parts of it to meet the needs of your application. You can extend the UIP Application Block, in the following ways:

  • Customizing state — You can develop your own state management class derived from the State class that has your own custom properties. One use of this is to develop a strongly typed state class.
  • Customizing state persistence — You can develop your own implementation of the state persistence interface to persist state wherever and whenever you want.
  • Developing custom view implementations — You can implement the IView interface to create a base class for your views and the IViewManager interface to create a management class to control them.
  • Developing custom task implementations — You can implement the ITask interface within your application to save and load task information in persistent storage. This enables a user to work on a task, suspend that task, and then later resume the task. Because the information can be stored in a persistent medium, the task can be suspended across computer reboots.

Customizing State

The existing state class is sufficient for most uses of the UIP Application Block. However, there are circumstances when you may need to create a custom state class. These include situations where you want to modify when state is persisted and when you want to create a strongly typed state class.

Creating a Custom State Class

To create and use a custom state class, you need to do the following:

  1. Inherit the existing State class from within the block, and add any custom properties you require.
  2. Create the constructors.
  3. Modify the configuration information to use the class.

After you have a state derivation, you can customize it to include whatever functionality you require.

Note   This section describes how to override the State class itself and does not show how to extend the functionality of that base class. The examples shown simply call the functionality included in the base class, which is required of any custom derivation.

Inheriting the State Class

To create a custom state class, you must inherit from the existing State class in the block. You must also ensure that your class is serializable so that any state persistence implementations that serialize data to binary format for storage will work correctly. The following example code shows how to do this.

[Visual Basic]
Imports Microsoft.ApplicationBlocks.UIProcess
<Serializable()> Public Class MyState
  Inherits State
  . . .
End Class

[C#]
using Microsoft.ApplicationBlocks.UIProcess;
[Serializable] public class MyState : State

Creating the Constructors

Your custom state class must contain the constructor that is used by the block factories for creating the custom state object. You can achieve this by calling the code in the base State class, as shown in the following code example.

[Visual Basic]
Public Sub New(statePersistenceProvider As IStatePersistence)
  MyBase.New(statePersistenceProvider)
End Sub

[C#]
public MyState(IStatePersistence statePersistenceProvider)
  :base(statePersistenceProvider)
{
}

The class must also contain a constructor for the serialization code to use when deserializing state from the persisted store. Again, you can call the matching base class constructor, as shown in the following code example.

[Visual Basic]
<SecurityPermissionAttribute(SecurityAction.Demand, _
  SerializationFormatter := True), _
  SecurityPermissionAttribute(SecurityAction.LinkDemand, _
  Flags := SecurityPermissionFlag.SerializationFormatter)>  _
  Protected Sub New(si As SerializationInfo, context As 
StreamingContext)
    MyBase.New(si,context)
End Sub

[C#]
[SecurityPermissionAttribute(SecurityAction.Demand,Serialization
Formatter = true)]
  [SecurityPermissionAttribute(SecurityAction.LinkDemand,
  Flags=SecurityPermissionFlag.SerializationFormatter)]
  protected MyState(SerializationInfo si, StreamingContext 
context)
    : base(si,context)
  {
  }

Finally, the class must contain the standard constructors, which once again can call the matching base class constructors, as shown in the following code example.

[Visual Basic]
Public Sub New()
  MyBase.New()
End Sub

Public Sub New(taskId As Guid)
  MyBase.New(taskId)
End Sub

Public Sub New(taskId As Guid, navGraph As String)
  MyBase.New(taskId, navGraph)
End Sub

Public Sub New(taskId As Guid, navGraph As String, currentView As 
String)
  MyBase.New(taskId, navGraph, currentView)
End Sub

Public Sub New(taskId As Guid, navigationGraph As String, 
currentView As String, _
               navigateValue As String, statePersistence As 
IStatePersistence)
  MyBase.New(taskId, navigationGraph, currentView, navigateValue, 
_
             statePersistence)
End Sub

[C#]
public MyState() : base(){}

public MyState(Guid taskId) : base(taskId){}

public MyState(Guid taskId, string navGraph) : base(taskId, 
navGraph){}

public MyState(Guid taskId, string navGraph, string currentView) : 
base(taskId,
               navGraph, currentView){}

public MyState(Guid taskId, string navigationGraph, string 
currentView, string
               navigateValue, IStatePersistence statePersistence) 
: base(taskId,
               navigationGraph, currentView, navigateValue, 
statePersistence){}

Modifying Configuration Information to Use the Custom State Class

After you create your new state class, you must ensure that you modify the configuration information to use this class. To do this, modify the <state> element in the <objectTypes> section and the state attribute of the <navigationGraph> element in the configuration file, as shown in the following code example.

<state
  name="MyState"
  type="MyNamespace.MyState, MyNamespace/>

<navigationGraph
  iViewManager="WebFormViewManager"
  name="MyGraph"
  state="MyState"
  statePersist="MyStatePersistence"
  cacheExpirationMode="Sliding"
  cacheExpirationInterval="1000"
  startView="View1">
  . . .
</navigationGraph>

Creating a Strongly Typed Class

One reason for creating a custom state class is so that you can use a strongly typed class. You can create a strongly typed class by inheriting from the existing state class in the block, and then modifying it to use strongly typed properties.

Adding Strongly Typed Properties

To enable strong typing of your custom state class, you can add strongly typed properties that store state information specific to your application. When setting any properties, you should call the StateChanged event to notify any event listeners that a state item has changed.

The following example code shows how to include a custom string property and custom structure property as part of the State object.

[Visual Basic]
Private _userName As String
Public Property UserName() As String
  Get
    Return _userName
  End Get
  Set(ByVal Value As String)
    _userName = value
    RaiseEvent StateChanged(Me, New 
StateChangedEventArgs("UserName"))
  End Set
End Property

Private _address As AddressStruct
Public Property Address() As AddressStruct
  Get
    Return _address
  End Get
  Set(ByVal Value As AddressStruct)
    _address = value
    RaiseEvent StateChanged(Me, New 
StateChangedEventArgs("Address"))
  End Set
End Property

[C#]
private string _userName;
public string UserName
{
  get{return _userName;}
  set
  {
    _userName = value;
    if (null != StateChanged)
    {
      StateChanged(this, new StateChangedEventArgs("UserName"));
    }
  }
}

private AddressStruct _address;
public AddressStruct Address
{
  get{return _address;}
  set
  {
    _address=value;
    if ( null != StateChanged )
    {
      StateChanged(this, new StateChangedEventArgs("Address"));
    }
  }
}

Declaring the Events

Because your custom state class calls the StateChanged event, it must include declarations of the StateChangedEventHandler. This handler allows views to be notified if the internal state changes. The following example code shows how to declare this.

[Visual Basic]
Shadows Delegate Sub StateChangedEventHandler(ByVal sender As 
Object, _
                                              ByVal e As 
StateChangedEventArgs)
Public Shadows Event StateChanged As StateChangedEventHandler

[C#]
public new delegate void StateChangedEventHandler(object sender,
                                                  
StateChangedEventArgs e);
public new event StateChangedEventHandler StateChanged;

Creating the Constructors

Your class constructors must contain code that builds the custom state, in addition to calling the base constructor. The following example code shows how to modify the constructors to initialize your custom properties.

[Visual Basic]
Public Sub New()
  MyBase.New()
  ' Initialize custom properties
  _userName = Nothing
  _address = New AddressStruct(0, Nothing, Nothing, Nothing)
End Sub

Public Sub New(taskId As Guid)
  MyBase.New(taskId)
  ' Initialize custom properties
  _userName = Nothing
  _address = New AddressStruct(0, Nothing, Nothing, Nothing)
End Sub

Public Sub New(taskId As Guid, navGraph As String)
  MyBase.New(taskId, navGraph)
  ' Initialize custom properties
  _userName = Nothing
  _address = New AddressStruct(0, Nothing, Nothing, Nothing)
End Sub

Public Sub New(taskId As Guid, navGraph As String, currentView As 
String)
  MyBase.New(taskId, navGraph, currentView)
  ' Initialize custom properties
  _userName = Nothing
  _address = New AddressStruct(0, Nothing, Nothing, Nothing)
End Sub

Public Sub New(taskId As Guid, navigationGraph As String, 
currentView As String, _
               navigateValue As String, statePersistence As 
IStatePersistence)
  MyBase.New(taskId, navigationGraph, currentView, navigateValue, 
_
             statePersistence)
  ' Initialize custom properties
  _userName = Nothing
  _address = New AddressStruct(0, Nothing, Nothing, Nothing)
End Sub

[C#]
public MyState() : base()
{
  // Initialize custom properties
  _userName = null;
  _address = new AddressStruct(0, null, null, null);
}

public MyState(Guid taskId) : base(taskId)
{
  // Initialize custom properties
  _userName = null;
  _address = new AddressStruct(0, null, null, null);
}

public MyState(Guid taskId, string navGraph) : base(taskId, 
navGraph)
{
  // Initialize custom properties
  _userName = null;
  _address = new AddressStruct(0, null, null, null);
}

public MyState(Guid taskId, string navGraph, string currentView) : 
base(taskId,
               navGraph, currentView)
{
  // Initialize custom properties
  _userName = null;
  _address = new AddressStruct(0, null, null, null);
}

public MyState(Guid taskId, string navigationGraph, string 
currentView, string
               navigateValue, IStatePersistence statePersistence) 
: base(taskId,
               navigationGraph, currentView, navigateValue, 
statePersistence){}
{
  // Initialize custom properties
  _userName = null;
  _address = new AddressStruct(0, null, null, null);
}

The constructor that is used by the serialization code to deserialize state from the persisted store must also create your custom properties. The following example code shows how to store the deserialized data in an instance of the custom state object.

[Visual Basic]
<SecurityPermissionAttribute(SecurityAction.Demand, _
  SerializationFormatter:=True), _
  SecurityPermissionAttribute(SecurityAction.LinkDemand, _
  Flags:=SecurityPermissionFlag.SerializationFormatter)> _
  Protected Sub New(si As SerializationInfo, context As 
StreamingContext)
    MyBase.New(si,context)
    ' Deserialize custom properties
    _userName = si.GetString(NameUserName)
    _address = CType(si.GetValue(NameAddressStruct, 
GetType(AddressStruct)), _
                     AddressStruct)
End Sub

[C#]
  [SecurityPermissionAttribute(SecurityAction.Demand,
SerializationFormatter = true)]
  [SecurityPermissionAttribute(SecurityAction.LinkDemand,
  Flags=SecurityPermissionFlag.SerializationFormatter)]
  protected MyState(SerializationInfo si, StreamingContext 
context)
    : base(si,context)
  {
    // Deserialize custom properties
    _userName = si.GetString(NameUserName);
    _address =
      (AddressStruct)
si.GetValue(NameAddressStruct,typeof(AddressStruct));
  }

Implementing ISerializable

To ensure that your custom properties are serialized by the state persistence mechanism, you must also modify the ISerializable.GetObjectData method. The following example code shows how to modify this method.

[Visual Basic]
<SecurityPermissionAttribute(SecurityAction.Demand, _
  SerializationFormatter:=True), _
  SecurityPermissionAttribute(SecurityAction.LinkDemand, _
  Flags:=SecurityPermissionFlag.SerializationFormatter)> _
  Public Overrides Sub GetObjectData(info As SerializationInfo, _
                                     context As StreamingContext)
    MyBase.GetObjectData(info, context)

    ' Add custom properties
    info.AddValue(NameUserName, _userName)
    info.AddValue(NameAddressStruct, _address, 
GetType(AddressStruct))
End Sub

[C#]
[SecurityPermissionAttribute(SecurityAction.Demand,
SerializationFormatter = true)]
  [SecurityPermissionAttribute(SecurityAction.LinkDemand,
  Flags=SecurityPermissionFlag.SerializationFormatter)]
  public override void GetObjectData(SerializationInfo info,
                                     StreamingContext context)
  {
    base.GetObjectData(info, context);

    // Add custom properties
    info.AddValue(NameUserName, _userName);
    info.AddValue(NameAddressStruct, _address, 
typeof(AddressStruct));
  }

Customizing State Persistence

Another way of extending the capability of the UIP Application Block is to modify how state is persisted. In Web applications, scalability is generally an important requirement, and by optimizing your state persistence mechanism, you can increase the scalability of the overall application. Applications based on the Microsoft® Windows® operating system generally require high performance, and careful planning of your state persistence provider can help meet this requirement.

You can customize state persistence in one of two ways:

  • Create and use a custom state persistence provider.
  • Modify when state is persisted.

Creating a Custom State Persistence Provider

You may decide against using the IStatePersistence implementations supplied in the block, and instead create your own provider. You might do this so that you can persist your state to a different type of database, a file, or any other type of data storage. For more information about caching data, see the "Caching Architecture Guide for .NET Framework Applications".

Note   You cannot easily persist state using the .NET Framework XmlSerializer class. This is because the XmlSerializer treats classes that implement IDictionary in a special way. It is possible to wrap the State class using a Hashtable class, which will hide the IDictionary implementation from the XmlSerializer class. However, this can inhibit performance and is not generally recommended.

To create your own custom state persistence provider, you need to create a class that inherits from the IStatePersistence interface, as shown in the following code example.

[Visual Basic]
Public Class MyStatePersistence
  Implements IStatePersistence
End Class

[C#]
using Microsoft.ApplicationBlocks.UIProcess;

public class MyStatePersistence : IStatePersistence

The interface defines three classes: Init, Load, and Save. Your custom class must override these three methods.

The following code example shows how to override the Init method to pass user information to a custom database state persistence provider.

[Visual Basic]
Public Sub Init(ByVal statePersistenceParameters As _
                Specialized.NameValueCollection) _
                Implements IStatePersistence.Init
  connectionInfo = statePersistenceParameters(conInfo)
End Sub

[C#]
public void Init(NameValueCollection statePersistenceParameters)
{
  connectionInfo = statePersistenceParameters[conInfo];
}

The following code snippet shows how to override the Load method.

[Visual Basic]
Public Function Load(ByVal taskId As System.Guid) As State _
Implements IStatePersistence.Load
  ' Database specific code to access the stored state
  Return storedState
End Function

[C#]
public State Load(Guid taskId)
{
  // Database specific code to access the stored state
  return storedState;
)

The following code snippet shows how to override the Save method.

[Visual Basic]
Public Sub Save(ByVal state As State) Implements 
IStatePersistence.Save
  ' Database specific code to save the passed state
End Sub

[C#]
public void Save(State state)
{
  // Database specific code to save the passed state
}

Modifying Configuration Information to Use the Custom State Persistence Class

After you implement your new state persistence provider, you must ensure that you modify the configuration information to use this class. To do this, you must modify the <statePersistenceProvider> element in the <objectTypes> section and the statePersist attribute of the <navigationGraph> element in the configuration file, as shown in the following example code.

<statePersistenceProvider
  name="MyStatePersistence"
  type="MyNamespace.MyStatePersistence, MyNamespace"/>

<navigationGraph
  iViewManager="WebFormViewManager"
  name="MyGraph"
  state="State"
  statePersist="MyStatePersistence"
  cacheExpirationMode="Sliding"
  cacheExpirationInterval="1000"
  startView="View1">
  . . .
</navigationGraph>

Customizing When State Is Persisted

By default, the state in a process is persisted to the main store each time a new view is displayed. While this is the safest option and ensures that no state is ever lost, it can result in performance issues due to the time taken to serialize the data and store it elsewhere.

You may decide against using the State object at all for application data. If you are using high-scale online transaction processing that has been designed to use a stateless middle tier, storing application data in the State object will conflict with the original middle tier design. Alternatively, you may choose to cache state in memory for the lifetime of the application, and only persist it when the process is about to end.

However, if you want to persist state more frequently and still improve performance, you can customize the block to ensure that state is only persisted if it has changed since last stored. You can do this by implementing an IsDirty property in a custom state class. Then, you must create a custom state persistence provider by implementing the IStatePersistence interface. After you develop these custom classes, you must modify them to implement and use an IsDirty check.

Note   For information about creating a custom state class and a custom state persistence provider, see "Creating a Custom State Class" and "Creating a Custom State Persistence Provider" earlier in this Appendix.

Adding the IsDirty Property to the Custom State Class

The following code example shows how to add the IsDirty property to your custom state class.

[Visual Basic]
Private _isDirty As Boolean = False

Public Property IsDirty() As Boolean
  Get
    Return _isDirty
  End Get
  Set(ByVal Value As Boolean)
    _isDirty = value
  End Set
End Property

[C#]
private bool _isDirty = false;

public bool IsDirty
{
  get{return _isDirty;}
  set{_isDirty = value;}
}

Initializing the IsDirty Property

You should initialize the IsDirty property to false on creation. This is because the State object gets recreated multiple times during the lifetime of an application, and the flag should only be true when state needs to be saved. However, the IsDirty flag should be set to true when the task starts so that the initial values can be persisted.

To do this, you should set the flag to true in the constructor used by the custom state persistence provider, as shown in the following example code.

[Visual Basic]
Public Sub New(statePersistenceProvider As IStatePersistence)
  MyBase.New(statePersistenceProvider)

  ' Initialize _isDirty to ensure that initial values are 
persisted
  _isDirty = True
End Sub

[C#]
public MyState(IStatePersistence statePersistenceProvider)
  :base(statePersistenceProvider)
{
  // Initialize _isDirty to ensure that initial values are 
persisted
  _isDirty = true;
}

Setting the IsDirty Property to True

Each time you change a member of your custom state class, you need to set the IsDirty property to true. This may include the contents of the internal hash table, the custom properties within your custom state class, or both.

To set the IsDirty flag in your custom properties, you should add the following code to the Set accessor of each property.

[Visual Basic]
IsDirty = True

[C#]
IsDirty = true;

To set the IsDirty flag when the contents of the hash table change, you must override the accessors for the hash table from the base state class. These include the Add, Remove, and this[ ] methods.

The following code shows how to override these methods.

[Visual Basic]
Public Overrides Sub Add(key As String, value As Object)
  MyBase.Add(key, value)
  IsDirty = True
End Sub

Public Overrides Sub Remove(key As String)
  MyBase.Remove(key)
  IsDirty = True
End Sub

Default Public Overrides Property Item(key As String) As Object
  Get
    Return MyBase.Item(key)
  End Get
  Set(ByVal Value As Object)
    MyBase.Item(key) = Value
    IsDirty = True
  End Set
End Property

[C#]
public override void Add(string key, object value)
{
  base.Add(key, value);
  IsDirty = true;
}

public override void Remove(string key)
{
  base.Remove(key);
  IsDirty = true;
}

public override object this[string key]
{
  get{ return base[key]; }
  set
  {
    base[key] = value;
    IsDirty = true;
  }
}
Note   If you decide not to persist the changes made to the hash table members, you must still persist the creation of the members or the original values will be lost. In this case, you must ensure that you use the Add method (rather than the this[ ] indexer) to add members to the hash table, and that you set the IsDirty flag in the Add method.

If you are particularly concerned with performance, you can check whether the incoming state value is different from the current value before setting the IsDirty property. This will ensure that the flag is only set when the state has actually changed, as opposed to when it is passed to the state class.

Using IsDirty in a Custom IStatePersistence Class

After you define and set the IsDirty property in the custom state class, you are able to use it in a custom implementation of the IStatePersistence interface. You must add code to the Save method that persists the data if the custom state object is dirty and that resets the flag before exiting, as shown in the following code example.

[Visual Basic]
<SqlClientPermission(System.Security.Permissions.
SecurityAction.Demand)> _
  Public Sub Save(ByVal state As State) Implements 
IStatePersistence.Save
    ' cast base state object into custom state object
    Dim myState As MyState = CType(state, MyState)
    If myState.IsDirty = True Then
      ' code to serialize and persist data
      '. . .
      myState.IsDirty = False
    End If
  End Sub

[C#]
[SqlClientPermission(System.Security.Permissions.SecurityAction.
Demand)]
  public void Save(State state)
  {
    // cast base state object into custom state object
    StateChangedCheck myState = (StateChangedCheck)state;
    if (myState.IsDirty == true)
    {
      // code to serialize and persist data
      // . . .
      myState.IsDirty = false;
    }

Customizing Views and View Management

The UIP Application Block contains view and view management implementations for use in Windows-based applications and in ASP.NET Web applications. If you want to use other types of user interfaces for your views, you need to customize the view system. However, creating your own view classes involves more than just implementing the IView interface in a class and deriving views from that class. You must also implement your own view management class (using IViewManager), and write custom code for the creation and manipulation of your views.

Creating a custom view and view management system involves the following steps:

  1. Create a custom IView implementation. This may derive from whatever other classes you require.
  2. Create the views to be used in your application using the base class you created.
  3. Create a custom IViewManager implementation.
  4. Add code to execute the creation of the views, the retrieval of configuration information for the views, and the management of views.
Note   The .NET Framework allows you to derive a class from many interfaces, but only one class. If your views must derive from a standard class, they cannot also derive from WebFormView, WindowsFormView, or WindowsFormControlView. In this situation, you must implement the IView interface in a base view, and then derive your individual views from it.

Creating a Custom IView Implementation

You should only create a custom IView implementation if the versions supplied in the UIP Application Block do not meet your requirements. To create a custom IView implementation, you need to:

  1. Reference the IView interface.
  2. Code the custom View class.

These procedures are discussed in the following subsections.

Referencing the IView Interface

To create a custom view, you must first inherit from the IView interface defined in the block, as shown in the following code examples.

[Visual Basic]
Imports Microsoft.ApplicationBlocks.UIProcess
Public Class NewBaseView
  Implements IView

[C#]
using Microsoft.ApplicationBlocks.UIProcess;
public class NewBaseView : IView

Coding the Custom View

Your custom view must include code to do the following:

  • Declare private member variables to hold the data required by the block code.
  • Implement internal properties to allow the block to set the member variables.
  • Implement code to initialize and access the view's controller.
  • Implement the methods defined in the interface.

The following code examples show the declaration of private member variables to hold data required by the block code.

[Visual Basic]
Private _taskId As Guid
Private _navigationGraph As String
Private _viewName As String

[C#]
private Guid _taskId;
private string _navigationGraph;
private string _viewName;

The following code examples implement internal properties to allow the block to set the member variables.

[Visual Basic]
Friend WriteOnly Property InternalTaskId() As Guid
  Set
    _taskId = value
  End Set
End Property

Friend WriteOnly Property InternalNavigationGraph() As String
  Set
    _navigationGraph = value
  End Set
End Property

Friend WriteOnly Property InternalViewName() As String
  Set
    _viewName = value
  End Set
End Property

[C#]
internal Guid InternalTaskId
{
  set { _taskId = value; }
}

internal string InternalNavigationGraph
{
  set{ _navigationGraph = value; }
}

internal string InternalViewName
{
  set{ _viewName = value; }
}

The following code examples show how to initialize, cache, and access the controller.

[Visual Basic]
Private _controller As ControllerBase

Public ReadOnly Property Controller() As ControllerBase _
  Implements IView.Controller
  Get
    Return _controller
  End Get
End Property

Public Sub myBaseView_Load ([source] As Object, e As EventArgs)
  ' This event must be linked to the Load event of the derived 
class in
  ' the class constructor
  _controller = UIPManager.InitializeController(Me)
End Sub

' Constructor
Public Sub New()
  AddHandler Me.Load, AddressOf myBaseView_Load
End Sub

[C#]
private ControllerBase _controller;

public ControllerBase Controller
{
  get { return _controller; }
}

public void myBaseView_Load(object source, EventArgs e)
{
  // This event must be linked to the Load event of the derived 
class in
  // the class constructor
  _controller = UIPManager.InitializeController( this );
}

// Constructor
public MyBaseView()
{
  this.Load += new EventHandler(myBaseView_Load);
}
Note   If you are developing views based on Windows Forms, you must verify that the form is not in design mode before initializing the controller to avoid throwing a design time exception.

The following code examples show how to implement the methods defined in the IView interface.

[Visual Basic]
ReadOnly Property ViewName() As String Implements IView.ViewName
  Get
    Return _viewName
  End Get
End Property

ReadOnly Property TaskId() As Guid Implements IView.TaskId
  Get
    Return _taskId
  End Get
End Property

ReadOnly Property NavigationGraph() As String Implements 
IView.NavigationGraph
  Get
    Return _navigationGraph
  End Get
End Property

[C#]
string IView.ViewName
{
  get{ return _viewName; }
}

Guid IView.TaskId
{
  get{ return _taskId; }
}

string IView.NavigationGraph
{
  get{ return _navigationGraph; }
}

Create the Application Views

After you implement your custom view class, you can derive the application views from it, reference them in the application configuration file, and use them in the same way that you have used derivations of the WebFormView, WindowsFormView, or WindowsFormControlView classes.

Creating a Custom IViewManager Implementation

When implementing custom views, you must also implement a custom view manager and other view creation and management code. You should only create your own custom IViewManager implementation if the versions supplied in the UIP Application Block (WebFormViewManager, WindowsFormViewManager, and WizardViewManager) do not meet your requirements. To create a custom IViewManager implementation, you must do the following:

  1. Reference the IViewManager interface.
  2. Code the custom View Manager class.

Each of these procedures is discussed in the following subsections.

Referencing the IViewManager Interface

To create a custom view, you must first inherit from the IViewManager interface defined in the block. The following example code shows how to do this.

[Visual Basic]
Imports Microsoft.ApplicationBlocks.UIProcess
Public Class NewViewManager
  Implements IViewManager

[C#]
using Microsoft.ApplicationBlocks.UIProcess;
public class NewViewManager : IViewManager

Coding the Custom View Manager

Your custom code must implement each of the methods defined in the interface. For the methods that you do not use in your code, simply return true.

The following code shows an example of a custom Windows Form view manager, including the declarations for the member variables used. The following example code shows how to keep track of which forms are already active, enables reuse of that form if appropriate, and closes the previous form when a new one is loaded.

[Visual Basic]
'Stores active forms
Private Shared ActiveForms As New Hashtable()
'Stores active views
Private Shared ActiveViews As New Hashtable()
'Stores active views
Private Shared Properties As New Hashtable()

Public Sub StoreProperty(taskId As Guid, name As String, value As 
Object) _
                         Implements IViewManager.StoreProperty
  If Properties(taskId) Is Nothing Then
    Properties(taskId) = New Hashtable()
  End If
  CType(Properties(taskId), Hashtable)(name) = value
End Sub

Public Function IsRequestCurrentView(view As IView, stateViewName 
As String) _
                                     As Boolean _
                                     Implements 
IViewManager.IsRequestCurrentView
  ' Not needed for Windows Forms
  Return True
End Function

Public Sub ActivateView(previousView As String, taskId As Guid, _
                        navigationGraph As String, view As String) 
_
                        Implements IViewManager.ActivateView
  If ActiveForms(taskId) Is Nothing Then
    ActiveForms(taskId) = New Hashtable
    ActiveViews(taskId) = New Hashtable
  End If

  Dim taskActiveForms As Hashtable = CType(ActiveForms(taskId), 
Hashtable)
  Dim taskActiveViews As Hashtable = CType(ActiveViews(taskId), 
Hashtable)
  Dim newBaseView As NewBaseView = CType(taskActiveForms(view), 
NewBaseView)

  If Not (newBaseView Is Nothing) Then
    'Use the existing instance
    newBaseView.Activate()
  Else
    ' Locate form type info from config file
    ' . . .
    ' Create a new instance of the form
    ' . . .
    newBaseView.InternalTaskId = taskId
    newBaseView.InternalNavigationGraph = navigationGraph
    newBaseView.InternalViewName = view

    taskActiveForms(view) = newBaseView
    taskActiveViews(newBaseView) = view

    AddHandler newBaseView.Activated, AddressOf Form_Activated
    AddHandler newBaseView.Closed, AddressOf Form_Closed

    newBaseView.Show()
  End If

  If Not (previousView Is Nothing) Then
    ' Get the previousView form details from config file
    ' . . .
    If Not (previousForm Is Nothing) Then
      previousForm.Close()
    End If
  End If
End Sub

Public Function GetCurrentTasks() As Guid() _
                                Implements 
IViewManager.GetCurrentTasks
  Dim tasks As New ArrayList
  Dim key As Guid
  For Each key In ActiveViews.Keys
    tasks.Add(key)
  Next key

  Return CType(tasks.ToArray(GetType(Guid)), Guid())
End Function

[C#]
//Stores active forms
private static Hashtable ActiveForms = new Hashtable();
//Stores active views
private static Hashtable ActiveViews = new Hashtable();
//Stores active views
private static Hashtable Properties  = new Hashtable();

public void StoreProperty(Guid taskId, string name, object value)
{
  if( Properties [taskId] == null )
    Properties [taskId] = new Hashtable();
  ((Hashtable)Properties [taskId])[name] = value;
}

public bool IsRequestCurrentView(IView view, string stateViewName)
{
  // Not needed for Windows Forms
  return true;
}

public void ActivateView(string previousView, Guid taskId, string 
navigationGraph,
                         string view)
{
  if( ActiveForms[ taskId ] == null )
  {
    ActiveForms[ taskId ] = new Hashtable();
    ActiveViews[ taskId ] = new Hashtable();
  }
  Hashtable taskActiveForms = (Hashtable)ActiveForms[ taskId ];
  Hashtable taskActiveViews = (Hashtable)ActiveViews[ taskId ];
  NewBaseView newBaseView = (NewBaseView)taskActiveForms[ view ];

  if (newBaseView != null)
  {
    //Use the existing instance
    newBaseView.Activate();
  }
  else
  {
    //Locate form type info from config file
    . . .
    //Create a new instance of the form
    . . .
    newBaseView.InternalTaskId = taskId;
    newBaseView.InternalNavigationGraph = navigationGraph;
    newBaseView.InternalViewName = view;

    taskActiveForms[ view ] = newBaseView;
    taskActiveViews[ newBaseView ] = view;

    newBaseView.Activated += new EventHandler(Form_Activated);
    newBaseView.Closed += new EventHandler(Form_Closed);

    newBaseView.Show();
  }

  if( previousView != null )
  {
    // Get the previousView form details from config file
    // . . .
    NewBaseView previousForm =
    (NewBaseView)taskActiveForms[previousView];

    if (previousForm != null )
    {
      previousForm.Close();
    }
  }
}


public Guid[] GetCurrentTasks()
{
  ArrayList tasks = new ArrayList();
  foreach( Guid key in ActiveViews.Keys )
  {
    tasks.Add( key );
  }
  return (Guid[]) tasks.ToArray( typeof(Guid) );
}

To keep track of which forms are loaded, you must add event handlers for the Activated and Closed event of each form.

[Visual Basic]
Private Sub Form_Activated([source] As Object, e As EventArgs)
  Dim newBaseView As NewBaseView = CType([source], NewBaseView)
  Dim state As State = newBaseView.Controller.State

  Dim taskActiveViews As Hashtable = 
CType(ActiveViews(state.TaskId), Hashtable)

  Dim currentView As String = CStr(taskActiveViews([source]))

  If Not (currentView Is Nothing) Then
    state.CurrentView = currentView
  End If
End Sub

Private Sub Form_Closed([source] As Object, e As EventArgs)
  Dim newBaseView As IView = CType([source], IView)

  Dim taskActiveViews As Hashtable = 
CType(ActiveViews(newBaseView.TaskId), _
                                           Hashtable)
  Dim taskActiveForms As Hashtable = 
CType(ActiveForms(newBaseView.TaskId), _
                                           Hashtable)

  Dim currentView As String = CStr(taskActiveViews([source]))
  If Not (currentView Is Nothing) Then
    taskActiveForms.Remove(currentView)
    taskActiveViews.Remove([source])
  End If
End Sub

[C#]
private void Form_Activated( object source, EventArgs e)
{
  NewBaseView newBaseView = (NewBaseView)source;
  State state = newBaseView.Controller.State;

  Hashtable taskActiveViews = 
(Hashtable)ActiveViews[state.TaskId];

  string currentView = (string)taskActiveViews[source];

  if( currentView != null )
    state.CurrentView = currentView;
}

private void Form_Closed( object source, EventArgs e)
{
  IView newBaseView = (IView)source;

  Hashtable taskActiveViews = 
(Hashtable)ActiveViews[newBaseView.TaskId];
  Hashtable taskActiveForms = 
(Hashtable)ActiveForms[newBaseView.TaskId];

  string currentView = (string)taskActiveViews[source];
  if( currentView != null )
  {
    // Unhook event handlers
    // Code omitted for brevity

    taskActiveForms.Remove( currentView );
    taskActiveViews.Remove( source );
  }
}
Note   After you implement a custom view manager, you need to update the application configuration file with the type information for that class.

Custom View Management Code

After you create the custom view class, the application views, and the custom view manager, you need to add code to execute the creation of the views, the retrieval of configuration information for the views, and the management of views. You can do this either by writing your own versions of the UIPConfiguration classes and the Factory classes, or by customizing the code in the UIP Application Block to allow external classes to access the relevant code blocks.

Persisting Task Information Between User Sessions

One of the goals of the UIP Application Block is to provide the ability for users to suspend a session and then be able to restart it from the same point without losing any information. You can do this by creating a class that implements the ITask interface within your application. The class is then used to save and load a user/task correlation in persistent storage (for example, a SQL Server database). You can then call the StartNavigationTask, StartOpenNavigationTask, or StartUserControlsTask overloaded methods on the UIPManager with the task ID or task object, which the manager can then use to load the existing state for that task.

It is the responsibility of the client application to implement the user/task correlation and look up information, and also to ensure that a user does not try to work on the same task in two instances of the application at the same time. The block does not support multiple instances of the same task and this may result in corruption of state.

Implementing the ITask Interface

To implement the ITask interface, you must first create a class that references the UIPv2 Application Block and add ITask to the base class list. This is shown in the following code example.

[Visual Basic]
Imports System
Imports Microsoft.ApplicationBlocks.UIProcess
Public Class MyTask
  Implements ITask
End Class

[C#]
using System;
using Microsoft.ApplicationBlocks.UIProcess;
public class MyTask : ITask

Your class should include member variables that can be used to store the user information and task ID of a particular task. In the following code example, a user name and a task ID are used.

[Visual Basic]
Private _userName As String = ""
Private _taskId As Guid = Guid.Empty

[C#]
private string _userName = "";
private Guid _taskId = Guid.Empty;

Your class should contain constructors that allow for whatever information is required to correlate the user to the specific task. This could include an overload that takes a user information parameter to look up the task ID for that user. Another option is to pass user information and a human readable task identifier that can be used to look up a task ID when a single user may be working on multiple tasks. The following code contains examples of both of these overloads.

[Visual Basic]
Public Sub New(ByVal userName As String)
  _userName = userName
  _taskId = MyController.GetUserTaskId(_userName)
End Sub

Public Sub New(ByVal userName As String, ByVal taskName As String)
  _userName = userName
  _taskId = MyController.GetTaskIdFromTaskName(taskName)
End Sub

[C#]
public MyTask(string userName)
{
  _userName = userName;
  _taskId = MyController.GetUserTaskId( _userName );
}

public MyTask(string userName, string taskName)
{
  _userName = userName;
  _taskId = MyController.GetTaskIdFromTaskName( taskName );
}

The class must also implement the two methods defined in the ITask interface:

  • Create method — This method is used by the appropriate StartNavigationTask, StartOpenNavigationTask, or StartUserControlsTask method on the UIP manager to write the task information to the persistent storage when a new task is created. The following code is an example of an implementation of this method.
    [Visual Basic]
    Public Sub Create(ByVal taskId As Guid) Implements ITask.Create
      _taskId = taskId
      MyController.WriteTaskInfoToDB(_userName, _taskId)
    End Sub
    
    [C#]
    public void Create(Guid taskId)
    {
      _taskId = taskId;
      MyController.WriteTaskInfoToDB( _userName, _taskId )
    }
    
  • Get method — This method is used by the by the appropriate StartNavigationTask, StartOpenNavigationTask, or StartUserControlsTask method on the UIP Manager to retrieve the task id of an existing task. The following code shows how to implement this method.
    [Visual Basic]
    Public Function [Get]() As Guid Implements ITask.Get
      Return _taskId
    End Function
    
    [C#]
    public Guid Get()
    {
      return _taskId;
    }
    

Starting the Task

Each StartNavigationTask, StartOpenNavigationTask, or StartUserControlsTask method on the UIP manager has an overload that takes the name of the navigator and a task object or a task ID. This is the version that you should use when you need the ability to suspend and restart tasks. The following code is an example of how to use this overload. For more information see the section, "Starting and Resuming Tasks," in Chapter 3, "Developing Applications with the UIP Application Block."

[Visual Basic]
Private Sub Start_Click(sender As Object, e As System.EventArgs)
  UIPManager.StartNavigationTask("MyNavGraph", New 
MyTask(userName.Text))
End Sub

[C#]
private void Start_Click(object sender, System.EventArgs e)
{
  UIPManager.StartNaviagationTask("MyNavGraph", new 
MyTask(userName.Text));
}

Start | Previous | Next

patterns & practices Developer Center

Retired Content

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

Did you find this helpful?
(1500 characters remaining)
© 2013 Microsoft. All rights reserved.