Archive for January, 2008

How to implement complex UI composition in xLim?

Here’s another post that continues the topic of xLim extensibility and originates from Ayende’s blog. He has posted on an interesting problem of view composition. Here’s quick possible solution for the initial problem within the xLim 2 concept:

1. Define reusable composite view that is just a wrapper for the list of views and their parameters

public sealed class CompositeView : DesktopViewBase<CompositeViewParameters>
{
   protected override void OnLoad(CompositeViewParameters parameters)
   {
      Parameters = parameters;
   }
   public CompositeViewParameters Parameters { get; private set; }
}

2. Define reusable CompositeDesktopWorkspace class that can take any number of controls as an argument and create PanelWorkspace for each one.

public class CompositeDesktopWorkspace :
   Workspace<CompositeView, IViewInfoReader>
{
   public CompositeDesktopWorkspace(Control[] containerControls)
   {
      _workspaces = new IWorkspace[containerControls.Length];
      for (int i = 0; i < containerControls.Length; i++)
      {
         _workspaces[i] = new PanelWorkspace(containerControls[i]);
      }
   }

When asked for a view, CompositeDesktopWorkspace should load it. Load operation will fail if the view being loaded is not CompositeView, although one could add logic that will simply load any non-composite desktop view directly into the first PanelWorkspace.

   public void ShowViewByName(string name, IViewInfoReader info)
   {
      UnloadCurrentCompositeView();

      _viewContainer = _workspaceContainer.CreateInnerContainer();
      CompositeView view =
         _viewContainer.ResolveByName<CompositeView>(name);
      // this will end up in some code checks and a call to "OnShow"
      Show(view, info);
   }

Since the composite view is not a real view, we do not display it, but rather use its typed properties to load the appropriate sub-views. All the subviews are created within the shared _viewContainer. As result, if some sub-views require shared controller/manager (ICalendarSyncController, for example) in their constructors, then the first view will create that controller and the subsequent views will hook up to the existing instance.

   protected override void OnShow(CompositeView view,
      IViewInfoReader viewInformation)
   {
      view.LoadInfo(viewInformation);
      CompositeViewParameters parameters = view.Parameters;

      if (parameters.Count != _workspaces.Length)
         throw new InvalidOperationException();

      for (int i = 0; i < _workspaces.Length; i++)
      {
         _workspaces[i].SetContext(_viewContainer);
         _workspaces[i].ShowViewByName(parameters.ViewNames[i],
            parameters.ViewInfos[i]);
      }

      Activate(view);
   }

Notes:

  • The shared controllers/managers have to be registered in IoC as container-owned singleton instances.
  • if the composite workspace allows opening and closing of sub views, everything will keep on working. If new view being opened needs some shared controller/manager to sync with or subscribe to, then it will pull it from the constructor (thus automatically hooking to the over views). The drawback is that all shared controllers/managers will be disposed only when the workspace closes the entire composite view.

It is quite easy to add on-the-fly configuration of the composite view by the end-users. User could have XtraLayoutControl (or its analogue) to customize the layout. When he is done with the customization, the workspace is reinitialized with the set of panels that were created within the LayoutControl (PanelWorkspace is created out of each one); then the CompositeViewParameters object is simply assembled based on the actual items that the user has dropped there. The assembled information is passed to the Composite workspace (and optionally persisted in the appropriate IDataNode). IoC and DI will settle down the rest.

I didn’t realize you could do that.

PS: One could find more information on workspaces and UI composition while reading the sources for the Microsoft Composite Application Block. Plus there’s always list of posts on xLim 2 architecture.

How to implement DSL functionality efficiently?

The last post on the extensibility in xLim series turned out to be information-tense, and yet far from being complete. I’ll continue to concentrate on the topic.

Take a look at this DSL code from Ayende’s blog:

operation "/account/login"

if Principal.IsInRole("Administrators"):
   Allow("Administrators can always log in")
   return

if date.Now.Hour < 9 or date.Now.Hour > 17:
   Deny("Cannot log in outside of business hours, 09:00 - 17:00")

operation "/order/aprrove"
if Principal.IsInRole("Managers"):
   Allow("Managers can always approve orders")
   return

if Entity.TotalCost >= 10_000:
   Deny("Only managers can approve orders of more than 10,000")

Allow("All users can approve orders less than 10,000")

That looks to be concise, nice and flexible, is not it?

Well, .NET can do that too. Take a look at this code now:

public sealed class OrderSecurity : FastSecurityBase
{
   public OrderSecurity(IPrincipal principal, Order entity)
   {
      if (principal.IsInRole("Managers"))
         Allow("Managers can always approve orders");

      else if (entity.TotalCost >= 10000)
         Deny("Only managers can approve orders of more than 10,000");

      else Allow("All users can approve orders less than 10,000");
   }
}

public sealed class LogonSecurity : FastSecurityBase
{
   public LogonSecurity(IPrincipal principal)
   {
      if (principal.IsInRole("Administrators"))
         Allow("Administrators can always log in");
      else if ((DateTime.Now.Hour < 9) || (DateTime.Now.Hour > 17))
         Deny("Cannot log in outside of business hours, 09:00 - 17:00");
   }
}

Now, here are the points why the last approach is better:

  • Flexibility - you are not limited to the DSL-supported features and can do whatever could be done with .NET
  • Clarity - you can read the code
  • Maintenability - you do not need DSL parser to write and maintain, you can write unit tests for this code.
  • Simplicity - you do not have some cryptic syntax to document, there’s IntelliSense, R# support and compile-tme checking.

And the most important reason is that the last approach is efficient. I would not dare to implement DSL in a project that has severe constraints on time, resources and budget (quality is fixed and high). And the last approach… well, it took me just 30 minutes to come up with this sample that plugs into the code and could be used.

Here’s the “framework” code that’s needed for this security “DSL”

public sealed class Ruling
{
   public string Reason { get; private set; }
   public bool IsAllowed { get; private set; }

   public Ruling(bool isAllowed, string reason)
   {
      IsAllowed = isAllowed;
      Reason = reason;
   }
}

public interface ISecurityPolicy
{
   Ruling GetRuling();
}

public abstract class FastSecurityBase : ISecurityPolicy
{
   private Ruling _ruling = new Ruling(false,"Denied by default");

   protected void Allow(string message)
   {
      _ruling = new Ruling(true, message);
   }

   protected void Deny(string message)
   {
      _ruling = new Ruling(false, message);
   }

   public Ruling GetRuling()
   {
      return _ruling;
   }
}

With that you just need to load the policy assemblies into the IoC (batch registration would do it, although I prefer modular approach) and then just ask for for the specific policy like this:

_loadContext = ViewContext.CreateInnerContainer();
_loadContext.Register(order);
ISecurityPolicy policy = _loadContext.ResolveByName<ISecurityPolicy>(policyName);
Ruling ruling = policy.GetRuling();

“_loadContext” and “ViewContext.CreateInnerContainer();” - these names come from the last sample snippet in post on xLim extensibility (you could ignore these and just use any IoC container that provides the proper support for scopes).

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:

How to run XPO in hosting environment with some CAS restrictions?

Here’s the small code snippet on enabling/disabling some XPO features depending on the hosting environment. Just plug it in your kernel initialization routine and you should be fine.

try
{
   new ReflectionPermission( ReflectionPermissionFlag.ReflectionEmit).Demand();
   new SecurityPermission( SecurityPermissionFlag.ControlEvidence).Demand();
}
catch (SecurityException)
{
   XpoDefault.UseFastAccessors = false;
}

Update: If you use data caching, then you might need to manually set the XPO cache size for your DataCacheNode instances (otherwise XPO will attempt to detect it automatically via the code that cannot run under the Medium Trust). Here’s the sample code snippet:

if (manualCacheSizeMb > 0)
{
   node.TotalMemoryPurgeTreshhold = 0;
   node.TotalMemoryNotPurgeTreshhold = manualCacheSizeMb*1024*1024;
}

So far these are the only CAS-related problems that were encountered while deploying current xLim 2 implementation (Server+WebUI) on the CrystalTech accounts.

Experience of migrating from Castle to autofac

I’ve just finished porting current xLim 2 implementation from WindsorContainer to autofac.

It took about 14 hours total to have this out of my way (this also included adding C# 3.5 features to all 20 projects in the primary solution while keeping the output .NET 2.0 compatible).

Disadvantages:

  • Configuration files got clumsier. Autofac does not support includes and property aliases, yet.
  • When we had Castle I didn’t see the possibility to cleanup the logical organization of xLim solutions at business/configuration level. Now I do see that. It hurts to see the improvement that’s not feasible at the moment.

Benefits:

  • We had to use modified Castle sources in order to be able to run xLim under medium trust level of some hosting providers (minus one point for the maintainability). Autofac just works.
  • 5 Castle assemblies were replaced by 1 small autofac library. Getting rid of extra code is efficient.
  • We had to manually copy transient ComponentModels from the upper scope to the child scope (or register them manually) to keep the resolution bound to the current context. This code was also gone, since Autofac inherently supports multiscoping.
  • There’s greater flexibility in controlling lifecycle of views, commands and workflows. This will be utilized.

On the overall - Castle is a great solution, but autofac is a better tool for the specific purpose of building xLim 2.

Organization of xLim solutions: development, svn and integration 2

In the last post in xLim 2 series I’ve talked about the overall folder organization of the xLim solutions (including documentation, version control and integration). Let’s drill down to the scope of the atomic Visual Studio solution.

Caveat, Reader: although this solution structure is based on multiple articles, guidelines and delivered projects, it is tailored specifically for the xLim 2 or similar implementations (see the requirements page).

General guidelines for the xLim solutions

  • Every solution should be atomic. It should not contain any links or references to any projects, resources or libraries outside the trunk directory
  • If the solution has to reference code from the other solutions (like it is done by any extension project), then the referenced code should be in binary form and located in the trunk\Resource\[ReferencedSolution]
  • All assets that are used in the process of integration, build and distribution (i.e.: build tools, templates for help generation, images, xsl transforms, code analysis tools etc) should be stored in the Resources where appropriate (versioning .NET 2.0 SDK redistributables is not a good idea).
  • All machine-specific config files (web.config, app.config) should be stored in SVN as *.config.template.
  • Visual studio solution file is named [Solution] and located in the trunk\ directory. Secondary solution files are allowed (i.e.: solution w/o Tests, solution with examples)
  • Primary build file should be located in the same directory with the .sln and be named [Solution].build
  • Every primary build file should contain following targets
    • clean - launches MSBuild  for the [Solution].sln, wipes trunk\Build and all temporary files and build artifacts
    • build - generates temporary “VersionAssemblyInfo.cs” (if version parameter is passed), then launches MSBuild for the [Solution].sln
    • copy - copies relevant build output files into the appropriate Build\[Name] directory
    • distrib - produces redistributables from the Build\[Name] files and places them in Build\Distrib (integration server can override this with parameter)
    • test - runs unit tests for the [Build]\Test
    • integrate - executes clean, build, copy, test
    • config - configures hosts in the Build\[Name] using the passed parameter as a reference to the configuration folder
    • release - executes integrate, config, distrib
  • Every trunk\ directory should contain “go.cmd” file that has the code
@echo off
@Resource\\NAnt\\NAnt.exe -buildfile:[Solution].build %*

Guidelines for the source projects

  • Source projects should be located in the trunk\Source directory. They should be named in [Solution].[Section].[AssemblyType] format where
    • [Solution] bears the name of the implementation project (e.g. “xLim2″) or the name of the extension (e.g. “xLim2.Crm”)
    • [Section] logically groups project by the implementation specifics:
      • Shared - code that is common to all Sections areas
      • Server - xLim server code
      • Client - code for the desktop interface
      • Web - web interfaces
      • Engine - code for the automation “interface”
      • Tool - different utilities and command-line helpers normally get this namespace
    • [AssemblyType] specifies type of the code within the assembly:
      • Interface - interfaces and common types.
      • Core - actual implementations of the items declared in the interfaces.
      • Host - entry points for the
        • Web - web application)
        • Client - Win.Forms application)
        • Server - either embedded into web, windows service or separate IIS hosted web service
      • There could additional types of code libraries if needed (i.e.: Shared.Data, Client.DxCore, Web.DxCore)
    • There should be 2 files in the Source directory
      • VersionAssemblyInfo.cs
        using System.Reflection;
        
        [assembly : AssemblyVersion("1.0.0.0")]
        [assembly : AssemblyFileVersion("1.0.0.0")]
      • GlobalAssemblyInfo.cs. For example:
        using System.Reflection;
        
        [assembly : AssemblyCompany("Landor Systems")]
        [assembly : AssemblyProduct("xLim 2 Framework")]
        [assembly : AssemblyCopyright("Copyright © Landor Systems 2008")]
        [assembly : AssemblyTrademark("")]
    • All VS projects in the Source folder should reference these source files as links.
  • Source projects should follow these reference rules:
    • [Section].Interface can reference only Shared.Interface and framework assemblies (no 3rd party components)
    • Any [Section].* can reference Interface library from the same Section
    • Any [Section].* can reference Shared.Interface, any other references between the sections are prohibited.
  • Code for the [Solution].[Section].[Type] has default namespace of [Solution].[Section]

