X is for “eXtensible” - inversion of control container in xLim

That’s how views are handled within the Web and Desktop clients according to the xLim 2 approach.

The views are declared in the code like this

// web module
public sealed class WebFormView : WebViewBase<FormParameters>
public sealed class WebGridView : WebViewBase<GridParameters>
...
// desktop module
public sealed DesktopFormView : DesktopViewBase<FormParameters>
public sealed DesktopGridView : DesktopViewBase<GridParameters>

Views (along with the workflow controllers, commands, translators, etc) are registered in the appropriate module when it is being hooked up by the IoC. Normally they have container scope and ownership (unless they are reusable, thread-safe and do not have any context-specific dependencies).

Here’s real-life example of such registration:

public class CoreWebModule : Module
{
   protected override void Load()
   {
      RegisterWebView<WebCommentsView>(CommentsParameters.HandlerName);
      RegisterWebView<WebAdminView>(AdminView.ComponentName);
      RegisterWebView<WebDocumentView>(DocumentParameters.HandlerName);
      RegisterWebView<WebFormView>(FormParameters.HandlerName);
      RegisterWebView<WebGridView>(GridParameters.HandlerName);
      ...

Interface-specific base classes for views (WebViewBase, DesktopViewBase, WapViewBase etc) inherit from the core ViewBase class that could have the following declaration:

public abstract class ViewBase<T> : IView
{
   ...
   protected Container ViewContext { get { return _viewContext; } }
   protected abstract void OnLoad(T properties);
   protected abstract void OnUnload();
   ...
}

Views are displayed by the IWorkspace implementations (i.e.: tabbed workspace, panel workspace, window workspace, composite web workspace etc) like this:

IWorkspace space = container.ResolveByName<IWorkspace>(workspaceName);
space.ShowViewByName(viewName, info);

Each workspace knows how to handle its views. In addition it manages the container scopes for the view. Simple implementation for the “ShowViewByName” of the desktop panel workspace could be like this:

public void ShowViewByName(string name, IViewInfoReader info)
{
   IView view;
   if (!_views.TryGetValue(name, out view))
   {
      view = _workspaceContainer.ResolveByName<IView>(name);
      _views.Add(name , view);
   }
   Show(view, info);
}

and the actual “void Show(IDesktopView view, IViewInfoReader info)” method simply deserializes the information (which is securely stored and passed behind the scenes) to the view-specific property type, does some maintenance and calls the “abstract void OnLoad(T properties)” of the view.

Simple XAF-related examples of this are WebGridView and DesktopGridView implementations. They do what their names imply - display some data in grid format, while allowing it to be edited (just like XAF does). The specific behavior is determined by the GridParameters object and the controllers that get loaded. Here are some self-explanatory properties of this object:

public string EditSchemaName { get; set; }
public string ViewSchemaName { get; set; }
public string Criteria { get; set; }
public string CriteriaManagerName { get; set; }
public string WorkflowControllerName { get; set; }

Note, that when the view is initially created by the workspace, it is being passed some context-specific container via the DI. Normally the workspace passes its own container, that is the child of shellContainer, but could have same useful overrides (i.e. window workspace overrides the BarManager by providing window-specific bar manager to extend to; or, if the every view is opened in a new window, then there’s new view scope for every view).

Since scope creation is a cheap operation, the view might want to create one more scope within the OnLoad method, and then use it to resolve specific controllers while optionally registering some objects that are specific to this operation (commands usually rely on these). Note, that in this case all new objects with container scope and lifestyle will be disposed along with the owner container (and that should happen in “ViewBase.OnUnload”).

Here’s some imaginary implementation for the WebFormView (reality is a bit more complicated):

protected override void OnLoad(FormParameters parameters)
{
   _loadContext = ViewContext.CreateInnerContainer();

   IDocumentSecurity security =
   _loadContext.ResolveByName<IDocumentSecurity>(parameters.SecurityWorkflow);

   DocumentPolicy policy = security.GetPolicyForDocument(parameters.FormId);
   LoadCommands(_loadContext, policy);
   UpdateControls(policy);

   Form form = _session.LoadByKey<Form>(parameters.FormId);
   LoadForm(form);
   ...
   // validating form when needed
   IFormValidator validator =
      _loadContext.ResolveByName<IFormValidator>(form.ValidatorName);
   var result = validator.Validate(form);
   ...
}

Note how:

  • we use the local scope to resolve workflow controllers hidden behind “IDocumentSecurity” and “IFormValidator”. Specific implementations of the IDocumentSecurity might ask for the IIdentiyInfo or ISecurityLevel instances and these shall be passed down to them from the appropriate context.
  • we use typed property object instead of the property bags.
  • we implement form-specific named validation policy to handle business logic, while leveraging the generic (yet named and thus replaceable) IDocumentSecurity to handle the security; names are defined in the properties/objects and thus the associated behavior be changed easily.
  • All items that are created in the _loadContext will get local objects in their constructors. If these are not found, then the upper scope will provide them.
  • workflow controllers, commands, parameter objects are not UI-dependent and thus they are reused and shared between all UI clients.

These posts extend this article:

1 Response to “X is for “eXtensible” - inversion of control container in xLim”


  1. 1 Managing xLim 2 flexibility: IoC Container Inspector at Rinat Abdullin

Leave a Reply