Welcome to NetFxFactory Sign in | Join | Help

Papers

  • Let your community site leverage Windows Live

    1. Why

    Most web sites require managing users from personal details to authentication. Event though, ASPNet provides some tools ease this process with a Sql Server backend database. This can be tedious to manage and implies a responsibility to keep personal data safe furthermore people put some time and effort into filling in their Windows Live Id profile information. Of course some web sites make their business on having a huge user database. However community web sites such as netfxfactory does not gain any added value from managing its own user database. Various blogs and papers on the web explain how to leverage Windows live authentication mechanism to validate a local user (see here here and here). They mostly let someone associate its web site account with his Windows Live Id however it’s name, dob, email etc… are still managed localy by the web site (this is the functionalities offered by the Memebership provider offered with the Windows Live SDK). Only the authentication mechanism is leveraged here.

    The goal of this article is to go one step further and externalize as much as possible the user management. Managing locally user’s data requires an extra effort from the user to keep his information up to date. We will demonstrate a solution using Windows Live based on a custom aspnet membership provider used to pull out information from Windows Live such as User info but also his contacts info.

    This is a first step towards S+S as promoted by Microsoft.

    2. Solution

    a. Register live application

    In order to be able to take advantage of Windows Live API one needs to register his application. This registration process requires several pieces of information such as a return Url that will be called after a successful sign in along with a set of parameters allowing to complete the authentication process on our site. The form bellow shows the information to supply:

    WindowsLiveApplicationID

    Note: In a dev environment, you can add a static DNS resolution on your dev machine in order to resolve the domain name to 127.0.0.1 Edit your host file (c:\windows\system32\drivers\etc\host)
    Note: It seems that Windows Live does not let you register domain name that does not contains “dots” in it.

    Upon completion of this form, you are being attributed an ApplicationID that your application have to provide along with the secret key.

    b. ASP.Net related

    Acknowledgement

    The membership provider mechanism offered by ASPNet offers a large functional perimeter allowing managing for instance password expiration, reset etc… Because we intend to rely as much as possible on Windows  Live there is no need to implement such functionality on our web site.

    Custom Windows Live Membership provider

    Based on the acknowledgement detailed above, the main method of AspNet Membership provider that we are interested in is ValidateUser but instead of validating a username password as we usually do, we will only have to ensure that the current user has signed in properly on Windows Live (therefore owns a valid authentication and delegation token).

    Because Windows Live authentication only returns the id of the logged in user and extra step is required to retrieve personal information about the user. This step is done in the overridden GetUser() method. A REST request is sent to retrieve information details about the user such as his/her name, email(s) and so on. this can then easily accessed throughout the site using Membership.GetUser() method.

    Custom Windows Live Membership user

    In order to facilitate the use of MembershipUser, we provide a specialized one that exposes the user’s Windows live information. Here the choice has been made to not include the complete address book but just the user’s details. A quick look at the Windows Live Api Rest Provider described bellow would do the trick.

    Custom Windows Live Contact Api Rest Provider

    This class is responsible for exposing methods that wraps calls made to the Windows Live services exposed as REST. Its main task is to build the requests to send from Windows Live Services (url constructs, token and authentication assignment as well as deserialization)

    c. Windows live

    Delegated authentication

    Windows Live delegated authentication is mechanism allowing taking advantage of Windows Live ID authentication to prove to Windows Live services (Contact, Photos, Application Storage and so on) that a trusted relationship exists between the user and a your web site. it suits perfectly the bill of our requirements, i.e. handle locally user’s data as less as possible as stated here: “Sign-in and consent management functions are performed by the Windows Live ID service, so you don't have to worry about implementing these details. Another advantage to Delegated Authentication is that Windows Live ID profile data is not shared with your Web site. As a result, you can focus more on the functionality of your application, and less on managing user data”.

    The corner stone of delegation authentication is that users own their own data. They can decide which sites get access to that data and what those sites can do on the user's behalf with that data.

    Consent management

    Windows Live let users decide what information they want to share with a web site leveraging Windows Live authentication. Anyone with a Windows Live account can manage his consents at the following address: https://consent.live.com. This page sums up all the sites you have given access to as well as the access mode (read, update, write …). You can revoke access of any of these sites at any time before your consent expires.

    Here is what the consent management page looks like:

    WindowsLive_Consent

    The Windows Live Contact consent can even be done on a per contact basis. Unfortunately, there is no way to select/unselect  a group of contact indeed it can become tedious to select/unselect individual contacts when you address book contains 30+ contacts.

    WindowsLive_AllowConsent

    d. Implementation

    1. Windows Live protocol

    Most of the protocol related code is taken from the Windows Live API samples. I won’t  drill into too much details here, have a look at the classes LiveLogin, LiveConsentToken and LiveAuthenticatedUser in the sample code provided with this paper.

    2. Windows Live delegated authentication process

    wlid del auth

    The delegated authentication process is composed of the following steps (these include the local authentication steps as well):

    1. A user requests the web site’s home page (default.aspx). Because he is not yet authenticated and forms authentication has been setup on the site he is redirected to the login page (login.aspx),
    2. The login page offers, through an iFrame pointing to Windows Live login page, a sign in link,
    3. Once the user clicks on it, he is rediricted to the classic Window Live login page,
    4. Windows Live then calls the web site that initiated the authentication (wlid.axd) with a signin action,
    5. The handler checks if the request already contains a valid Windows Live consent token. If the consent token is valid then go to step 7. If no consent token is available then a new one is requested through the Windows Live consent form,
    6. Once the user has granted the required delegation rights to the site, Windows live then calls back the handler  (wlid.axd) with a delegated authentication(delauth) action.
    7. The user is then validated against the membership provider, signed in on the web site and finally redirected to the home page.

    Steps 7 and  8 on the schema above  represents the web site requesting user’s details such as full name, emails, location etc… as the only information provided to the site by the windows live login is an ID that uniquely identifies the user as well as a token.

    Windows Live callback handler

    This callback handler is used to handle Windows Live callback upon successful authentication and consent agreement. The  address that exposes this HttpHandler is the one specified in the Windows Live Application registration form known as ReturnUrl. It is possible to add extra parameters (referred as a context) to the initial request made for authentication in order to supply additional information to the callback. For instance, one can supply a url that must be called at the end of authentication and consent agreement phase.

    The callback handler needs to be able to address 4 types of action on the callback made by Windows Live:

    • signin,
    • signout,
    • clear cookie,
    • delegated authentication

    This handler is an important piece of the process as it handles requests made by  Windows Live and act on the local web site accordingly. Its action is mostly based on redirections and local authentication.

    Windows Live Asp net membership provider

    This custom membership provider is quite different from the one that are usually presented. Because our goal is to externalize as much as possible the user management there are quite a few methods that usually requires a specific implementation but that are not relevant in our case. For exemple, ChangePassword, ChangePasswordQuestionAndAnswer, GetPassword and so on.

    Here are the methods and properties that needs to be implemented in our WindowsLiveMembershipProvider:

    Methods:

    ValidateUser,
    GetUser

    Properties:

    ApplicationName,
    EnablePasswordReset,
    EnablePasswordRetrieval,
    RequiresQuestionAndAnswer,
    RequiresUniqueEmail

    The most interesting parts of this membership provider are obviously the ValidateUser and GetUser methods. The first one ensures that the current user own a valid Windows Live token and the last returns a custom membership user that contains all the details about the user such as his name, his email address(es), location etc…

    Here is the implementation of the ValidateUser method:

    public override bool ValidateUser(string username, string password)
    {
        System.Diagnostics.Debug.WriteLine("ValidateUser");
    
        HttpCookie __consentCookie = HttpContext.Current.Request.Cookies[WindowsLiveConstants.COOKIE_CONSENT];
    
        if (__consentCookie != null)
        {
            LiveConsentToken __token = LiveMembershipProvider.LiveLogin.ProcessConsentToken(__consentCookie.Value);
            return __token.IsTokenValid;
        }
        return false;
    }
    .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

    and the GetUser method:

    public override MembershipUser GetUser(string username, bool userIsOnline)
    {
        MembershipUser __user = null;
        long __lid;
        string __delegationToken;
        if (IsConsentTokenValid(out __lid, out __delegationToken))
        {
            //get the user details from Windows live contact api
    //to improve responsiveness user details can be tested on the current user to see if they have already been retrieved
    LiveContactsRestProvider __liveContactsRestProvider = new LiveContactsRestProvider(__lid, __delegationToken); Owner __owner = __liveContactsRestProvider.GetOwnerDetails(); __user = LiveMembershipUser.CreateLiveMembershipUser(__owner, this.Name); } return __user; }
    .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

    As described earlier, because the default membership user only contains a user id, it is required to create a specialized membership user.

    Here is the details of its implementation:

        public sealed class LiveMembershipUser
            :MembershipUser
        {
            private Owner _owner;
    
            private LiveMembershipUser(Owner owner, string providerName, string name, object providerUserKey, string email, string passwordQuestion, string comment, bool isApproved, bool isLockedOout, DateTime creationDate, DateTime lastLoginDate, DateTime lastActivityDate, DateTime lastPasswordChangedDate, DateTime lastLockoutDate)
                :base(providerName, name, providerUserKey, email, passwordQuestion, comment, isApproved, isLockedOout, creationDate, lastLoginDate, lastActivityDate, lastPasswordChangedDate, lastLockoutDate)
            {
                _owner = owner;
            }
    
            public Owner Owner
            {
                get { return _owner; }
                set { _owner = value; }
            }
    
            public static LiveMembershipUser CreateLiveMembershipUser(Owner owner, string providerName)
            { 
                string __userName = String.Format("{0} {1}", owner.Profiles.Personal.FirstName, owner.Profiles.Personal.LastName);
    
                //Look up the default address if none pickup the first email available
                List __emails = owner.Emails.Where(email => email.IsDefault == true).ToList();
                string __email = (__emails.Count == 1) ? __emails[0].Address : owner.Emails[0].Address;
    
                LiveMembershipUser __user = new LiveMembershipUser(
                                                    owner,                  //owner detais
                                                    providerName,           //membership provider name
                                                    __userName,             //user's name
                                                    owner.WindowsLiveID,    //providerUserkey
                                                    __email,                //email
                                                    string.Empty,           //password question
                                                    string.Empty,           //comment
                                                    true,                   //is approved
                                                    false,                  //isLockedOut
                                                    DateTime.MinValue,      //creation date
                                                    DateTime.Now,           //last login date
                                                    owner.LastChanged,      //last activity date
                                                    DateTime.Now,           //last password change date
                                                    DateTime.MinValue);     //last lock out date
                
                return __user;
            }
        }
    .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

    3. Windows Live Contact data (lack of) API

    Microsoft does not provide (yet) a .Net object model representing the Windows Live Contact data, the only thing close is the schema of the data returned by the REST service. So I had to build up my own. Here is the class diagram representing the Windows Live Contact object model as I represented it:

    LiveContacts

    4. Windows Live Contact Rest provider

    This last piece is nothing more than a helper allowing to query the Windows Live Contact services exposed as REST. It is in charge of building the request url, provide the authentication details , call the service and deserialize the xml returned into .Net objects described in the previous step.

    The authentication is provided by a specific header added to the request:

    __request.Headers.Add(WindowsLiveConstants.DELEGATION_HEADER_KEY, 
    String.Format(WindowsLiveConstants.DELEGATION_HEADER_VALUE_FORMAT, _delegationToken));
    .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

    The header must conform to the following pattern:

    public const string DELEGATION_HEADER_VALUE_FORMAT = "DelegatedToken dt=\"{0}]\"";
    .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

    5. Use a user’s contact list

    Once the user is logged in on the site, we can now use Windows Live Contacts to retrieve his friends. In the sample provided with this article, the about.aspx page lists the user’s friends that have published on NetFxFactory.

    The sample uses rss feeds to get the posts from NetFxFactory and populate this sample site.

    Now, we can look up the user’s contacts in order to retrieve those that published something on NetfxFactory*. Then we easily provide a list of friends as well as a link to a page listing all the posts made by a particular contact.

    *Acknowledgement: Because NetFxFactory is powered by Community Server an helper has been done in order to map IDs from CS to Windows Live unique IDs.

    f. Sign in on Windows Live with Cardspace

    Windows Live allows you to associate a Carspace card to your Windows Live account in order to sign in via Cardspace instead of the usual email/password pair. This saves you from handling Cardspace on your site (specially the required SSL certificate used to authenticate the web site requesting the Card), simply delegate the work to Windows Live that will handle request and validation of the card.

    To associate a card to your Windows Live account, navigate here and follow the registration process. Once completed, you get the following options when signing in Windows Live:

    WindowsLive_Cardspace

    3. Samples

    An implementation of NetFxFactory meeting Windows Live is available at this address: http://live.netfxfactory.org. This sample is an ASPNET application that uses Windows Live delegated authentication, as demonstrated in this paper, to logon the site. Once logged in, the user is able to browse the site and display rss feeds from www.netfxfactory.org. The “My Friends” section displays all the user’s Windows Live Contacts in two groups: the user’s friends who posted an article to NetFxFactory and the friends who did not. In the group of contacts that wrote an article on NetFxfactory, there is a link to a dedicated page that displays all the articles posted by a particular contact. The source code is available here (note: you need to register an application as demonstrated above and update the web.config and login.aspx files accordingly. Check out readme.txt within the Zip archive).

    4. Going further

    As we have seen in this paper, it is pretty straight forward to take advantage of Windows Live ID to handle all most of the user management related to a community site. We voluntarily set aside the problem of storing custom data (for instance user’s role, preferences etc…) that can easily be externalized as well using Windows Live Application Base Storage (check http://dev.live.com/livedata/sdk/ for details and samples).

    a. Claims and Federation

    Taking advantage of Windows Live is great in a sense but wouldn’t  be nice to be able to raise the abstraction one level up and use claim based authentication on your web site? Using claims and Federation one would be able to target multiple identity providers such Windows Live, Facebook, Yahoo and so on, abstract their respective implementation details and only manage claim evidence on client side to deal with authentication, user management and so on. This will be the subject of a paper to come.

    b. Geneva (formerly known as Zermatt)

    Microsoft Code Name Geneva is Microsoft’s answer to federation and claim based identity. There is a thorough paper presenting Geneva and its implementation written by Keith Brown from Plurasight LLC available here. Microsoft Sharepoint is meant to go down the way of claims based authentication. Even though there are no clues as of today that Zermatt will be used as the underlying technology to implement such authentication mechanism, there are good chances that both can interoperate.

    Geneva is one of the pillars beyond Veracruz project.

    This is a hot subject as proven by the Microsoft 2008 PDC agenda and the Issue 16 of the Architecture Journal.

    c. Veracruz

    Veracruz project will rely on such a mechanism and leverage Facebook capabilities (authentication, user/profile/contact management) in conjonction with Windows Sharepoint Services in order to make identity federation at heart of the system.

  • Snip your dsl into prototypes

    Preamble

    Mary Jo Foley revealed the love story between Microsoft and modeling a few months ago. But the information that must retain our attention is that Oslo will be certainly the city chosen for this honeymoon. Indeed, a few years ago Microsoft did the first move when appointing famous modeling engineers and developing DSL Tools in order to seduce this model on the first date. Since then, Microsoft never stopped declaring its love and even promoted it as an essential pillar of its SOA strategy.

    So, for connected system developers, having a look at DSL tools seems to be a good way to understand the perspectives that Oslo can bring into the SOA panorama.

    That being said, this post targets DSL model prototyping.

    Introduction

    Everybody knows patterns promoted by the famous Gang of Four, when defining a model the same problematic applies. Some concepts are based on the same structure or a concept can be an aggregate of existing concepts. It would be very useful to be able to define reusable patterns in DSL as we can do for code.

    This post will present a solution to integrate such mechanisms into your DSL tools projects. DSL tools offer many customization points that we will use to accomplish our goal. Unlike technologies such as WCF, WPF or WF, DSL tools offers customization points rather than extensibility mechanisms. The promised feature codenamed Backplane (designer bus) or Oslo will maybe change this situation.

    This post will present a solution to integrate such mechanisms into your DSL Tools projects.

    DSL Tools offers many customization points, we'll use them to respond to our needs.

    Unlike technologies such as WF or WCF, with DSL Tools we use more customization mechanisms than extensibility mechanisms. The promised Backplane feature (designer bus) and even Oslo may lead Microsoft to change its orientation towards this and introduce more extensibility rather than customization.

    How to

    DSL Tools offers basic features to support prototyping that are used essentially to customize dragging and dropping of a dsl toolbox item onto a diagram.

    These features are essentially based on a single class: ElementGroupPrototype.

    We must not interact directly with this class. A builder design pattern was set up in order to produce a prototype. ElementGroup class must be used instead because it provides a more convenient developer experience. This class has basic methods to build your prototype from selected model elements: Add, AddGraph, AddRange. The AddGraph method adds automatically embedded children and associated relationships of the added model element. Prototyping can be seen as a clone to whom a specific property is set with an autogenerated value. Indeed, when you merge your elementgroupprototype on your model, the created model element takes into account if one of its properties has an associated ElementNameProvider allowing assigning a unique value to this property.

    These features can be used to enable copy and paste operations as well. It will be our first step, it allows to easily adopt prototype mechanisms.

    We won’t dig into the details about this as it's already described into the "Domain-Specific Development with Visual Studio DSL Tools" book that must be considered as a bible for people using DSL Tools (See page 419 "Implementing Copy and Paste").[1]

    An implementation of these operations is available as well within the library developed by the DslFactory community; this library named "DslFactory Utilities" is available on Codeplex. We will take into account various fore mentioned work. We'll add the cut operation support and enable all these operations from the visual studio standard edit menu.

    The next step will be to customize the ModelExplorer window, especially the contained tree view in order to add our own nodes and mechanisms to list user defined prototypes.

    Finally, we will take advantage of DSL tools prototype serialization to persist prototypes on disk thus allowing offering a similar mechanism as code snippets does.

    We'll use a DSL Tools solution based on the Domain-Specific Language Designer Class Diagrams template in order to present this solution. We add to this template solution a folder named “CustomCode” in the “DSL” and “DSL package” projects

    Download

    Download the complete solution here.

    A screencast showing a scenario taking advantage of the functionalities presented in this paper is available here.

    The pdf version of this article is available here.

    Enable cut, copy and paste operations

    Here is the step by step procedure:

    · Define copy, cut and paste operation logic,

    · Register these operations in order to be able to call them via the standard and contextual menus.

    Declare your new commands

    1. Open Solution Explorer.

    2. Go to the DslPackage project.

    3. Open the Commands.vsct file.

    4. Define a button for each operation

    <Button guid="guidCmdSet" id="cmdidCopy" priority="0x0902" type="Button">
      <Parent guid="guidCmdSet" id="grpidContextMain"/>
      <CommandFlag>DefaultDisabled</CommandFlag>
      <CommandFlag>DefaultInvisible</CommandFlag>
      <CommandFlag>DynamicVisibility</CommandFlag>
      <Strings>
        <ButtonText>Copy</ButtonText>
      </Strings>
    </Button>

    5. Declare its associated symbols.

    5.a Open the GeneratedCode/Constants.cs

    5.b Copy the value of the Constants.xxxCommandSetId constant.

    5.c Assign it to the value attribute of the GuidSymbol xml element

    <GuidSymbol name="guidCmdSet" value="{xxxx-xxx-xx-xx-xxx}" >
      <IDSymbol name="cmdidCopy" value="0x804"/>
    </GuidSymbol>

    6. Create a static Commands class to reference the information associated to your new commands.

    6.a Right-click on the CustomCode folder of the DslPackage project, Add Class

    6.b Enter "Commands" as name.

    6.c For each command, declare the constant integer used as value for the value attribute of the IDSymbol xml element associated to the command.

    internal static class Commands
    {
        private const int CopyCommandId = 0x804;
        private const int CutCommandId = 0x805;
        private const int PasteCommandId = 0x806;
    
        public static readonly CommandID Copy;
        public static readonly CommandID Paste;
        public static readonly CommandID Cut;
    
        static Commands()
        {
            Copy = new CommandID(Constants.xxxCommandSetId, CopyCommandId);
            Paste = new CommandID(Constants.xxxCommandSetId, PasteCommandId);
            Cut = new CommandID(Constants.xxxCommandSetId, CutCommandId);
        }
    }

    Implement your commands

    7. Open or create a partial class of your CommandSet in the "CustomCode" folder.

    8. Develop for each operation an handler in order to display (or hide) the command depending on the context and an handler that executes the command when it is selected.

    private void OnStatusCopyOrCut(object sender, EventArgs e)
    {
        MenuCommand command = sender as MenuCommand;
        command.Visible = command.Enabled = 
            ((this.CurrentSelection.Count > 0) && 
            !(this.GetSelectedObject(0) is Diagram));
    }
    private void OnMenuCopy(object sender, EventArgs e)
    {
        Diagram l_diagram = this.CurrentDocView.CurrentDiagram;
        ElementGroup l_elementGroup = new ElementGroup(l_diagram.Partition);
    
        foreach (object selectedObject in this.CurrentSelection)
        {
           ShapeElement l_shapeElement = selectedObject as ShapeElement;
           if (l_shapeElement != null && 
               l_shapeElement.ModelElement != null && 
               l_shapeElement.ModelElement is ModelType)
           {
              l_elementGroup.AddGraph(l_shapeElement.ModelElement, true);
           }
         }
         if (l_elementGroup.RootElements.Count == 0) return;
         IDataObject data = new DataObject();
         data.SetData(l_elementGroup.CreatePrototype());
         Clipboard.SetDataObject(
            data,
            false,
            10,
            50);
    }

    For the copy and cut operations, we display commands only if the current selection contains objects other than current Diagram.

    For the paste operations, we could check if the clipboard data content can be merged with the current diagram.

    The cut operation logic adds to the copy operation logic a step that deletes the copied elements.

    As you can see, if you downloaded the complete solution, we added to our partial CommandSet class 2 additional things:

    • An operation GetSelectedObject to get a selected object from its index in the current selection list.
    • A property CurrentStore to easily access the current IMS.

    9. Override the GetMenuCommands() method to add your new commands to the context menu.

    9.a Instantiate a DynamicStatusMenuCommand for each operation.

    protected override IList<MenuCommand> GetMenuCommands()
    {
      IList<MenuCommand> l_commandList = base.GetMenuCommands();
    
      DynamicStatusMenuCommand l_cmdCopy = 
         new DynamicStatusMenuCommand(
           new EventHandler(OnStatusCopyOrCut),
           new EventHandler(OnMenuCopy),
           Commands.Copy);
      
      l_commandList.Add(l_cmdCopy);
      ...
      return l_commandList;
    }

    In the introduction, we wanted to enable the standard Visual Studio cut, copy, paste commands on our shapes. The only remaining difficulty is to identify the commands associated to them. They are available in the well known System.ComponentModel.Design.StandardCommands

    By now, you must only instantiate a new DynamicStatusMenuCommand class by using the relevant StandardCommands field for each command (StandardCommands.Copy etc…).

    Deploy your new commands

    It is required to increment the Menu Resource Index for DSL Tools to take into account your new commands.

    10. Open GeneratedCode\Package.tt

    11. Increment the second integer of the ProvideMenuResource attribute.

    [VSShell::ProvideMenuResource("1000.ctmenu", 2)]

    12. Transform all templates

    13. Rebuild.

    14. Enjoy

    image

    Figure 1 - Visual Studio Edit Menu

    Conclusion

    So, it works fine but the current behavior is very restricted not only because operations are enabled only for ModelType and its derived types. When you copy a class that contains operations, they are not copied. It’s the expected behavior, indeed, by default; the copy propagation is not enabled. So, if you want to change this behavior, you only need to set to true the property PropagatesCopy of ModelClass Domain Role of ClassHasOperations relationship.

    image

    Figure 2 PropagesCopy property

    The recommendation is to allow a fine grained customization of the prototype creation from a domain class. To do this, you can define an interface IPrototypable to declare the domain class that support copy operation and to allow the customization of the creation of an ElementGroupPrototype from it.

    internal interface IPrototypable
    {
        ElementGroupPrototype GetPrototype();
    }

    To implement this interface by a desired domain class, it’s only required to create a dedicated partial class and to implement the default prototype logic:

    public ElementGroupPrototype GetPrototype()
    {
        ElementGroup l_elementGroup = new ElementGroup(this.Store);
        l_elementGroup.AddGraph(this, true);
        return l_elementGroup.CreatePrototype();
    }

    Customize the model explorer

    Add your own nodes

    The model explorer user interface is based on a tree view; the idea here is to add custom nodes to the treeview in order to list user-defined prototypes.

    Unfortunately, if it’s possible to customize the existing nodes (label, image, visibility), no real mechanism are offered to add our own nodes. However, a workaround exists: the treeview loading mechanism is done by a single method: RefreshBrowserView()

    If it was a virtual method, we could override it to add our own nodes (This one is going straight to connect.microsoft.com, so feel free to vote for it). In the mean time, you can have a look at the methods that use it thanks to Reflector: ModelExplorerToolWindow. OnDocumentWindowChanged, and ModelExplorerTreeContainer.ElementEventsEndedEventHandlerImpl.

    We can override the first method to add a call to our own refresh browser view logic. The second method is a callback registered for ElementEventsEnded IMS event, we can add or own callback for this event.

    To add custom nodes to the model explorer, there is only one constraint, custom node class must inherit from Microsoft.VisualStudio.Modeling.Shell.ExplorerTreeNode. It is required to call the method ProvideNodeText in order to populate the Text property of your node if it is not set it in its constructor.

    Below is a basic implementation of a custom node:

    public class DefaultTreeNode : ExplorerTreeNode
    {
            public DefaultTreeNode(String name, String text)
            {
                if (name == null)
                {
                    throw new ArgumentNullException("name");
                }
                if (text == null)
                {
                    throw new ArgumentNullException("text");
                }
                this.Name = name;
                this.Text = text;
            }
            protected override string ProvideNodeText()
            {
                return this.Text;
            }
    }

    1. Go to the DslPackage project

    2. Open or create a partial class of your ModelExplorerTreeContainer (ModelExplorer) in the "CustomCode" folder.

    3. Add it this method :

    internal void RefreshBrowserViewExt() 
    { 
    // Add your custom logic here 
    }

    4. Add your custom logic to load your nodes in the model explorer.

    5. Create a callback that calls your previously created method.

    private void ElementEventsEndedEventHandlerImplExt(object sender, ElementEventsEndedEventArgs e)
    {
        this.RefreshBrowserViewExt();
    }

    6. Override SubscribeToImsEvent method and add your own callback in order to call your custom refresh browser logic.

    protected override void SubscribeToImsEvent(Store newStore)
    {
        base.SubscribeToImsEvent(newStore);
        // HACK : Check above if the store is null.
        newStore.EventManagerDirectory.ElementEventsEnded.Add(
            new EventHandler<ElementEventsEndedEventArgs>(
            this.ElementEventsEndedEventHandlerImplExt)
            );
    }

    7. Override UnsubscribeToImsEvent method to remove your callback.

    protected override void UnsubscribeToImsEvent(Store oldStore)
    {
      base.UnsubscribeToImsEvent(oldStore);
      if (oldStore != null)
      {
          oldStore.EventManagerDirectory.ElementEventsEnded.Remove(
           new EventHandler<ElementEventsEndedEventArgs>(
           this.ElementEventsEndedEventHandlerImplExt));
      }
    }

    8. Open or create a partial class of your ModelExplorerToolWindow in the "CustomCode" folder of the DslPackage.

    9. Override OnDocumentWindowChanged method in order to call your custom refresh browser logic view.

    protected override void OnDocumentWindowChanged(
                ModelingDocView oldView, ModelingDocView newView)
    {
       base.OnDocumentWindowChanged(oldView, newView);
       ((YourModelExplorer)this.TreeContainer).RefreshBrowserViewExt();
    }

    Add your own model explorer commands

    You can add your own commands in the context menu associated to the model explorer.

    The only differences between this and doing it for the context menu associated to the current diagram are:

    • The ids to use: guidCommonModelingMenu and cmdidMenuExplorerCommand.
    • The mandatory definition of a guid symbol associated to the command

    1. Go to the DslPackage project.

    2. Open the Commands.vsct file.

    3. Define a button for your operation using the relevant ids.

    <Button guid=" guidMenuExplorerCommand" 
           id="cmdidMenuExplorerCommand" priority="0x0902" type="Button">
      <Parent guid="guidCommonModelingMenu" id="grpidExplorerMenuGroup"/>
      <CommandFlag>DefaultDisabled</CommandFlag>
      <CommandFlag>DefaultInvisible</CommandFlag>
      <CommandFlag>DynamicVisibility</CommandFlag>
      <Strings>
        <ButtonText>My command</ButtonText>
      </Strings>
    </Button>

    4. Declare the symbol associated to it.

    <GuidSymbol name=" guidMenuExplorerCommand " value="{xxxx048E-739A-474A-94CC-CDA7347C52E7}" >
      <IDSymbol name=" cmdidMenuExplorerCommand " value="0x807"/>
    </GuidSymbol>

    5. Create a static Commands class to reference the information associated to your new commands.

    a. Right-click on the CustomCode folder of the DslPackage project, Add Class

    b. Enter "Commands" as name.

    c. Declare the constant guid used as value for the value attribute of the GuidSymbol xml element associated to your command.

    d. Declare the constant integer used as value for the value attribute of the IDSymbol xml element associated to your command.

    6. Open or create a partial class of your ModelExplorerTreeContainer (ModelExplorer) in the "CustomCode" folder.

    7. For each command implement a handler in order to display (or hide) the command depending on the context and a handler that is executed if the command is selected.

    8. Override the AddCommandHandlers method to add your new commands to the model explorer context menu.

    public override void AddCommandHandlers(IMenuCommandService menuCommandService)
    {
        base.AddCommandHandlers(menuCommandService);
    
        DynamicStatusMenuCommand l_cmdMyCmd = new DynamicStatusMenuCommand(
            new EventHandler(OnStatusCmd),
            new EventHandler(OnMenuCmd),
            Commands.MyCmd);
        menuCommandService.AddCommand(l_ cmdMyCmd);
    }

    9. Open the GeneratedCode\Package.tt

    10. Increment the second integer of the ProvideMenuResource attribute.

    [VSShell::ProvideMenuResource("1000.ctmenu", 3)]

    11. Transform all templates

    12. Rebuild

    13. Enjoy

    Define prototype mechanism

    Here we’re going to offer to users a mechanism to prototype a model element. To do this, we use the logic presented to enable copy operations and to customize the model explorer.

    The customization of the model explorer must allow:

    Adding custom node when a user defines a prototype.

    Handling drag and drop on prototype node from the model explorer.

    Saving user-defined prototypes beyond the scope of visual studio instance (persisted on disk).

    Add custom node when a user defines a prototype

    1. Create a serializable class Pattern

    a. Right-click on the CustomCode folder of the DslPackage project, Add Class

    b. Set its name to "Pattern".

    2. Add to it two properties : Name (String) and Prototype (ElementGroupPrototype)

        [Serializable]
        public sealed class Pattern
        {
            public String Name { get; set; }
            public ElementGroupPrototype Prototype { get; set; }
    
            public Pattern(String name, ElementGroupPrototype prototype)
            {
                this.Name = name;
                this.Prototype = prototype;
            }
       }

    3. Implement an ExplorerTreeNode dedicated to your class.

    public class PatternTreeNode : ExplorerTreeNode
    {
       private Pattern m_pattern;
    
       public PatternTreeNode(Pattern pattern)
       {
           if (pattern == null)
           {
               throw new ArgumentNullException("pattern");
           }
           m_pattern = pattern;
           this.Name = pattern.Name;
       }
    
       public Pattern Pattern
       {
           get { return m_pattern; }
       }
    
       protected override string ProvideNodeText()
       {
           return m_pattern.Name;
       }
    }

    4. Open or create a partial class of your ModelExplorerTreeContainer (ModelExplorer) in the "CustomCode" folder.

    5. Add private field to store Pattern objects.

    private List<Pattern> m_patternList = new List<Pattern>();

    6. Code an Add and Remove method to manage the pattern list and to update the model explorer

     internal void AddPatternTreeNode(String name, ElementGroupPrototype prototype)
     {
         string l_name = name;
         if (string.IsNullOrEmpty(name))
         {
             l_name = this.GetNewPatternName();
         }
         Pattern l_pattern = new Pattern(l_name, prototype);
         this.AddPatternTreeNode(l_pattern);
         m_patternList.Add(l_pattern);
     }
     private void AddPatternTreeNode(Pattern pattern)
     {
         TreeNode l_rootTreeNode = this.ObjectModelBrowser.TopNode;
         TreeNode l_prototypesTreeNode = l_rootTreeNode.Nodes["Prototypes"];
         PatternTreeNode l_patternTreeNode = new PatternTreeNode(pattern);
         l_patternTreeNode.UpdateNodeText();
         this.InsertTreeNode(l_prototypesTreeNode.Nodes, l_patternTreeNode);
     }
     private void RemovePatternTreeNode(PatternTreeNode patternTreeNode)
     {
         patternTreeNode.Remove();
         m_patternList.Remove(patternTreeNode.Pattern);
     }

    7. Load into the Model Explorer your list of patterns by using a specific RefreshBrowserViewExt method

    internal void RefreshBrowserViewExt()
    {
        this.ObjectModelBrowser.BeginUpdate();
        if (this.ObjectModelBrowser.TopNode != null)
        {
            TreeNode l_rootTreeNode = this.ObjectModelBrowser.TopNode;
            DefaultTreeNode l_defaultTreeNode = new DefaultTreeNode("Prototypes");
            this.InsertTreeNode(l_rootTreeNode.Nodes, l_defaultTreeNode);
            l_defaultTreeNode.UpdateNodeText();
            for (int i = 0; i < m_patternList.Count; i++)
            {
                this.AddPatternTreeNodeToObjectModelBrowser(m_patternList[i]);
            }
            l_defaultTreeNode.Expand();
        }
        this.ObjectModelBrowser.EndUpdate();
    }

    8. Similar to what has been done for copy operations; add to your diagram’s context menua method to prototype the user’s selection. The only difference is that it is not the Clipboard that will receive the created ElemtGroupPrototype but the ModelExplorer. To get a pointer to our ModelExplorer, we add a strongly typed property to our ModelExplorerToolWindow that casts its generic TreeContainer property.

    private void OnMenuPrototype(object sender, EventArgs e)
    {
        Diagram l_diagram = this.CurrentDocView.CurrentDiagram;
        ElementGroup l_elementGroup = new ElementGroup(l_diagram.Partition);
        foreach (object selectedObject in this.CurrentSelection)
        {
            // Pick out shapes representing Component model elements.
            ShapeElement l_shapeElement = selectedObject as ShapeElement;
            if (l_shapeElement != null && l_shapeElement.ModelElement != null && 
                l_shapeElement.ModelElement is ModelType)
            {
                l_elementGroup.AddGraph(l_shapeElement.ModelElement, true);
            }
        }
        this.PrototypeExplorerToolWindow.PrototypeExplorer.AddPatternTreeNode(
                null, l_elementGroup.CreatePrototype());
    }

    9. Don’t forget to increment the second integer of the ProvideMenuResource attribute.

    image

    Figure 3 Diagram Context menu

    Implement prototype node drag and drop from the model explorer

    The tree control contained in the ModelExplorer is simply the well known System.Windows.Forms.TreeView control. Thus , it offers the same features such as drag and drop.

    To customize its behavior, you can override your ModelExplorer's OnCreateControol method ( due to ContainerControl base class inheritance )

    1. Open or create a partial class of your ModelExplorerTreeContainer (ModelExplorer) in the "CustomCode" folder.

    2. Override the OnCreateControl () method to customize the tree control.

    protected override void OnCreateControl()
    {
       base.OnCreateControl();
       this.ObjectModelBrowser.LabelEdit = true;
       this.ObjectModelBrowser.KeyUp += new KeyEventHandler(ObjectModelBrowser_KeyUp);
       this.ObjectModelBrowser.ItemDrag += new ItemDragEventHandler(ObjectModelBrowser_ItemDrag);
       this.ObjectModelBrowser.AfterLabelEdit += new 
         NodeLabelEditEventHandler(ObjectModelBrowser_AfterLabelEdit);
    }

    3. Implement the desired logic for each event handled. We decide to offer:

    • Prototype deletion by selecting it and pressing the delete key.
    • Prototype renaming by clicking on it.
    • Prototype drag and drop (as you can see it, it’s straightforward to activate it, thanks to ElementGroupPrototype).
    private void ObjectModelBrowser_KeyUp(object sender, KeyEventArgs e)
    {
       if (!e.Handled && e.KeyValue == (int)Keys.Delete)
       {
                    TreeNode l_treeNode = this.ObjectModelBrowser.SelectedNode;
                    if (l_treeNode != null && l_treeNode is PatternTreeNode)
                    {
                        this.RemovePatternTreeNode((PatternTreeNode)l_treeNode);
                    }
       }
    }
    private void ObjectModelBrowser_ItemDrag(object sender, ItemDragEventArgs e)
    {
      if (e.Item is PatternTreeNode)
      {
       this.ObjectModelBrowser.SelectedNode = (TreeNode)e.Item;
       this.ObjectModelBrowser.DoDragDrop(
         ((PatternTreeNode)e.Item).Pattern.Prototype, DragDropEffects.Copy);
      }
    }
    private void ObjectModelBrowser_AfterLabelEdit(object sender, NodeLabelEditEventArgs e)
    {
      if (e.Node is PatternTreeNode)
      {
        ((PatternTreeNode)e.Node).Pattern.Name = e.Label;
      }
    }

    image

    Figure 4 Customized model explorer

    Save user-defined prototypes beyond the scope of visual studio instance.

    Saving user-defined prototypes is only a matter of serializing it. Indeed, ElementGroupPrototype can transit through the Clipboard. So, the serialization mechanism is already supported. We use it to save them in a basic file.

    1. Create a static class to manage serialization (and deserialisation) of user-defined prototype list into a file.

    a. Right-click on the CustomCode folder of the DslPackage project, Add Class

    b. Enter “UserDefinedPrototypeSerializationHelper" as its name.

    2. Add a method to implement serialization and deserialization mechanism.

    internal static void Serialize(List<Pattern> patternList, string file)
    {
      if (!Directory.Exists(Path.GetDirectoryName(file)))
      {
        Directory.CreateDirectory(Path.GetDirectoryName(file));
      }
      using (Stream stream = File.Open(file, FileMode.Create))
      {
        BinaryFormatter l_binaryFormatter = new BinaryFormatter();
        l_binaryFormatter.Serialize(stream, patternList);
      }
    }
    
    internal static List<Pattern> DeSerializeObject(string file)
    {
      List<Pattern> l_patternList = null;
      using (Stream stream = File.Open(file, FileMode.Open))
      {
        BinaryFormatter l_binaryFormatter = new BinaryFormatter();
        l_patternList = (List<Pattern>)l_binaryFormatter.Deserialize(stream);
      }
      return (l_patternList == null ? new List<Pattern>() : l_patternList);
    }

    3. The deserialization mechanism used in the experimental hive requires that you provie a specific a AssemblyResolve mechanism in order to resolve dsl assemblies.

    static UserDefinedPrototypeSerializationHelper()
    {
                AppDomain.CurrentDomain.AssemblyResolve += 
                   new ResolveEventHandler(CurrentDomain_AssemblyResolve);
    }
    
    private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
      if (args.Name.IndexOf("DslPackage") > 0)
      {
        return typeof(UserDefinedPrototypeSerializationHelper).Assembly;
      }
      else if (args.Name.IndexOf("Dsl") > 0)
      {
        return typeof(YourDomainModel).Assembly;
      }
      return null;
    }

    4. Open or create a partial class of your ModelExplorerTreeContainer (ModelExplorer) in the "CustomCode" folder.

    5. Add specific code to the overridden method OnCreateControl () to deserialize saved user-defined prototypes and to load it into the ModelExplorer.

    protected override void OnCreateControl()
    {
        String l_fileName = this.GetPrototypeFileName();
        if (File.Exists(l_fileName))
        {
            m_patternList = PrototypeSerializationHelper.DeSerializeObject(l_fileName);
        }
        base.OnCreateControl();
        this.ObjectModelBrowser.LabelEdit = true;
        …

    6. Override the Dispose method to save when the user-defined prototypes into a file when the ModelExplorer is closed.

    protected override void Dispose(bool disposing)
    {
         if (disposing)
         {
             String l_fileName = this.GetPrototypeFileName();
             UserDefinedPrototypeSerializationHelper.Serialize(m_patternList, l_fileName);
         }
         base.Dispose(disposing);
    }

    Going further

    This article lists a fair amount of customization mechanisms and promotes ElementGroupPrototype class. It’s possible, of course to go further. You can add export and import feature on prototypes in order to share them in a similar way as visual studio templates or code snippets. If the fact that you can associate an ElementNameProvider to only one property is too limitating, one can use an interface (IPrototypable to broaden the customization of the model element created. For instance, customizing the property grid associated to the prototype allows defining a description to it.

    It would be very useful if the Model Explorer could support domain class namespaces (as model element base classes).

    Add to toolbox

    Before ending this article, we add to the ModelExplorer context menu a specific command to allow adding to the toolbox and user-defined prototype.

    1. Implement an “Add to toolbox” command (See paragraph entitled “Add your own model explorer commands”)

    2. Use IToolboxService service mechanism in the execution logic of your command and take advantage of ModelingToolboxItem class to create and add to the toolbox an item dedicated to your prototype.

    private void OnMenuAddToToolBox(object sender, EventArgs e)
    {
      IToolboxService l_toolBoxService = 
            (IToolboxService)this.ServiceProvider.GetService(typeof(IToolboxService));
    
      if (this.ObjectModelBrowser.SelectedNode is PatternTreeNode)
      {
        Pattern l_pattern = ((PatternTreeNode)this.ObjectModelBrowser.SelectedNode).Pattern;
    
        ModelingToolboxItem l_modelingToolboxItem = new ModelingToolboxItem(
                 l_pattern.Name, 1,  l_pattern.Name, 
                (Bitmap)ImageHelper.GetImage(
    PrototypeDomainModel.SingletonResourceManager.GetObject("ModelClassToolboxBitmap")), 
                             "Prototypes", "Prototypes", "", "Prototype", l_pattern.Prototype, 
                            new ToolboxItemFilterAttribute[] {                            new ToolboxItemFilterAttribute(PrototypeToolboxHelper.ToolboxFilterString, ToolboxItemFilterType.Require)  });
    
        l_toolBoxService.AddToolboxItem(l_modelingToolboxItem, "Prototypes");
      }
    }

    image

    Figure 5 Customized toolbox

    Workaround

    Unfortunately, the treeview control has unexpected behavior when you want to customize the status of a context menu command.

    If you want command displayed only for a specific tree node, you basically use SelectedNode property of ObjectModelBrowser (on the ModelExplorer instance). But this property doesn’t return the relevant selected node if you do not select the node with a left click before right clicking it to display the context menut.

    To work around this problem, one can implement a specific callback for the AfterSelect event raised by the treeview that uses the selection service.

    1. Open or create a partial class of your ModelExplorerTreeContainer (ModelExplorer) in the "CustomCode" folder.

    2. Add specific code to the overrided OnCreateControl () method to add a callback for AfterSelect event raised by the contained treeview (ObjectModelBrowser).

    protected override void OnCreateControl()
    {
      String l_fileName = this.GetPrototypeFileName();
      if (File.Exists(l_fileName))
      {
        m_patternList = UserDefinedPrototypeSerializationHelper.DeSerializeObject(l_fileName);
      }
      base.OnCreateControl();
      this.ObjectModelBrowser.LabelEdit = true;
      this.ObjectModelBrowser.KeyUp += new KeyEventHandler(ObjectModelBrowser_KeyUp);
      this.ObjectModelBrowser.ItemDrag += 
      new ItemDragEventHandler(ObjectModelBrowser_ItemDrag);
                this.ObjectModelBrowser.AfterLabelEdit += 
      new NodeLabelEditEventHandler(ObjectModelBrowser_AfterLabelEdit);
                this.ObjectModelBrowser.AfterSelect += 
      new TreeViewEventHandler(ObjectModelBrowser_AfterSelect);
    }

    3. Implement your callback by using the selection service to customize the selection mechanism associated to our custom tree node.

    private void ObjectModelBrowser_AfterSelect(object sender, TreeViewEventArgs e)
    {
        if (e.Node is PatternTreeNode)
        {
            this.SelectionService.SetSelectedComponents(
              new object[] {((PatternTreeNode)e.Node).Pattern});
        }
    }

    Support prototype auto-layout on drag and drop

    When you drag and drop a prototype or when you copy and paste a model element, the associated shapes created by this action are displayed by default on the same position.

    To avoid this, one can call AutoLayoutShapeElements method of the diagram on which modelelements are dropped.

    Here is below a code extract to accomplish this task.

    1. Open Solution Explorer.

    2. Go to the Dsl project.

    3. Open or create a partial class of your Diagram in the "CustomCode" folder.

    4. Override the OnDragDrop method to layout automatically dropped elements.

    Take into account model element namespace

    There are multiple ways to customize the model explorer; one of them is to define a new tool window.

    You can act on the default model explorer representation and take into account the namespace associated to the domain class of a model element.

    A smart Class designer

    The sample solution coming along with this paper is, as described in the introduction, based on the class diagram DSL template. If one of Rosario’s promises is to offer a set of new diagrams built on DSL tools, it would be very useful if these diagrams provide prototyping capability. For instance, a class diagram could assist developers by offering by default a set of code design patterns (gang of four…) and offering a simple sharing capability between users following code snippet footsteps.

     

     


    [1] You can as well navigate on the dedicated MSDN Forum to Visual Studio extensibility.

    Download the complete solution here.

    A screencast showing a scenario taking advantage of the functionalities presented in this paper is available here.

    The pdf version of this article is available here.

  • Acropolis, what is the real target?

    Adopting new technology is not just about building a “Hello World” Application but testing its capabilities to match precise requirements for real-world situations. After a fair amount of playing and digging in, this article aims to provide an overview about Acropolis. At the same time, I will suggest solutions to enhance some of the drawbacks identified in the current build that I outline in this paper.

    Rapid Application Development

    Acropolis provides an integrated designer within Visual Studio Orcas where we can drop “Parts” and set up their properties. This is an excellent way for “Beginners” or for Quick and Dirty purpose. Unfortunately, we stick on this design when we start to apply enterprise productivity. Let’s me take an example; an application that loads different workspace depending on “User Profile” can’t be integrated in Acropolis NavigationManager. We need to call manually the method “BindToCollection” on “Microsoft.Acropolis.PartFx.NavigationManager” because when “AcropolisApplication” is activated, it performs the “ChildParts” binding on the NavigationManager.

    Another RAD orientation is to associate “Part” of its “View” with a custom Attribute “ViewExtensionInfo”. A more convenient way to address this would be to use an external mapping file.

    At least, Services Dependencies concept is pretty cool by design, but in Real World application, we don’t know in advance all the components involved in a composite application.

    It makes sense that most efforts are driven towards RAD. Acropolis is at a very early stage and probably focused on a seduction phase, it has to gain its audience. This means that it targets a wider audience and it uses WPF more for its simple and sexy look and feel, but it seems that is done to the detriment of Enterprise reality.

    On the road to Enterprise Productivity

    The current build of Acropolis is RAD oriented but it provides many opportunities to be extended.

    Extensibility

    One of the beautiful things that Acropolis provides is a real reusable framework that comes from CAB and so on. This API’s is very useful when we want to provide custom implementations on top of Acropolis. This means that we can participate to Acropolis effort and provide an enterprise orientation to ensure its specific requirements are addressed.

    Theme Manager

    Implementing custom theme is done by inheriting from “Microsoft.Acropolis.Windows.Theme”. Themes registration can be done by the method “AcropolisApplication.RegisterThemeAssembly” by specifying your theme object type as parameter, the registration have to be done when “AcropolisApplication” starts (override the method “OnStartup”). This is the easiest way but makes it hard coded. On the other hand, you can declare your themes by configuration as follow:

    <configuration>
      <configSections>
        <sectionGroup name="microsoft.acropolis">
          <section name="themeInfo" 
                   type="Microsoft.Acropolis.ThemeInfoConfigSection,Microsoft.Acropolis.PartFx,Version=2.0.0.0,Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
        </sectionGroup>
      </configSections>
      <microsoft.acropolis>
        <themeInfo>
          <themes>
            <add assembly="[AssemblyName]" type="[Namespace.ThemeObjectType]"/>
          </themes>
        </themeInfo>
      </microsoft.acropolis>
    </configuration> 
    
    

    The theme configuration above can be loaded by “ConfigFileThemeProvider” class that implements “IThemeProvider”. This class loads all the types implementing the “ITheme” interface when the type attribute on “theme\add” element is a star (“*”), otherwise, it loads the type specified explicitly. Once again, we need to register manually when on Application starts by calling “ThemeService.Instance.RegisterThemes”. I suggest implementing an automatic theme registration mechanism when it is specified by configuration. In addition, having a property “IsDefault” on “ThemeConfigElement” allows specifying theme externally without setting “Theme” property on the “AcropolisApplication”.

    Part Model

    Part Composition

    Acropolis Composition applies a “Composite” Pattern to represent a hierarchical object model for “Part”. However, the user Interface representation within WPF doesn’t provide by default a child Parts chaining, it has to be done manually. It should be best to provide this behavior by default within the Composition Engine.

    With Acropolis, there is no limit to add child parts. I’m very anxious in regards to this behavior because it might generate issues on how developers make their application. What I mean by this is that Part is not only focused on the business case it addresses, it also has to be aware of its “environment” in the sense that it has to take into account the layout or the window it is loaded in.

    To address this, I suggest a new Part Composition Object Model that allow for anyone to compose an application layout based on “PartView”.

    First, let’s make sure we’re all in the same page by presenting the new Object Model in the figure below:

    On the left side:

    1. Workspace” represents the Root Level of your application; basically, it’s a Window. Workspace has one or many Layout child,
    2. Layout” represents a single active view on the Workspace, Layout has one or many Slot child,
    3. Slot” represents a zone (like a canvas), Slot has one or many Component,
    4. Component” represents the Developers Unit of Work, it can have a child that we can call “Sub-Component

    On the right side:

    You notice that it’s the UI Control mapping for our Part Object Model. Don’t worry, this model is not really different than the one provided by default by Acropolis provides. The goal is the same but the approach is a little bit different using WPF Control. So, let’s talk about the advantage of this model, several capabilities come to us:

    1. Parts Composition is not only for the Developers but also for Designer. Both Layout and Slot aims to compose your application layout design, allowing Developers to work only on the Component View.
    2. Allows a clean composition of Parts
    3. Layout and Slot allow defining a “Scope” level because they act as a “Container”. This is an important feature in all cases to apply “Event Notification”, “Data”, “Services” and so on.
    4. Avoid confusion about using “ActiveParts” versus “ActivePart” Navigation Property. By using WPF “ItemsControl” Control, the only one thing we do is to call the “ActiveParts” property without taking care about children. Now, the big question is: How can I apply a Layout Pattern on Workspace, LayoutChildParts and SlotChildParts? The answer is very simple, WPF is powerful; ItemsControl provides a property called “ItemsPanelTemplate”, this property allows defining any kind of layout (LayoutPane in Acropolis) like Tab, Grid, Stack, …

    Screenshots are more meaningful than a long sentence:

    Figure 1: Single (Main Layout) Layout on a Workspace, Slots (Menu, Navigation and Main Content) are in a “StackPanel” with Vertical orientation.

    Figure 2: Single (Main Layout) Layout on a Workspace, Slots (menu, Navigation and Main Content) are in a “TabPanel”.

    Figure 3: Layouts (Main Layout and Second Layout) are in a “TabPanel” on a Workspace, Slots are in a “StackPanel” with Vertical orientation.

    Part Communication

    Building Composite application tends to avoid thigh coupling between Parts. Unfortunately, NavigationManager is not going this way with NavigateTo method that implies a dependency between two parts. The alternative is to provide a custom NavigationManager implementation.

    Part provides a set of features as “Services Dependencies”, “Data Publishing” and “EventNotification”. This is sounds good but the lack is in the PartView side that consumes them. Of course, by code behind, we can access the Part via “IPartViewContract”, right. Now, what about XAML? It would be more convenient to be able to develop parts without requiring registering events or calling external services in code behind.

    Part Life Cycle

    One of the big surprise with Acropolis is there is no real life cycle applied on Part or on Acropolis Application. It is very important to have a well known, pre defined, life cycle applied on all components. I suggest having something like ASP.NET’s Life Cycle; it simplifies the implementation of custom code, and allows knowing exactly what happen behind the scene at each and every stages of the execution. By experience, doing something as a black box induces some frustration and slows down technology adoption.

    Conclusion

    Personally, I see Acropolis taking the RAD approach and not target industrialization at this stage. Acropolis is heavily based on the famous composite UI application bloc (CAB) project, so a big impatience is born to get the same level of functionality that CAB provided. I would love to see the next Acropolis releases address more heavily tools and enterprise environment. Will this request be heard? We’ll see...

  • Service Referencing part II: dynamic service discovery

     

    Introduction

    As outlined in the first part of this article, referencing services greatly extends their visibility and promotes their capitalization. When most of the time services are deployed somewhere and accessible from a fairly static location, it happens that, to cater particular business needs that we will be outlined later on this post, some services requires to be discoverable in order to be accessed transparently regardless of their particular location at the time. This document covers such a requirement by exposing how to combine open standards and WCF to publish services in such a way that they can be discovered dynamically at runtime based on custom business criteria.

    Service Referencing

    Dynamic service referencing and discovery in general are complementary to static referencing and address different business requirements.

    Dynamic Service Discovery

    Requirements

    Prior to take advantage of discovering services at run time, two major points must be addressed and settled. First, because discovery has a cost that is not negligible it must be used only to address particular business needs. Then, it must be handled with the scope of a business contract. This contract defines the way the service can be discovered as well as the options available for interacting with it.

    Discovery is costly

    By nature, discovery is costly either on the network because discovery requests will have to be sent across the whole network using multicast or either on pure processing because the discovery will add a few extra steps in the way you access a service. By using discovery, you cannot just have the service’s access points in your configuration or in a repository (remember part I of this paper).

    A full use case of taking advantage of discovery requires those steps:

    · Send a request on the network for services matching a business requirement,

    · Receive all the responses from the services,

    · Select the most relevant one,

    · Act accordingly which means extracting the selected service’s metadata in order to dynamically generate a proxy in order to communicate with this service.

    As you can see these are extra steps that increase the client side effort required to communicate with a service. Compared to just using Visual Studio’s “Add web/service reference”, it becomes obvious that it would be foolish to use discovery all over the place just for the sake of calling dynamically services. This highlight once again the fact that discovery must respond to a business requirement and must be evaluated as such before deciding on using it.

    Discoverability driven by business need

    Taking the decision to expose a service as discoverable must be done in the light of a business requirement. The overhead induced by such discoverability must be balanced with the benefits you get out of it. Some business scenarios require services to be discoverable because they’re occasionally up, they are nomad and are not always accessible at the same location and so on… We will quickly describe few scenarios later on this paper.

    Existing service selection solutions: BAS/TPM

    Existing products and technologies already address service selection based on a set of business rules; a product such as Microsoft Biztalk offers Trading Partner Management (TPM) as well as Business Activity Services (BAS) that allows selecting business partners. This mechanism rests on well defined static business contracts as well as a predefined set of rules. This is similar to what discovery requires in the sense that business contracts must be settled first before starting discovering services. The rules equivalent used by BAS/TPM is represented as criteria. Where BAS/TPM offers dynamic selection of existing, referenced services endpoints, discovery allows going one step further and adds dynamic communication between client and service.

    Business contract settled

    The first thing to complete before even thinking about launching Visual Studio to hit the discovery API and start discovering dynamically services is to clearly define the business contracts responsible to formalize the way a client can interact with such a service. This also helps identifying if the service in question should even be discoverable at all. Doing this preliminary step with discovery in mind will help you evaluate which operations should be discoverable and can, and probably will, have an impact on the way you design and decompose your business contracts. This is a crucial step to complete in order to ensure a proper use of discovery and avoid useless discovery overhead on operations that do not require it.

    Negotiation phase finalized

    The approach taken here in relation to the discovery requires that a “negotiation process” occurred prior to starting discovering services. Those services are already well known, they are driven by a well known business contract, and the only unknown piece of information at the time of discovery is the location of the service responding to a particular business need. The lever allowing switching between existing services are defined by search criteria as explained bellow.

    Search criteria defined (Extended search criteria with business context which is not included in business contract)

    Discovering services is all about being able to specify custom, business oriented criteria that are relevant to the discovery. Those criteria must be though through and evaluated in such a way that they can conveniently be set in place, computed and interpreted. At the same level that one must define the business contracts that will dictate the way services are exposed as discoverable, one must define the search criteria that will be used to discover such services.

    Use cases

    Because, as outlined previously, one must be careful to where to take advantage of discovery, it is important to outline a few sample use cases where discovery can help addressing business requirements. The most obvious advantage of using discovery features in your environment is to reduce the effort required to register your services within an information system as well as provide business attributes in order to easily discover relevant services based on business criteria.

    Goal: Reduce registration effort

    Here are three sample use cases that highlight the benefits of using discovery to either reducing registration service effort, cope with services occasionally connected as well as nomad users that requires to connect to specific, context driven, services.

    Nomad services

    A salesman visiting clients worldwide often has to connect to the office VPN to either download or upload data in order to do day to day work. By taking advantage of discovery as well as his geographical position, he can transparently find to the most relevant local branch to connect to in order to

    Disconnected services

    When dealing with occasionally connected services, it is important, in order to offer the best quality of service, to be able to find active services that respond to particular business requirements. Using discovery in such scenario, allows querying the information system for active services addressing a particular business requirement. This query mechanism can be sharpened to include specific business or technical parameters to ease the look up process and provide a transparent experience from a consumer point of view.

    N3RD – Command Center

    During wild fires in south of France, firemen have to deploy a remote command center close to the incident. Because of time constraints, there is no time to waste to setup and register this new, temporary, command center. This is where discovery comes into place; it allows the remote command center to register onto the network so the calls related to the fire are routed automatically to this new virtual station and not to the existing fire stations. Once the fire is over, this temporary, remote command center is then switched off and disappears from the information system. No calls can be routed anymore to this command center.

    Video demonstration

    A demo video of WS-discovery in action is available here.

    This demo addresses the N3RD Command Center use case described above and contains 3 parts:

    1. The client runs a probe match on the network and several stations respond to it. (Client doesn't know which fire station must be selected).

    2. Fire stations respond to the client request and indicate their respective geographical position. The client then picks the closest one.

    3. The client sends a custom probe message indicating its geographical position, only services located within a 3 miles range respond to the request. The client picks the closest one.

    In the last part, we used header injection in the address in order to call dynamically the service.

    Standardization

    There are multiple ways to technically address such a capability. Where anyone could implement its own solution to this problem it is critical to offer the most open solution. This is all about discovery therefore if you want your services to be discoverable the worst path to follow would be to implement a custom proprietary discovery protocol. In the same way, open standards allow exposing services metadata, so regardless of the platform you are working with; you can query, read and interpret those metadata. The same concern is true for discovery, being discoverable requires following standards so third parties or different platforms can still discover your services.

    Business service must focus on business it serves

    Adding to a service the capability of being discoverable must not interfere with the business purpose of the service. Such a capability must not require any specific code on the service itself and must be able to be plugged side by side with the service it exposes as discoverable. The implementation that we will describe later on takes into account this fact.

    Dynamic discovery is a technical service

    Discovery is a purely technical process allowing addressing business requirements. Such a capability must not interfere with the business in any way. The discoverability footprint must be reduced to the maximum in order to ensure proper business throughput.

    Use a specification with large adoption

    Discovery is all about the ability to be discoverable for the services requiring to be exposed as such. It is therefore crucial to follow standards in order to ease interoperability with existing technologies or products. There are numerous standards driven by consortiums that helps standardization and promotes interoperability. Within the WS-* galaxy, there is one specification targeting discovery: WS-Discovery.

    WS-Discovery

    The implementation presented here is based on Vipul Modi’s work published here that offers a very good baseline for building an effective and extendable business oriented service discovery.

    This specification, driven by few major actors of the IT world such as BEA, Microsoft, Intel, Canon etc…, defines a multicast discovery protocol to locate services. As a WS-* specification, it quickly becomes the de facto protocol when dealing with dynamic service discovery. The implementation presented bellow follows this specification and ensure a wide openness with existing platforms as well as the largest scope to provide the widest discoverability. The full WS-Discovery specification is available here.

    Maturity

    This specification is already widely available for device discovery thanks to UPnp, SSDP and Windows Rally initiative. Windows Vista uses this standard for discovering devices capabilities and offering or exposing relevant functionalities. For example, when displaying your network neighborhood, your residential gateway can expose its configuration page so Windows Vista can add an extra contextual menu allowing opening up the gateway’s configuration regardless of its actual IP address.

    Smart implementation

    This discovery process as outlined in the WS-Discovery specification follows the famous Publish - Subscribe pattern where a discoverable service joining the network sends a multicast Hello message notifying all the listening parties that it is available. In the same way, before shutting the service down, it notifies the same listening parties that it is leaving and will therefore not be available to serve requests anymore by sending a Bye message. During the time the service is available and discoverable, it is able to responds to discovery messages sent across the network, again following WS-Discovery specifications. This technical capability must not interfere with the business purpose of the service. Such a service cannot afford to take execution time to respond to discovery messages.

    Relayed and diffused infrastructure

    As mentioned above, a service cannot waste time servicing pure technical operations. Therefore, a dedicated mechanism must be setup to expose the discovery capability of the business service without interfering with the business value the service brings. In order to address that, a separate, purely technical, service is automatically created and started along with the business service. It runs as separate instance with the only purpose of exposing discovery and responding to discovery requests. The diagram bellow outlines the architecture put in place clearly separate business value from technical capability brought by WS-Discovery:

    As this diagram suggest, there is a clear separation between the business scope of the service and client and the discovery process and the discovery process never interfere with the business operations.

    Few messages exchanged

    WS-Discovery is based on very simple, yet powerful, set of messages. By offering Hello and Bye messages, the publish/subscribe pattern can be easily implemented. In conjunction with those two messages, come two types of messages allowing querying for discoverable services: Probe and Resolve.

    Identity card to extend

    The specification describes a common identity card specifying a set of common attributes that a discoverable service must expose. As described in the class diagram bellow, these are: AnyAttributes, AnyElelments, EndpointAddressAugust2004, MetadataVersion, Scopes, Types and XAddress.

    Extensibility driven

    Thanks to WS-Discovery specification openness (with AnyAttribute and AnyElements) the messages are endlessly extensible to cater any specific business requirements. One of the goals of the WS-Discovery implementation exposed here was to provide the most convenient extensibility model in order to allow developer to easily define custom, business oriented messages that provides a friendly business centric client side discovery mechanism. The class diagram bellow describes this architecture:

    As you can see, by inheriting from ServiceProperties class, it is trivial to declare a custom, business focused, specialization of it that exposes the geographical position of the service declared as discoverable. Using the same technique, messages can be specialized as EmergencySiteHello class suggests. Again, client side, the same technique is used to create business centric MatchCriteria that allows discovering services based on business attributes, see CallerMatchCriteria.

    Going further

    Microsoft Surface

    Such a technology could greatly extend the applicative discoverabilities of Microsoft Surface. Any application can easily take advantage of this discovery mechanism based on business or technical contracts as well as custom criteria thus react appropriately with devices or applications in range of the Surface.

    Going further than just a way to connect applications and devices, such a technology can open a broad new range of scenarios where opened applications could be modified or notified by discovered devices or services and provide a deeper interaction between human and machine.

    ESB Toolkit (Resolve UUID)

    A cornerstone of an enterprise service bus (ESB) is the service repository that it maintains. The notion of static and dynamic referencing address in this two part article can be directly applied to an ESB by taking advantage of both UDDI for the static service referencing and WS-Discovery for dynamic service discovery. The extra step that an ESB should offer is a dedicated proxy allowing taking advantage of WS-Discovery in order to provide a transparent communication initiation.

    Getting started with our implementation

    This article comes with a fully featured getting started kit walking you through two samples in order to get you started quickly with our implementation of WS-Discovery. The kit is available here

    Links

    The pdf version of this paper is here.
    The demo video is here.

    Format: wmv
    Duration: --:--

  • Acropolis NavigationManager Extensions

    1 - Introduction

    In my previous post, I said that Acropolis provide out of the box 2 kinds of Navigation Model : "SinglePartNavigation" and "MultiPartNavigationManager".

    In conclusion of my previous post, I have shortly described the limitations of these 2 navigation models, highlighting the fact that the Navigation between Components is hard coded on the application Design. Therefore, enforcing dependencies between Components.

    That's why, it is more convenient to build our own navigation engine. This means tha