Guidelines for the test projects

  • Test projects are located in trunk\Test directory
  • Project that tests [Solution].[Section].[Type] should be named [Solution].[Section].[Type].Test
  • Test project can reference only [Solution].Test (common test helpers), assembly being tested or any of it’s references.

Note, that this article will be updated as xLim 2 requirements get settled down.

World’s simplest validation “framework” in .NET 3.5

Recently I’ve faced an interesting problem. The task engine for the xLim 2 type of architecture had to package some internal data and send it to the 3rd party business server. That remote server performed simple validation and then checked every submitted entity against internal business rules (it has a lot of these). The rules are checked one by one, and as soon as any rule fails, the failure result is returned along with the error description.

For some strange reason the validation takes a huge amount of time, and only the first failure is returned.

Obviously that’s not the friendliest integration situation. To prevent delays in the xLim 2 workflows I had to get some rules out of the server’s documentation and implement them in a scoped manner (you check all the rules that you can and then move to the next scope if there are no blocker errors).

The interesting part here is that only two small classes were needed to implement all the required validation rules without any reflection.

Simple generic rule class:

public sealed class Rule<T>
{
  private readonly Func<T, bool> _body;
  private readonly Func<T, string> _error;

  public Rule(Func<T, bool> body, Func<T, string> error)
  {
    _body = body;
    _error = error;
  }

