DCSIMG
July 2007 - Posts - Guy Burstein's Blog

Guy Burstein's Blog

Developer Evangelist @ Microsoft

News

Guy Burstein The Bu

Disclaimer
Postings are provided 'As Is' with no warranties and confer no rights.

Guy Burstein LinkedIn Profile

TwitterCounter for @bursteg

July 2007 - Posts

My Most Wanted Posts

Over the past year I've written almost 300 posts, and recently I took the time to see which posts were read the most.

Here is the list (not ordered by the number of time they were read). If you missed any of them, you can read them again now.

  • WCF and DataSet? Use maxReceivedMessageSize
  • WCF Custom Message Headers
  • Next Generation Exception Message Box
  • How To: Debug Workflow Library
  • How To: Change PaperClip Theme Image and Width
  • Presentation Demo Tips
  • Workflow Persistence Point with PersistActivity | PersistOnClose
  • wca.exe - Workflow Communication Activities Generation
  • Getting Started SQLCE | SQL Compact Edition | SQL Everywhere
  • ADO.Net Entity Framework Stored Procedures
  • Oracle LINQ, Entity Framework Support
  • Add ExternalDataExchangeService by Config - ExternalDataExchangeServiceSection
  • Windows Workflow Foundation (WF) vs. Biztalk Server?
  • Validation Application Block (VAB) Windows Workflow Rules Engine Integration - Part 1
  • Validation Application Block (VAB) Windows Workflow Rules Engine Integration - Part 2
  • WCF Integration in Enterprise Library 3.0
  • WCF Exception Handling with Exception Handling Application Block Integration
  • WF and WCF Integration in "Orcas" - Part 1 - Workflow Enabled Services
  • WF and WCF Integration in "Orcas" - Part 2 - Service Enabled Workflows
  • Orcas Datasets - Separate Datasets from TableAdapters
  • TableAdapterManager in ADO.Net Orcas
  • Create Sharepoint Site By Template in Code - Workflow Custom Activity
  • Enjoy!

    Visual Studio 2008 Beta 2: svcutil.exe crashes with WCF Test Client

    Visual Studio 2008 Beta 2: svcutil.exe crashes with WCF Test Client

    svcutil.exe WCF Test Client I was very excited about the Visual Studio 2008 Beta 2 Announcement earlier. After downloading, extracting and installing I started playing with the new release. My first attempt was to check out the new WCF Test Client which is very similar to Cassini Web Server - The file system web server that can host Web Services during development and testing.

    I created a new WCF Service library and without changing a thing, ran the project. The WCF Test Client started the service, but when svcutil.exe tried to pull some metadata out of it, it crashed.

    I received this exception:

    Unhandled Exception: System.IO.FileLoadException: Could not load file or assembly 'svcutil, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. Strong name validation failed. (Exception from HRESULT: 0x8013141A)
    File name: 'svcutil, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' ---> System.Security.SecurityException: Strong name validation failed. (Exception from HRESULT: 0x8013141A)
    The Zone of the assembly that failed was:
    MyComputer
     

    I recorded myself reproducing the problem so it will be easier to help me. Can someone please help?


    View Video
    Format: aspx
    Duration: --:--

    [Update July 28th, 2007]: Thanks for everyone who took the time and helped me to diagnose this.

    I turns out to be a known issue with the WCF Tools in Visual Studio Beta 2. It was written in the ReadMe file, which out of excitement of the new release I skipped reading:

    2.3.7.8 Running some WCF-based project templates results in a crash of svcutil.exe crashing due to a signing issue

    Running some WCF-based project templates starts the service in WCF Service Host and opens a test form to debug operations on the service. Because of a signing problem, this results in a crash of svcutil.exe and the test form does not work.

    To resolve this issue:

    Disable strong name signing for svcutil.exe by opening a Visual Studio Command Prompt. At the command prompt run: sn -Vr "<program files>\Microsoft SDKs\Windows\v6.0A\Bin\SvcUtil.exe"  (replace <program files> with your program files path - ex: c:\Program Files)

    So this is what I did.

    Thanks again!

    Linq to SQL Features in Visual Studio 2008 Beta 2

    Linq to SQL Features in Visual Studio 2008 Beta 2

    LINQ to Sql Visual Studio 2008 Beta 2

    1. Code Generation improvements: Partial methods on DataContext and entity classes that can be extended in your partial class. Examples include
      1. Property setter validation methods (pre/post)
      2. Entity initialization and validation methods
      3. Create/Update/Delete (CUD) override methods (e.g. UpdateCustomer())
    2. Unification of code gen. Now SqlMetal's code generator also powers the beautiful LINQ to SQL designer. So you can use either tool to gen your classes and get consistent results.
    3. LinqDataSource for your web apps that modify data but don't want to include a ton of custom code.
    4. Revamped and rationalized WinForms databinding support (no more ToBindingList() needed)
    5. Tonnes of performance improvements across the board. 
    6. Streamlined and enhanced support for detached objects (see Attach() APIs) for multi-tier apps
    7. SQL Server Compact Edition (SQL CE) support
    8. Better APIs for getting commands and changed objects (GetChangeSet/GetCommand instead of GetChangeText/GetQueryText)
    9. Inheritance enhancements - a relationship can be added in a derived class and a relationship to a derived class is now supported
    10. Partial trust support - now you can use it in ASP.NET medium trust with the addition of RestrictedMemberAccess permission
    11. Better serialization
    12. 500+ fewer bugs :-)
    13. Query Visualizer is back disappearing in beta1 for a design overhaul.
    14. Lots of improvements in the designer for sproc handling - especially for insert/update/delete. But that is almost a separate topic best handled elsewhere

    Enjoy!

    Visual Studio 2008 Beta 2 - Orcas Beta 2

    Visual Studio 2008 Beta 2 - Orcas Beta 2

    Visual Studio 2008 Beta 2 Orcas Beta 2Beta 2 of Visual Studio 2008 (formerly "Orcas") and .Net Framework 3.5 are available for download in several editions:

    Microsoft .NET Framework 3.5 Beta 2

    MSDN Library for Visual Studio 2008 Beta 2

    Visual Studio Team System 2008 Beta 2 Team Suite (Virtual PC)

    Visual Studio Team System 2008 Beta 2 Team Suite

    Visual Studio 2008 Beta 2 Team Foundation Server

    Visual Studio 2008 Beta 2 Professional Edition

    Visual Studio 2008 Beta 2 Standard Edition

    For an overview of Visual Studio 2008 and a list of features that are available in this beta release, see the Visual Studio 2008 Overview Whitepaper located here.

    The C# Language Specification Version 3.0 is now available for review.

    There are samples and hands on labs available to help you explore this release:

    Enjoy!

    How To: Write a Custom Validator for Validation Application Block (VAB) using Application Block Software Factory

    How To: Write a Custom Validator for Validation Application Block (VAB) using Application Block Software Factory

    This post is a step by step guide on how to write a custom validator using Application Block Software Factory and integrate it in the configuration editor of Enterprise Library.

    When you install Enterprise Library 3.1, you not only get library code and documentation, you also get the Application Block Software Factory. This factory allows you to create new application block that can sit above the Enterprise Library code services such as configuration and ObjectBuilder. This Software Factory can also help you to create typed and untyped providers for the library such as new TraceListener for the Logging Application Block, Call Handler for the Policy Injection Application Block, Custom Validator - Validation Application Block, etc.

    Create a new Provider Library

    1. In Visual Studio 2005, create a new project of type Guidance Packages -> Application Block Software Factory -> Provider Library.

    Custom Validator Validation Application Block

    2. This will start a wizard that will request some details from you:
    Name: The name of the solution and the name pf the project that will contain the validator. I named it Bursteg.ProvidersLibrary, and we'll meet this name later in this post.
    Description: A description that will be placed in the AssemblyInfo.cs of the providers library.
    Author: Your Name
    Namespace: Namespace of your choice.

    Custom Validator Validation Application Block

    3. Click Finish and Visual Studio will generate the solution structure for you. The solution contains the Bursteg.ProvidersLibrary project in which the validator will be created, and another project called Bursteg.ProvidersLibrary.Configuration.Design that will contain the visual elements needed in order to integrate this validator into the Enterprise Library Configuration Editor. If in step 1 you created a project with tests then the solution will also contain 2 unit tests projects.

    ValidatorSolutionStructure

    Create the Custom Validator

    Generate Initial Code for the Custom Validator

    4. Right click the providers project (Bursteg.ProvidersLibrary) and from select Applciation Block Software Factory -> Validation Application Block -> New Validator (Typed).

    Custom Validator VAB

    5. This will start another wizard that will request the name of the validator. For this demo proposes I will create a Person ID Validator, that will validate an ID number according to a configured type. I named the validator: PersonIDValidator.

    Custom Validator VAB

    6. Click Finish and Visual Studio will add the necessary references and generate the initial source code for this validator. Notice that Bursteg.ProvidersLibrary has 3 new files for it.
    PersonIDValidator.cs contains the code for the validator.
    PersonIDValidatorAttribute.cs contains the code for the validator attribute in case you want to apply it by an attribute.
    PersonIDValidatorData.cs in the Configuration folder contains the code for the configuration element of the validator - the class that holds its data that was configured in the validator.

    SolutionWithValidator

    Add support for properties

    You should follow this step only if your validator receives any parameters. In this guide, the validator will receive a Country Code parameter and will validate the Person ID with the logic according to his country.

    7. Open the PersonIDValidator.cs and add the country code property.

    [ConfigurationElementType(typeof(PersonIDValidatorData))]

    public class PersonIDValidator : ValueValidator

    {

        private int countryCode;

     

        public int CountryCode

        {

            get { return countryCode; }

            set { countryCode = value; }

        }

        ...

    }

    8. Scroll down in this file and notice this commented code block:

    // TODO: Decide whether a constructor with discrete arguments is necessary for the PersonIDValidator.

    //public PersonIDValidator(object param1, object param2, string messageTemplate, bool negated)

    //    : base(messageTemplate, null, negated)

    //{

    //    this.var1 = param1;

    //    this.var2 = param2;

    //}

    Uncomment it and create an appropriate constructor that receives the relevant parameters for your validator. In this guide I will receive the country code parameter. The constructor should look like that:

    public PersonIDValidator(int coutry, string messageTemplate, bool negated)

        : base(messageTemplate, null, negated)

    {

        this.countryCode = coutry;

    }

    This constructor will be useful when you can to initialize the validator by code, or by attribute.

    If the country code is a mandatory parameter (It is in my case) then you should make sure it exist in all the overloads of the constructor. Finally, this is how the constructors should look like:

    /// <summary>

    /// <para>Initializes a new instance of the <see cref="PersonIDValidator"/>.</para>

    /// </summary>

    /// <param name="coutry">Country Code to validate by it</param>

    public PersonIDValidator(int country)

        : this(country, null, false)

    { }

     

    /// <summary>

    /// <para>Initializes a new instance of the <see cref="PersonIDValidator"/>.</para>

    /// </summary>

    /// <param name="configuration">The configuration instance for the validator.</param>

    public PersonIDValidator(PersonIDValidatorData configuration)

        : this(configuration.CountryCode, configuration.MessageTemplate, configuration.Negated)

    {

    }

     

    /// <summary>

    /// <para>Initializes a new instance of the <see cref="PersonIDValidator"/>.</para>

    /// </summary>

    /// <param name="coutry">Country Code to validate by it</param>

    /// <param name="negated">True if the validator must negate the result of the validation.</param>

    public PersonIDValidator(int country, bool negated)

        : this(country, null, negated)

    { }

     

    /// <summary>

    /// <para>Initializes a new instance of the <see cref="PersonIDValidator"/>.</para>

    /// </summary>

    /// <param name="coutry">Country Code to validate by it</param>

    /// <param name="messageTemplate">The message template to use when logging results.</param>

    public PersonIDValidator(int country, string messageTemplate)

        : this(country, messageTemplate, false)

    { }

     

    // <summary>

    /// <para>Initializes a new instance of the <see cref="PersonIDValidator"/>.</para>

    /// </summary>

    /// <param name="coutry">Country Code to validate by it</param>

    /// <param name="messageTemplate">The message template to use when logging results.</param>

    /// <param name="negated">True if the validator must negate the result of the validation</param>

    public PersonIDValidator(int coutry, string messageTemplate, bool negated)

        : base(messageTemplate, null, negated)

    {

        this.countryCode = coutry;

    }

    9. Open the PersonIDValidatorData.cs (under the Configuration folder) and locate the following commented line:

    // TODO: Add the configuration properties for PersonIDValidatorData. The snippet for creating configuration properties would be useful.

    Add the coutry code property to this class using the code snippet for creating configuration properties provided with the Application Block Software Factory. (To insert a snippet, click Ctrl + K + X and select the snippet you want. If you want to use this one directly, you can type its shortcut configproperty and click Tab twice).

    Custom Validator with Application Block Software Factory

    Edit the snippet fields with the property's name, configuration name and type.

    private const string CountryCodePropertyName = "CountryCode";

    [ConfigurationProperty(CountryCodePropertyName)]

    public int CountryCode

    {

        get { return (int)this[CountryCodePropertyName]; }

        set { this[CountryCodePropertyName] = value; }

    }

    10. Go back to the PersonIDValidator.cs and modify the constructor that receives the PersonIDValidatorData as parameter. This constructor is being called when the validator was configured in the configuration file. Simply assign the values from the configuration properties to the validator properties.

    /// <summary>

    /// <para>Initializes a new instance of the <see cref="PersonIDValidator"/>.</para>

    /// </summary>

    /// <param name="configuration">The configuration instance for the validator.</param>

    public PersonIDValidator(PersonIDValidatorData configuration)

        : this(configuration.CountryCode, configuration.MessageTemplate, configuration.Negated)

    {

    }

    Notice the first parameter that this constructor overload receives.

    10. Open the PersonIDValidatorAttribute.cs  and add a data member for the country code.

    private int coutryCode;

    Locate the following commented code:

    // TODO: Decide whether a constructor with discrete arguments is necessary for the PersonIDValidatorAttribute.

    //public PersonIDValidatorAttribute(object param1, object param2)

    //{

    //    this.var1 = param1;

    //    this.var2 = param2;

    //}

    Uncomment this code and create the constructor with the country code parameter:

    public PersonIDValidatorAttribute(int country)

    {

        this.coutryCode = country;

    }

    If the country code is a mandatory parameter (It is in my case) then you should remove the default constructor.

    11. In the same file edit the DoCreateValidator method. This method creates the instance of the validator based on the parameters of the attribute. Make sure to use the constructor overload that receives the country code.

    protected override Validator DoCreateValidator(Type targetType)

    {

        return new PersonIDValidator(this.coutryCode, MessageTemplate, Negated);

    }

    Implement the Validation Logic

    12. Go back the the PersonIDValidator.cs and locate the DoValidate method at the bottom of the class. This method will contain the validation logic, but first you must know what each of the parameters mean. From the parameters description above the method:
    objectToValidate - The object to validate.
    currentTarget - The object on the behalf of which the validation is performed.
    key - The key that identifies the source of objectToValidate
    validationResults - The validation results to which the outcome of the validation should be stored.

    For example:
    If this validator receives a string to validate (person id is string in this example):

    PersonIDValidator validator = new PersonIDValidator(3);

    ValidationResults results = validator.Validate("123");

    Than the objectToValidate will be a string containing "123", as same as the currentTarget. The key will null.

    If the validator receives an object and validates only a property of that object:

    Person p = new Person();

    p.PersonID = "123";

    ValidationResults results = Validation.Validate<Person>(p);

    Than the objectToValidate will be "123", the currentTarget will be the person instance and the key will be "PersonID".

    Now, when you understand what each parameter does, we can go on and implement the validation logic. The validation logic in this case is very simple, since this is not the main purpose of this guide. The validator checks the input string and make sure it ends with the country code.

    protected override void DoValidate(object objectToValidate, object currentTarget, string key, ValidationResults validationResults)

    {

        // Get the object to validate

        string id = (string)objectToValidate;

     

        // Check if it is valid

        string end = countryCode.ToString().Trim();

        bool isValid = id.EndsWith(end);

     

        // If the negated property is false, and the id is not valid, log a validation error

        if (isValid == Negated)

        {

            LogValidationResult(validationResults, this.MessageTemplate, currentTarget, key);

        }

    }

    Change the Message Templates of the Custom Validator

    13. When you log a validation error you also add a message that contains the error description. By default the validator uses 2 message templates - one for the default validation error and one for the negated error. To change the validation error messages, expand the provider library project (Bursteg.ProvidersLibrary) and double click the Resources.resx file under the properties folder.

    Custom Validator with Application Block Software Factory

    This will open the Resource Editor and let you edit the messages. Notice that there 2 message templates - PersonIDValidatorNegatedDefaultMessage (for the negated template) and PersonIDValidatorNonNegatedDefaultMessage (for the non-negated template).

    Custom Validator with Application Block Software Factory

    Add Design-Time support for the Custom Validator

    When you develop Custom Validator for your project, you'll probably want to add design-time support that will enable developers configure the new validator from the Enterprise Library Configuration Tool just as simple as they would do for every other out-of-the-box validator.

    Creating Design-Time providers node is a recipe that is similar to all Enterprise Library providers and not something specific for each provider type.

    14. Add references to the Validation Application Block assemblies (Microsoft.Practices.EnterpriseLibrary.Validation and Microsoft.Practices.EnterpriseLibrary.Validation.Configuration.Design).

    Right click the design-time support project (Bursteg.ProvidersLibrary.Configuration.Design) and choose Application Block Software Factory -> Create Design-Time Provider Node.

    Custom Validator with Application Block Software Factory

    15. This will start a new wizard that will request some detail about the provider:

    Node Name is the name of the class that  will be generated and will represent the node in the configuration tree for this validator. In this guide it is PersonIDValidatorNode.

    Runtime Configuration Type is the type that holds the configuration data for this validator, and it was already created for us earlier. In this guide it is PersonIDValidatorData in the Bursteg.ProvidersLibrary assembly.

    Base Design Mode is the class to inherit from when generating the configuration type. If you noticed when we edited the validator class it inherited from a class called ValueValidator, which has a Design-Time configuration type called ValueValidatorNode in the Microsoft.Practices.EnterpriseLibrary.Validation.Configuration.Design assembly. You should pick this type as the Base Design Mode.

    Parent UI Node is the parent node in the configuration tree. The validator will always have parent of the following nodes:

    • SelfNode - When we want to validate an object (the whole object).
    • PropertyNode - When we want to validate a property of a type.
    • MethodNode - When we want to validate method parameters of a type
    • FieldNode - When we want to validate a field of a type.

    You would probably want to support all of the above options, but the wizard supports only a single selection. You can select SelfNode for now, and we will add the others by code.

    Cardinality indicates whether this validator can be applied more than once. For example - if you want to have 2 PersonIDValidators validating a single property. For this guide it is Single.

    Custom Validator with Application Block Software Factory

    16. Click Finish and Visual Studio will generate the code for the design-time configuration support. Notice that Bursteg.ProvidersLibrary.Configuration.Design has 4 new files for it:
    PersonIDValidatorNode.cs contains the class that represents the node in the configuration.
    CommandRegistrar.cs, ConfigurationDesignManager.cs and NodeMapRegistrar.cs contains the code that registers the new configuration node to the configuration tree in the location we have selected earlier in the wizard.

    Custom Validator with Application Block Software Factory 

    17. In step 14, we have selected to place the validator node under the SelfNode. If you want to add support for other node types, expand the CommandRegistrar.cs file node, and open the CommandRegistrar.PersonIDValidatorNode.cs file. This is a part of the file that registers the validator to the configuration.

    Edit the AddPersonIDValidatorNodeCommand method: duplicate the code that invokes AddSingleChildNodeCommand method as many as you need to add the validator parent nodes. Usually you will want to allow all 4 parent nodes, so you should copy it 4 times. The only difference between all the 4 invocations is the last parameter which is the type of the parent node. In step 14 we selected the SelfNode, so the other 3 invocations should use PropertyNode, MethodNode and FieldNode.

    sealed partial class CommandRegistrar

    {

        private void AddPersonIDValidatorNodeCommand()

        {

            AddSingleChildNodeCommand(

                Resources.PersonIDValidatorNodeUICommandText,

                Resources.PersonIDValidatorNodeUICommandLongText,

                typeof(PersonIDValidatorNode),

                typeof(Microsoft.Practices.EnterpriseLibrary.Validation.Configuration.Design.SelfNode));

     

            AddSingleChildNodeCommand(

                Resources.PersonIDValidatorNodeUICommandText,

                Resources.PersonIDValidatorNodeUICommandLongText,

                typeof(PersonIDValidatorNode),

                typeof(Microsoft.Practices.EnterpriseLibrary.Validation.Configuration.Design.PropertyNode));

     

            AddSingleChildNodeCommand(

                Resources.PersonIDValidatorNodeUICommandText,

                Resources.PersonIDValidatorNodeUICommandLongText,

                typeof(PersonIDValidatorNode),

                typeof(Microsoft.Practices.EnterpriseLibrary.Validation.Configuration.Design.MethodNode));

     

            AddSingleChildNodeCommand(

                Resources.PersonIDValidatorNodeUICommandText,

                Resources.PersonIDValidatorNodeUICommandLongText,

                typeof(PersonIDValidatorNode),

                typeof(Microsoft.Practices.EnterpriseLibrary.Validation.Configuration.Design.FieldNode));

     

     

        }

    }

    18. Change the captions of the Custom Validator as they will show in the configuration tool. Edit the resources file of the design-time support project (Bursteg.ProvidersLibrary.Configuration.Design).

    Edit the following resources:
    PersonIDValidatorNodeName - PersonID Validator 
    PersonIDValidatorNodeUICommandLongText - PersonID Validator Long Test
    PersonIDValidatorNodeUICommandText - PersonID Validator Command Text

    You'll soon see where each of these captions are displayed.

    19. I've already said that adding a design-time support for all provider types is done using the same recipe, but with validators you have to change some code in order to complete this task.

    Open PersonIDValidatorNode.cs and override the CreateValidatorData() method. As an implementation, paste the code from the ValueValidatorData property.

    public override ValidatorData CreateValidatorData()

    {

        PersonIDValidatorData data = new PersonIDValidatorData(this.Name);

        data.CountryCode = this.countryCode;

        data.Negated = this.negated;

        data.MessageTemplate = this.messageTemplate;

        data.MessageTemplateResourceName = this.messageTemplateResourceName;

        data.MessageTemplateResourceTypeName = this.messageTemplateResourceTypeName;

        data.Tag = this.tag;

        return data;

    }

    After you pasted the code, remove the ValueValidatorData property. You don't need it anymore.

    20. Change the constructor that receives PersonIDValidatorData as a parameter. Have it call the base class constructor with the parameter value.

    /// <summary>

    /// Initialize a new instance of the <see cref="PersonIDValidatorNode"/> class with a <see cref="Bursteg.ProvidersLibrary.Configuration.PersonIDValidatorData"/> instance.

    /// </summary>

    /// <param name="data">A <see cref="Bursteg.ProvidersLibrary.Configuration.PersonIDValidatorData"/> instance</param>

    public PersonIDValidatorNode(Bursteg.ProvidersLibrary.Configuration.PersonIDValidatorData data)

        :base(data)

    {

        ...

    }

    21. In order that the configuration tool can see your provider library and register the validator, you should copy the output assembly of the providers library and design type support (Bursteg.ProvidersLibrary.dll and Bursteg.ProvidersLibrary.Condiguration.Design.dll) to the Enterprise Library bin directory. By default it is C:\Program Files\Microsoft Enterprise Library 3.1 - May 2007\Bin.

    Test the Custom Validator

    Test the validator when used in code

    22. Open a new instance of Visual Studio (otherwise the configuration changes will not be applied) and create a new Console Application in order to test the validator. After the project is created, add a new class of type Person:

    public class Person

    {

        private string personID;

     

        public string PersonID

        {

            get { return personID; }

            set { personID = value; }

        }

    }

    23. Add references to the relevant assemblies:
    (Microsoft.Practices.EnterpriseLibrary.Common and Microsoft.Practices.EnterpriseLibrary.Validation) for the validation support, providers library(Bursteg.ProvidersLibrary) and System.Configuration.dll.

    24. In the Main method, create a new instance of person and set its ID. Use the validator to check if the ID is valid.

    // Initialize a new instance of Person

    Person p = new Person();

    p.PersonID = "123";

     

    // Validate a valid person ID

    PersonIDValidator validator = new PersonIDValidator(3);

    ValidationResults results = validator.Validate(p.PersonID);

    Debug.Assert(results.IsValid);

    DisplayValidationResults(results);

     

    // Validate an invalid person ID

    validator = new PersonIDValidator(2);

    results = validator.Validate(p.PersonID);

    Debug.Assert(!results.IsValid);

    DisplayValidationResults(results);

     

    // Validate a valid Person ID, but with negated = true

    validator = new PersonIDValidator(3, true);

    results = validator.Validate(p.PersonID);

    Debug.Assert(!results.IsValid);

    DisplayValidationResults(results);

     

    // Validate an invalid Person ID, but with negated = true

    validator = new PersonIDValidator(2, true);

    results = validator.Validate(p.PersonID);

    Debug.Assert(results.IsValid);

    DisplayValidationResults(results);

    Where DisplayValidationResults method can be something like:

    private static void DisplayValidationResults(ValidationResults results)

    {

        if (!results.IsValid)

        {

            foreach (ValidationResult result in results)

            {

                Console.WriteLine(result.Message);

            }

        }

    }

    Test the validator when configured using an attribute

    25. Add the validator attribute to the ID Property of the Person class.

    public class Person

    {

        private string personID;

     

        [PersonIDValidator(3)]

        public string PersonID

        {

            get { return personID; }

            set { personID = value; }

        }

    }

    26. In the Main method, validate the person instance to check if the ID is valid.

    // Validate the person

    results = Validation.ValidateFromAttributes<Person>(p);

    DisplayValidationResults(results);

    Test the validator when configured in the configuration file

    27. Add a new app.config to your Console Application, and edit with the Enterprise Library Configuration Tool.

    Custom Validator Validation Application Block

    28. Add the Validation Application Block Configuration Section. Right click the configuration file name node and choose New -> Validation Application Block.

    Custom Validator Validation Application Block

    29. This will add the Validation Application Block configuration section to your configuration file. Add a new type to validate. Right click the Validation Application Block node and choose New -> Type.

    Custom Validator Validation Application Block  

    30. This will open the Type Selector Dialog. Click Load an Assembly, and navigate to the output folder of the test project in order to find the assembly that contains the class Person. Finally, select the Person type.

    Custom Validator Validation Application Block  

    31. When configuring validations for a type using the configuration tool you cannot add validations straight on the type, you must group a few validations into a Ruleset. Add a new Ruleset by clicking the Person type and selecting New -> Ruleset. After the new Ruleset is added, you should change its name to something meaningful.

    Custom Validator Validation Application Block

    32. The Custom Validator we created in this guide validates objects of type string that are ID's of people. In the Person type, we would like to validate the PersonID property with this validator. To validate this property we first have to add it to the Person node. Right Click the Person node and choose New -> Property. Name the property exactly as it is named in the class definition, case sensitive.

    Custom Validator Validation Application Block  

    33. Add the PersonIDValidator to the PersonID node. Right click the PersonID node and select New-> PersonID Validator Command Text. (Now you see where the command text you edited in step 18 goes to.

    Custom Validator Validation Application Block

    34. After the validator is added, a new node will be shown called PersonID Validator (same as the PersonIDValidatorNodeName resource from step 18). Go to the properties pane and set its country code.

    In the Main method, validate the person instance to check if the ID is valid, and don't forget to use the name of the ruleset.

    // Validate the person

    results = Validation.ValidateFromConfiguration<Person>(p, "DefaultValidation");

    DisplayValidationResults(results);

    Conclusion

    Creating a Custom Validator is very easy to do, especially with the Application Block Software Factory. You create a provider library and generate the validator and you add design-type support using predefined recipes. Developers can use the new validator as simple as with any other out-of-the-box validator.

    You can download a sample project: Custom Validator with Application Block Software Factory

    If you have any questions or feedback, I'd like to hear.

    Enjoy!

    מחשבה: האם להוסיף קישור ל- LinkedIn Profile בקורות חיים?

    מחשבה: האם להוסיף קישור ל- LinkedIn Profile בקורות חיים?

    linkedin bursteg guy burstein רשתות חברתית הפכו לא מכבר לעובדה בשטח ולרובנו יש כבר יוזר לפחות ברשת חברתית אחת כמו LinkedIn, Facebook או אחרות. לדעתי LinkedIn היא הרשת החברתית הכי "מקצועית" באופיה ומאד מזכירה מבנה של קורות חיים - אנשים בונים לעצמם פרופיל שמכיל את ההשכלה שלהם, את הניסיון התעסוקתי שלהם ודואגים לשמור על הפרופיל מעודכן. בנוסף אנשים שמכירים אותך מהעבודה, מהלימודים וכו'  יכולים להמליץ עליך שכולם יכולם לראות.

    בימים אלו, אני מעדכן את קורות החיים שלי וכמובן שכתובת הבלוג רשומה בו. ואני שואל את עצמי, האם כדאי להוסיף גם קישור לפרופיל ב- LinkedIn. אולי זה יראה מוזר כי אנשים עדיין לא עושים את זה, אבל לדעתי זה יכול להיות טוב מאד! מתוך הנחה שהיום קורות חיים עוברים כמסמכים (Word, PDF, XPS) אפשר להניח שניתן שיכילו קישורים. תארו לכם שמישהו שמקבל את קורות החיים שלי יכנס לבלוג ויתרשם, ויכנס לפרופיל שלי ב- LinkedIn. הוא יוכל למצוא שם את קורות החיים שלי (שבתכל'ס יש לו אותם כבר...) אבל גם יוכל לראות את ההמלצות שיש עלי, הקשרים שיש לי ואולי אפילו למצוא מכרים משותפים. במידה ויש לנו כאלה הוא יוכל לקבל מהם דעה עוד לפני הראיון...

    אז מה דעתכם? להוסיף או לא להוסיף?

    Enterprise Library Screencasts

    Enterprise Library Screencasts

    Last Month I recorded several some Enterprise Library Screencasts.
    You can find them here:

     Enterprise Library Screencasts
    Logging Application Block Screencast
     Enterprise Library Screencasts 
    Exception Handling Application Block Screencast
     Enterprise Library Screencasts 
    Validation Application Block Screencast
     Enterprise Library Screencasts 
    Policy Injection Application Block Screencast

    Enjoy!

    Workflow External Data Exchange Service | ExternalDataExchange

    Workflow External Data Exchange Service - ExternalDataExchange

    In many scenarios, a workflow will need to communicate with the application in order to get some data, or notifications about state that has changed. In order to achieve that, the workflow needs a mechanism to exchange data with the application. Speaking in Windows Workflow Foundation terms, it is called External Data Exchange.

    This post explains how to communicate between a workflow and the hosting environment using the External Data Exchange Service. After reading this series of posts, you will be able to understand each of the elements in the following Diagram:

    External Data Exchange Service

    Long running workflows contain activities that their execution depends on "user" actions after the workflow has already started. In those scenarios, the workflow is waiting for the application to raise an event before it continue to the next activity.

    This is how you should implement such long running scenario using the External Data Exchange Service.

    External Data Exchange Interface

    The first thing you should do is to create an interface that will define the communication contract between the workflow and the application. This interface can contain methods and events and there are several rules for each of these elements. (These rules will be covered later in this post).

    This is an example of a External Data Exchange Interface. This interface is decorated with the [ExternalDataExchangeAttribute], and in this case contains a single event.

    [ExternalDataExchange]

    public interface IExternalData

    {

        event EventHandler<ActionEventArgs> Done;

    }

    External Data Exchange Events and Arguments

    Notice that the event declared in the above interface has a specific declarations type. The event uses the delegate EventHandler<T> that receives (object sender, T e) as arguments, where T is derived from EventsArgs class.

    The event arguments class is used to communicate between the application and a workflow must derive from System.Workflow.Activities.ExternalDataEventArgs class. This class holds the woflow instance ID that the raised event is related to.

    The ActionEventArgs that is used in the above interface is declared like this:

    [Serializable]

    public class ActionEventArgs : ExternalDataEventArgs

    {

         public ActionEventArgs(Guid instanceId, ...)

         :base(instanceId)                              

         {

            ...

         }

         ...

    }

    There are two about that event arguments class you should notice:

    1. The constructor receives a Guid instance ID and calls the base class constructor with thie ID as parameter.
    2. This class is Serializable.

    External Data Exchange Service

    After creating the External Data Exchange Interface, you should create a class that implements it and attach it to the Workflow Runtime using the ExternalDataExchangeService.

    The class you should create that implements the Extrenal Data Exchange Interface is the communication service. Use this code to add the External Data Exchange Service to the workflow runtime, and add your communication service as a service for exchanging data with workflows:


    // Add the ExternalDataExchangeService to the WorkflowRuntime

    ExternalDataExchangeService
    dataExchange = new ExternalDataExchangeService();

    // Add the Communication Service as a service for exchaging data
    workflowRuntime.AddService(dataExchange);

    dataExchange.AddService(new ActionCommunicationService());

    Handling External Event using HandleExternalEventActivity

    Using the HandleExternalEventActivity blocks the workflow until the registered event is raised by the communication service.

    In order to use the HandleExternalEventActivity and relate to a specific event of the communication service, simply follow the following steps:

    1. Select the HandleExternalEvent activity from the toolbox and drag and drop it onto the workflow design surface. Set the name property of the activity.
    2. Use the […] button of the InterfaceType property, and use the dialog to choose an External Data Exchange Interface that is declared in your project, or begin referenced by it.
    3. Choose an event to handle from that interface, by choosing an item from the drop-down list of the EventName property of the activity.
    4. The event sends a EventArgs instance as described in the previous post. In order to save It for later usage, declare a member:

       // Workflow1.cs
       public ActionEventArgs actionArguments = null;
    5. Set the e property of the activity. Use this property to inform the workflow in which member the received instance of EventArgs will be stored in.

    To test this activity, I used the following code snippet. It registers the ExternalDataExchangeService to the runtime, and adds my communication service that implements the Extrenal Data Exchange Interface. Then, It starts a workflow instance. After waiting for somw time, It raises the Done event. After raising the event, the workflow will continue its execution.

    // Add the ExternalDataExchangeService to the WorkflowRuntime

    ExternalDataExchangeService dataExchange = new ExternalDataExchangeService();

    workflowRuntime.AddService(dataExchange);

     

    // Add the Communication Service as a service for exchaging data

    ActionCommunicationService commSercice = new ActionCommunicationService();

    dataExchange.AddService(commSercice);

     

    WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(Workflow1));

    instance.Start();

     

    Thread.Sleep(20000);

    commSercice.DoYourWork(instance.InstanceId);

    Some Important Tips

    The above code snippet called the method DoYourWork of the communication service, that in some point raised the Done Event. Lets examine that code:

    public void DoYourWork(Guid instanceID)

    {
       ...
     

       if (this.Done != null)

       {

          this.Done(null, new ActionEventArgs(instanceID, ""));

       }

    }

    Keep in mind that there is a many-to-one relationship between the the communication service and the workflow instance. By that, I mean that a single communication service can communicate with many workflow instances. Therefore, you should always pass the workflow instance ID as parameter to methods, and also raise the event with the right instance ID. (Tip #1)

    Notice that when calling the event handlers, you pass an object sender. (Tip #2) This objects must be serializable, or else, you will receive a System.Workflow.Activities.EventDeliveryFailedException with message like: "Event <YourEventName> on interface type <YourInterfaceName> for instance id <YourInsatnceID> cannot be delivered." With an inner exception message: "EventArgs not serializable". Although the problem is with the sender and not with the event arguments, this is the inner exception message. A bit confusing.

    Using CallExternalMethodActivity

    After creating a communication service and registering it to External Data Exchange Service, we could use the HandleExternalEvent activity in order to handle events of that service. In the opposite direction, we are able to call methods of the service, pass parameters and get return values.

    If you are familiar with the HandleExternalEvent activity, using this activity is rather simple.

    In order to use the HandleExternalEventActivity and relate to a specific event of the communication service, simply follow the following steps:

    1. Select the CallExternalMethod activity from the toolbox and drag and drop it onto the workflow design surface. Set the name property of the activity.
    2. Use the […] button of the InterfaceType property, and use the dialog to choose an External Data Exchange Interface that is declared in your project, or begin referenced by it.
    3. Choose the method to call from that interface, by choosing an item from the drop-down list of the MethodName property of the activity.
    4. After choosing the method name, the parameters of that method will appear in the property grid. You can specify constant values as parameters, or you can bind the parameters to a specific workflow field using ParameterBindings (explained later). If the method has a return value, set the field to store the return value in.

    Parameter Binding

    When passing parameters to methods, you can choose to set a constant value (MaxItems = 2), or you can bind the parameter value to some variable. For example, if the value is not known in design time and only in runtime. One good example for the usage in workflow scenarios is to handle a communication service event that sends some event arguments, and call a method with the ID of the entity that its event was handled.

    To use Parameter Binding:

    1. Drag a CallExternalMethod activity or HandleExternalEvent activity to the design surface and choose the InterfaceType and EventName / MethodName.
    2. Click the […] button of a parameter / return value / event arguments.
    3. The Bind Property Dialog will open. You can use the first tab – "Bind to an existing member" to choose a public field or property to bind to the parameter, or you can use the other tab – "Bind to a new member" to create the member to create to store the value.

    External Data Exchange Service - Parameter Binding

    CallExternalMethodActivity.MethodInvoking and HandleExternalEventActivity.Invoked

    What if you want to change the state of your workflow instance after handling a event? What if you want to prepare the parameter object before calling the external method?

    In these cases, there couple of activity events that are very useful:

    CallExternalMethod activity has a event called MethodInvoking that occurs just before the external method is called by the workflow. You can easily handle this event by double-clicking the activity or by typing a handler method name in the property grid of the activity and pressing Enter. This will create the following handler method:

    private void CallExternalMethod1_MethodInvoking(object sender, EventArgs e)

    {

    }

    HandleExternalEventActivity activity has a event called Invoked that occurs right after the external event is handled by the workflow. You can easily handle this event by double-clicking the activity or by typing a handler method name in the property grid of the activity and pressing Enter. This will create the following handler method:

    private void HandleExternalEvent1_Invoked(object sender, ExternalDataEventArgs e)

    {

    }

    Notice that the event arguments of this event is ExternalDataEventArgs , which is the base class of the Event arguments we used to create the events in the External Data Exchange Interface.

    Summary

    In this post I talked about the windows workflow foundation way of communicating between the workflows and the hosting environment - External Data Exchange.

    I have shown how to declare an External Data Exchange Interface with External Data Exchange Events and how to register the External Data Exchange Service to the Workflow Runtime.
    Then, I talked about how to use the HandleExternalEvent Activity and the CallExternalMethod activity, and how to bind parameters to members in your workflow class. I also explained how to use the CallExternalMethodActivity.MethodInvoking and HandleExternalEventActivity.Invoked events to extend the operation done by these activities.

    You may want to read additional posts about Windows Workflow Foundation.

    Enjoy!

    Enterprise Library (Entlib) and WCF

    Enterprise Library (Entlib) and WCF

    Enterprise Library (Entlib) and WCFOne of the highlights of Enterprise Library 3.1 is the integration with Windows Communication Foundation (WCF).

    I've written a few tutorials to get you started with Enterprise Library (Entlib) and WCF.

    Enjoy!

    Developer Academy 2 - אירוע המפתחים הבא

    אירוע המפתחים הבא - Developer Academy 2

    הפוסט הזה מבשר על כך שההכזרות הרשמיות של גל המוצרים הבא של מיקרוסופט הכולל את Visual Studio 2008, Windows Server 2008 ו- SQL Server 2008 יתחילו באירוע רשמי בפברואר 2008. אחריו יהיה גל השקות בכל רחבי העולם, בדומה להשקות שהיו למוצרים כמו Vista, Exchange 2007 ו- Visual Studio 2005.

    אבל מה זה אומר לגבי הכנסים אצלנו?

    אירוע Tech-Ed, שמתוכנן לאיזור אפריל - מאי 2008, יהיה בעצם אירוע ההשקה של המוצרים האלה בישראל. בנוסף אליו מתוכנן גם אירוע מפתחים נוסף עד לסוף השנה הזאת, באיזור נובמבר - Developer Academy 2 (שם זמני). נובמבר זה ממש אוטוטו ובימים אלו מתחילים לחשוב על מה נדבר בכנס? איפה הוא יתקיים? מה תהיה מתכונת הכנס וכו'?

    וכאן מתחיל החלק שלנו - כיוון שעוד מספיק מוקדם להשפיע, אפשר כבר להעלות רעיונות לגבי מה צריך להיות בכנס או מה לא צריך להיות. כמובן שאפשר לדבר גם על לקחים מהכנס הקודם כמו סקר התכנים שבוצע, פגישה עם מובילים בקהילה, שילוב מרצים חדשים ועוד.

    אז מה היינו רוצים שיהיה באירוב המפתחים הבא?

    About the 2008 Products Launch

    This week at the Worldwide Partner Conference, Kevin Turner announced that Microsoft will be launching Windows Server 2008, Visual Studio 2008 and SQL Server 2008 in Los Angeles on February 27, 2008. This event will kick off a “launch wave” of hundreds of events that Microsoft will host worldwide.

    But...

    This announcement doesn't mean than these products will not be RTM'ed before that date. Most important for me is Visual Studio 2008 of course, that will be released by the end of the year, according to some Microsoft sources (Soma, Scott Guthrie and more).

    Regarding Visual Studio 2008 Beta 2 - The plan is to make it available for download in the next three weeks with a go-live license. This means that we are only three weeks away from being able to use .NET 3.5 in production!

    Enjoy!

    Oracle Workflow Persistence Service

    Oracle Workflow Persistence Service

    Oracle Workflow Persistence ServiceI always get this question from customers who consider using Windows Workflow Foundation (WF) and using Oracle DB. Today I ran into Mick's Blog, and found an implementation of Oracle Windows Workflow Persistence Service.

    I haven't tested it yet, and the above post clearly states it has not been roughly tested - but it can be a very good starting point.

    Enjoy!

    Data Access Guidance Package - The Repository Factory

    Data Access Guidance Package - The Repository Factory

    The Web Service Software Factory contains a set of guidance related to the creation of data access components, which is a much larger problem space than just services. The patterns & practices team have decided to split this package out into its own project and name it: The Repository Factory.

    The Data Access Guidance Package - The Repository Factory is not intended to be a be-all-does-everything ORM solution. Instead, it's a lightweight code generator that automates most of the hand-coding needed to build domain model object and persist them to a database.

    Enjoy!

    איך להיפטר מקוראי הבלוג שלך

    blogging בהמשך לסדרת הפוסטים "טיפים וטריקים: כתיבת פוסטים באתר הבלוגים", היום נתקלתי בפוסט שחשוב שכל בלוגר יכיר.
    מדובר בפוסט: "10 דרכים להיפטר מקוראי הבלוג שלך" שמצאתי בבלוג של אח"י דקר.

    שמחתי לראות שהבלוג שלי עומד בכללים... ושלכם?

     אם אתם עדיין לא מכירים את סדרת הפוסטים"טיפים וטריקים: כתיבת פוסטים באתר הבלוגים", הנה רשימת הפוסטים בסדרה:

  • טיפים וטריקים: כתיבת פוסטים באתר הבלוגים - הקדמה
  • מי קהל היעד של הבלוג שלך?
  • אז מה החלטת? עברית או אנגלית?
  • למה שמישהו ירצה לקרוא את הבלוג שלך בעצם?
  • Hello World! - הפוסט הראשון
  • לא יאמן עד כמה זה נכון...
  • מה הרייטינג שלך?
  • איך להשתמש בגלריה באתר הבלוגים?
  • מי אמר עברית ולא קיבל?
  • לצרוב את ה- Feeds שלך
  • מראה, מראה שעל הקיר - למי הבלוג הכי יפה בעיר?
  • אליה וקוד בה...
  • התוספת שתופסת

     

    למי שעדיין לא יצא לקרוא וליישם - אתם מוזמנים!
  • Create Sharepoint Site By Template in Code - Workflow Custom Activity

    Create Sharepoint Site By Template in Code - Workflow Custom Activity

    This post documents the steps I took in order to build a custom activity that creates a Sharepoint site by template. This post also talks about the steps needed in order to add this custom activity into the Sharepoint designer.

    1. Create The Custom Activity

    In Visual Studio 2005, I created a new Activity Library Project for my new activity. I created a new activity that derives from Activity (and not from the default SequentialActivity).

    namespace Bursteg.CustomActivities

    {

        public partial class CreateSiteFromTemplate : Activity

        {

            public CreateSiteFromTemplate()

            {

                InitializeComponent();

            }

     }

    This custom activity creates the new site based on an input template name, and according to the value of a selected column in the item who triggered the workflow. To name this action I'd say: Creates a new site by template X, and calls it like the value of field Y.
    In order for the activity to receive both values, 2 dependency properties should be created: SiteNameField and TemplateName, both of type string. (You can use the wdp code snippet in order to create them.)

    public static DependencyProperty SiteNameFieldProperty = DependencyProperty.Register("SiteNameField", typeof(string), typeof(CreateSiteFromTemplate));

     

    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]

    [ValidationOption(ValidationOption.Required)]

    public string SiteNameField

    {

        get return ((string)(base.GetValue(CreateSiteFromTemplate.SiteNameFieldProperty))); }

        set base.SetValue(CreateSiteFromTemplate.SiteNameFieldProperty, value); }

    }

     

    public static DependencyProperty TemplateNameProperty = DependencyProperty.Register("TemplateName", typeof(string), typeof(CreateSiteFromTemplate));

     

    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]

    [ValidationOption(ValidationOption.Required)]

    public string TemplateName

    {

        get return ((string)(base.GetValue(CreateSiteFromTemplate.TemplateNameProperty))); }

        set base.SetValue(CreateSiteFromTemplate.TemplateNameProperty, value); }

    }

    Since the activity has to perform some actions on the list item that has triggered the workflow, get its values and etc., it must receive some additional properties that gives the context to the activity: __Context, __ListId and __ListItem:

    public static DependencyProperty __ContextProperty = DependencyProperty.Register("__Context", typeof(WorkflowContext), typeof(CreateSiteFromTemplate));

     

    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]

    [ValidationOption(ValidationOption.Required)]

    public WorkflowContext __Context

    {

        get return ((WorkflowContext)(base.GetValue(CreateSiteFromTemplate.__ContextProperty))); }

        set base.SetValue(CreateSiteFromTemplate.__ContextProperty, value); }

    }

     

    public static DependencyProperty __ListItemProperty = DependencyProperty.Register("__ListItem", typeof(int), typeof(CreateSiteFromTemplate));

     

    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]

    [ValidationOption(ValidationOption.Required)]

    public int __ListItem

    {

        get return ((int)(base.GetValue(CreateSiteFromTemplate.__ListItemProperty))); }

        set base.SetValue(CreateSiteFromTemplate.__ListItemProperty, value); }

    }

     

    public static DependencyProperty __ListIdProperty = DependencyProperty.Register("__ListId", typeof(string), typeof(CreateSiteFromTemplate));

     

    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]

    [ValidationOption(ValidationOption.Required)]

    public string __ListId

    {

        get return ((string)(base.GetValue(CreateSiteFromTemplate.__ListIdProperty))); }

        set base.SetValue(CreateSiteFromTemplate.__ListIdProperty, value); }

    }

    Notice that the __Context Property is of type WorkflowContext. This property is the most important for the implementation of this activity.

    As for the implementation of the activity, we have to override the Execute method. In the following code I am using the WorkflowContext to get a reference to the list of custom templates in my site and creating the new site based on the input template. As for the name of the site, I am taking the value of the input field name for the current item. Notice the usage of all the input properties.

    protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)

    {

        // Get the list of custom templates in this site

        SPWebTemplateCollection templates = __Context.Site.GetCustomWebTemplates(1037);

     

        // Find the input template.

        SPWebTemplate template = templates[this.TemplateName];

     

        // Get the list in which this workflow was started

        SPList list = __Context.Web.Lists[new Guid(__ListId)];

     

        // Get the item that has triggered the workflow

        SPListItem item = list.Items[__ListItem - 1];

     

        // Get the value of the selected field

        string siteName = (string)item[this.SiteNameField];

     

        // Create the new site

        SPWeb newWeb = __Context.Web.Webs.Add(siteName, siteName, siteName, 1037, template, false, false);

     

        // This activity has finished its job.

        return ActivityExecutionStatus.Closed;

    }

     

    You can download a sample project with the custom activity I created.

    2. Register the activity with Sharepoint

    In order to use this custom activity in Sharepoint, the assembly must be signed and registered to the GAC. You should use a new or existing strong name key file and assign it the the custom activity project. (Simply go to the project properties, select the Signing pane and check the "Sign the assembly" checkbox.)
    You can register the assembly to the GAC by dragging it from the bin\debug\ directory to the c:\windows\assembly directory or you can use the command line tool:

    "C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe" -i  c:\....\bin\debug\MyAssembly.dll /f.

    A better approach (which I took) is to use the command line tool in the post-build event of the project. Simply go to the project properties, select the Build Events pane, and use the following command:

    "C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe" -i  $(TargetPath) /f.

    In this way, after every build, the new assembly will be registered to the GAC.

    Now that Sharepoint can use the assembly, you have to add its types as authorized types for Sharepiont. You can do that by editing the web.config of your site (For example: C:\Inetpub\wwwroot\wss\VirtualDirectories\8080\web.config). At the bottom of the file, locate the <System.Workflow.ComponentModel.WorkflowCompiler> tag and under the <authorizedTypes> add a new authorized type with the assembly details:

    <System.Workflow.ComponentModel.WorkflowCompiler>

      <authorizedTypes>

        <authorizedType Assembly="Bursteg.CustomActivities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0a01dfd64655f7f6" Namespace="Bursteg.CustomActivities" TypeName="*" Authorized="True" />

      </authorizedTypes>

    </System.Workflow.ComponentModel.WorkflowCompiler>

    I used .Net Reflector in order to get the full assembly name including the version, culture and public key token.

    3. Register the custom activity to Sharepoint Designer

    Sharepoint Designer has a simple and easy-to-use user interface for creating workflows. It allows selecting actions from a list of available actions and supplying parameters. You can add the new activity to that list and make it just like any other out-of-the-box activities.

    To do this, open the wss.actions file located in C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\1033\Workflow. Under the <WorkflowInfo> tag, locate the <Actions> tag and add a new action like the following:

    <Action Name="Create Site by Template"

      ClassName="Bursteg.CustomActivities.CreateSiteFromTemplate"

      Assembly="Bursteg.CustomActivities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0a01dfd64655f7f6"

      AppliesTo="list"

      Category="Custom"

      UsesCurrentItem="true">

      <RuleDesigner Sentence="Create a new site called %1 by template %2">

        <FieldBind Field="SiteNameField" Text="Site Name Column" Id="1" DesignerType="writablefieldNames"/>

        <FieldBind Field="TemplateName" Text="Template Name" Id="2" DesignerType="TextArea"/>

      </RuleDesigner>

      <Parameters>

        <Parameter Name="__Context" Type="Microsoft.SharePoint.WorkflowActions.WorkflowContext, Microsoft.SharePoint.WorkflowActions" />

        <Parameter Name="__ListId" Type="System.String, mscorlib" Direction="In" />

        <Parameter Name="__ListItem" Type="System.Int32, mscorlib" Direction="In" />

        <Parameter Name="SiteNameField" Type="System.String, mscorlib" Direction="In" />

        <Parameter Name="TemplateName" Type="System.String, mscorlib" Direction="In" />

      </Parameters>

    </Action>

    There are few things you should notice about this action declaration. The action name will appear in the list of actions in the Sharepoint Designer. The AppliesTo = "list" and UsesCurrentItem="true" attributes will cause the workflow to pass the values of the list id and list item properties to the activity. The Sentence will appear in the design surface when you add the action, and the %1 and %2 will be replaced with the designers that match the FieldBinding tags by the Id attribute.

    4. Testing the Custom Activity

    Create Sharepoint Site By TemplateIn order to test this activity create a site and save it as a template. Create a new list with a column that will hold the name of the site to create. For example, you can have a list of courses in which the course code will be the site name.

    In Sharepoint Designer create a new workflow, select the relevant list and select a start option (Start when item is created). In Step 2 of the designer, create a new step and add the Create Site by Template action from the Custom category. Choose the column from which to take the name of the site to create, and supply the name of the site template (should be something that ends with .stp).

    Click Finish to apply the workflow to the site.

    Now, go to the list and create a new item. The workflow should start and create the new site. You can find it the list of sites under your site collection.

    I hope this tutorial helps, and I'd love to hear any feedback, comments and questions about it.

    You can download a sample project with the custom activity I created.

    Enjoy!

    More Posts Next page »