  public bool IsValid(T item)
  {
    return _body(item);
  }

  public string GetError(T item)
  {
    return _error(item);
  }
}

Extension class from the same namespace:

public static class RuleExtension
{
  public static void Add<T>(this ICollection<Rule<T>> rules,
    Func<T, bool> assert, string error)
  {
    rules.Add(new Rule<T>(assert, c => error));
  }
  public static void Add<T>(this ICollection<Rule<T>> rules,
    Func<T, bool> assert, Func<T, string> error)
  {
    rules.Add(new Rule<T>(assert, error));
  }

  public static void Validate<T>(this ICollection<Rule<T>> rules,
    T item, IScope scope)
  {
    foreach (Rule<T> rule in rules)
    {
      if (!rule.IsValid(item))
      {
        scope.Write(rule.GetError(item));
      }
    }
  }
}

With that simple code I was able to declare different rules in quite compact manner.

Rule declaration:

var rules = new List<Rule<Project>>();
rules.Add(p => p.Starts < p.Ends, Resources.ProjectEndShouldBeAfterStart);
rules.Add(p => p.Budget >= 0, Resources.BudgetMustBePositive);
rules.Add(p => p.Ends > DateTime.Now,
  p => string.Format(Resources.Date0CantBeInPast, p.Ends));

And in order to validate some project instance against this rule-set, you just need to call Validate method (you can use simple ILog implementation instead of the IScope for this reference).

Validation:

rules.Validate(project, projectScope);

The rule-sets could be organized in extension modules and registered into the IoC system by name. Then you’d just load the proper set for the proper validation scope and run it.

This simple “framework” could be easily extended (yes, that just requires more of these small extension methods) to support more complex patterns like:

rules.IsNotNullOrEmpty(p => p.Name, "Name");
rules.CheckRegex(p => p.Code, new Regex("[A-Z]{4,6}"), "Code");
rules.ValidateStringLength(p => p.Name, 5, 10, "Name");

This approach is faster than the attribute based validation (check out the Validation Application Block for the example of that), simpler (just count the classes in VAP), and it separates validation logic from the actual business objects (you do not need to recompile your business objects if the validation logic has been changed.

This code feels to be quite efficient for the 30 minutes it took to write and cleanup it. And it has some decent chances of making it into the core shared libraries of the current xLim 2 implementation.

I wonder, if VAB can do something that’s not possible here…