Custom History Form in Skelta

The main feature of any workflow engine is tracking the histories of transactions for its process flows, normally all workflow engines will support for this feature but when you want track the custom history for a process flow like Approval history (approver, result, date, role, department and comments) then you need to dig your own logic based on your workflow engine. Normally history is collection log user or actor action of activities of a process flow, before displaying it we need to consolidate as single unit before it displaying it. Here some possible methods for showing custom history for a Skelta process flow.

  1. Maintaining consolidated custom history into database and Showing with Custom ASPX page.
  2. Maintaining consolidated custom history into database and Showing with Skelta Form.
  3. Maintaining consolidated custom history into List and showing with form which created on List.

Let us see merits and demerits about all above methods. First method gives us full customization on displaying and formatting data, here we don’t have limitation of Skelta form but it will take extra time to design and develop ASPX form, next things we have to replicate same thing for each process flow. In second method same like first one except custom ASPX page, here we need to design the form instead of ASPX page, but it requires special form rendering method because it is required to convert db data to Skelta form xml format and other thing is we may encounter with Skelta form limitations, more over nobody will choose this method.

Third method fully depends on Skelta List. History form should be created on list and this form not updating on anything on list table, instead of it is going to read the value from list tables, so we need to prepare the data for history list table using forms or Skelta API or both (in most case) here consolidating data little complex work. Skelta List can be stored in the repository database and it won’t support for custom database. Comparing with first two solutions this is good solution but still it has own drawbacks, so we need a generic solution following which fulfill following needs.

  1. Need a generic ASPX page for rendering History form for all process flows, so it will reduce development time.
  2. Consolidated History data should not depend on database.
  3. System should provide the option to Map the History form(s) for each Process flow otherwise every time we need to create or modify the history form render ASPX form.

Now we are going to have new solution with above requirements. Initially we need to do the following prerequisites.

  1. History Form Custom Property for Start Activity.
  2. History Skelta Form Design (for each work flow).
  3. SKHistory variable to workflow (for each work flow).
  4. Generic History Form Render ASPX page.

First and last steps are single time setups and we need to do other two steps for each work flows because history data and display format will vary.

History Form Property for Start Activity:

Skelta provides option to create our own custom property for custom activity, with this feature we going to create new custom property for mapping the history form for current process flow. Here we can use the FormDetails type property to specify the history form.

If you are familiar with creating custom activities for Skelta then you may breathe easy, don’t worry if you haven’t any prior experience on this, you just follow the following steps.

  1. Take backup this folder C:\Program Files\Skelta\BPM.NET\WorkflowElements\Default\en-US\Actions\XML. Path may vary according to your Skelta environment. Make sure that you copied on different drive if you copied on same Skelta BPM.Net folder then you may encountered by some problem, so you must consider the above point.
  2. Open the Actions.xml from above specified folder and find out the start activity section, refer the following image.
  3. Navigate to properties tag of Start activity and paste the following property node.
    
    
    
    Actually this property is modified version of Skelta Invoke form activity’s Form Details property. Here I changed the name of property; display name and mandatory field, rest of things are optional.
  4. Save the modified Actions.xml then close it.
  5. Stop IIS, Skelta Services and clear all required temporary files then start all services.
  6. Login as admin on your custom application or enterprise console then navigate Workflow list and create a new workflow. Now you can able to see the custom property which we added few moments before on process designer. Refer the following image.

  7. Click on History Form Details button, it will pop ups the Form Details window here you can select your History Form.
  8. Now publish process flow, actually step 7 not required now, it just for understanding the History Form details property.

We successfully completed first step, now we will move to next step.

SKHistory XML Variable:

SKHistory is a user defined XML variable (normally in Skelta process flow system variable start with SK) for storing custom history of the current process flow, schema of this variable is depends on History Form (the Skelta Form for showing history), it means SKHistory variable should be created based on History Form definition. If you want to show the custom history for a process flow then you should add this variable on your process flow and update it according to your requirements, so now we are not worry about database or duplicating the data.

Generic ASPX Form for Showing History Form:

Since we are using a custom property on start activity we need a special Form rendering method for Web form to access this custom property and render the specified form along with history data. But we need to consider the generic web form because we need to avoid creating new form for each process or application. Let us step into our business.

For preparing a generic render form we need to follow the following steps.

  1. Get History Form Id.
  2. Get metadata information for History Form.
  3. Get value SKHistory xml variables.
  4. Get the form definition.
  5. Render the form renderer.

Getting History Form Id:

Previously we seen how to add the custom property called History Form on Skelta process flow start activity, now we are going to use that property to get the form Id. Refer the following code snippet.

/// 
        /// Get the history form for current workflow
        /// 
        /// Workflow context/// History form details
        public static FormItem GetHistoryForm(this Workflow.NET.Engine.Context wfContext)
        {
            string[] strHistorForm = new string[2];
            FormItem frmItem = default(FormItem);
            if(wfContext != null && wfContext.ProcessDefinition.Actions["Start"].Properties.Contains("HistoryForm"))
            {
                string strValue = Convert.ToString(wfContext.ProcessDefinition.Actions["Start"].Properties["HistoryForm"].Value);
                if (!string.IsNullOrEmpty(strValue))
                {
                    string[] strValues = strValue.Split('?');
                    frmItem.FormName = strValues[0];
                    frmItem.Id = strValues[2];
                    frmItem.Version = Convert.ToInt32(strValues[1]);
                }
            }
            return frmItem;
        }
Template Class Form Item:
public struct FormItem
    {
        public string FormName { get; set; }
        public string Id { get; set; }
        public int Version { get; set; }
        public bool IsEmpty()
        {
            return string.IsNullOrEmpty(this.Id);
        }
    }
Metadata for Form:

Normally action forms are triggered from Skelta work list with a secured parameter, this parameter contains all required metadata about current work item and form but this parameter encrypted to share it across pages so we decrypt first then we can use it, here Skelta provides a adapter class to help us on this process, that adapter is WebWorkItemAdapter.

Code snippet for processing secured parameter.

WebWorkItemAdapter webAdapter = new WebWorkItemAdapter();
webAdapter.ProcessWebChannelRequest();

Now we decrypted and initialized the WebWorkItemAdapter object from that we can get the instance for current work item and form name other details, the following code snippet help us to get the work item and form.

Get SKHistory Variable Value:

SKHistory contains collection of log data to specific to process flow, it is like input xml variable for Invoke form activity, so we need to get the SKHistory variable Raw xml to fill the Skelta History form state. Find the code snippet to get the SKHistory variable value.

Workflow.NET.Engine.Context ctxContext = Workflow.NET.Engine.Context.GetContext(intInstanceID, strApplication);
inputInstanceXML = ctxContext.XmlVariables["SKHistory"].RawXml;
Form Definition:

Before rendering the given form on Form Renderer we need to get form definition object otherwise form render can’t render the given form. We can get form definition object by using below code snippet.

ListDefinition lstDef = ListDefinition.GetList(this.appObject, this.listParams.ListName);
    this.frmItem = new Skelta.Repository.List.ListItem(lstDef, this.listParams.ListItemId, this.listParams.VersionStamp);
    ListTableForm frmTable = (ListTableForm)this.frmItem.ListForm.FindById("_sys_fmdef_new");
    string strDef = Convert.ToString(((ListTextDataItem)frmTable.Records[0].FindControlByID("_sys_def_xml")).Value);
    if (string.IsNullOrEmpty(strDef))
    {
        //Show error message or thorw exception here if you required
    throw new Exception("Blank Form Definition");
        Response.End();
    }
    BaseForm frmBase = BaseForm.LoadDefinitionXml(strDef);
    frmBase.TopLevelForm.FormAssociatedItemID = this.frmItem.Id.ToString();
    return frmBase;

First we need to get the Skelta List definition for Forms List (Skelta is a generic master list to access all created Skelta elements for a repository each Skelta elements have their own list definition based on this definition Skelta List will display the master list). Based on this Forms List definition we can get ListItem object for given Form, by digging this list item we can get Form definition raw xml from _sys_def_xml system property. Finally convert this raw xml to BaseForm instance; this is Skelta API representation for Form Renderer.

Consolidated code snippet for Form Definition as follows,

/// 
    /// Get Form definition for given Skelta form
    /// 
    /// Form Definition object
    private BaseForm GetFormDefinitionXml()
    {
        try
        {
            bool isFlag = false;
            ListDefinition lstDef = ListDefinition.GetList(this.appObject, this.listParams.ListName);
            this.frmItem = new Skelta.Repository.List.ListItem(lstDef, this.listParams.ListItemId, this.listParams.VersionStamp);
            ListTableForm frmTable = (ListTableForm)this.frmItem.ListForm.FindById("_sys_fmdef_new");
            string strDef = Convert.ToString(((ListTextDataItem)frmTable.Records[0].FindControlByID("_sys_def_xml")).Value);
            if (string.IsNullOrEmpty(strDef))
            {
                //Show error message or thorw exception here if you required
                throw new Exception("Blank Form Definition");
                Response.End();
            }
            BaseForm frmBase = BaseForm.LoadDefinitionXml(strDef);
            frmBase.TopLevelForm.FormAssociatedItemID = this.frmItem.Id.ToString();
            return frmBase;
        }
        catch
        {
            return null;
        }
    }
Form Rendering:

Now we are ready for rendering workflow initiator form on web form. We can able render form by using following code snippet.

Skelta.Repository.List.ListItem item;
    try
    {
        item = new Skelta.Repository.List.ListItem(this.lstDef, fidForm);
    }
    catch
    {
        //Handle exception here
        return;
    }
    if (!item.IsFolder)
    {
        this.listParams.ListItemId = fidForm;
        this.listParams.VersionStamp = item.CurrentVersion.VersionStamp;
        this.listParams.ListName = this.lstDef.Name;
        this.frmRequest = new Skelta.Forms.Web.FormRenderer();
        frmRequest.ID = "frmRequest";
        frmRequest.Width = Unit.Percentage(100);
        frmRequest.Height = Unit.Percentage(100);
        this.frmRequest.FormDefinition = this.GetFormDefinitionXml();
        this.Title = this.frmRequest.FormDefinition.Name;
        frmRequest.FormDefinition.AssociatedListName = "Forms List";
        if (!base.IsPostBack)
            this.frmRequest.FormDefinition.FormInstanceXml = "";
        this.frmRequest.InDesignMode = false;
        this.frmRequest.HeaderHidden = true;
        this.pnlForm.Visible = true;
        this.pnlForm.Controls.Add(this.frmRequest);
        this.frmRequest.OnSaveClick += new Skelta.Forms.Web.SaveClickDelegate(frmRequest_OnSaveClick);
    }

Create Form Renderer control instance and assign the Form Definition which we already generated from previous step. Set design mode false then add the renderer to panel.

Find the consolidated code snippet for Form rendering with full signature,

/// 
/// Render the History form to custom aspx page
/// 
private void RenderForm()
{
    try
    {
        //Get the technician form  id from web.config file
        Guid fidForm = new Guid(strFormGuid);
        Skelta.Repository.List.ListItem item;
        try
        {
            //Try to get the technician form information
            item = new Skelta.Repository.List.ListItem(this.lstDef, fidForm);
        }
        catch
        {
            this.ShowMessage("Cannot find the specified Item.");
            return;
        }
        if (!item.IsFolder)
        {
            this.listParams.ListItemId = fidForm;
            this.listParams.VersionStamp = item.CurrentVersion.VersionStamp;
            this.listParams.ListName = this.lstDef.Name;

            this.frmRender = new Skelta.Forms.Web.FormRenderer();
            frmRender.ID = "frmRender";
            frmRender.Width = Unit.Percentage(100);
            frmRender.Height = Unit.Percentage(100);

            //Assign the form definition to form renderer
            this.frmRender.FormDefinition = this.GetFormDefinitionXml();
            //Get the current form name
            strFormName = this.frmRender.FormDefinition.Name.ToString();
            //frmRender.FormDefinition.FormAssociatedItemID = fidForm.ToString();
            frmRender.FormDefinition.AssociatedListName = "Forms List";
            if (!base.IsPostBack)
                this.frmRender.FormDefinition.FormInstanceXml = inputInstanceXML;

            this.frmRender.InDesignMode = false;
            this.frmRender.HeaderHidden = true;
            this.pnlFormRender.Visible = true;
            this.pnlFormRender.Controls.Add(this.frmRender);
            this.frmRender.OnSaveClick += new Skelta.Forms.Web.SaveClickDelegate(frmRender_OnSaveClick);
        }

    }
    catch (Exception errMsg)
    {
        //Handle the exception
    }
}

Recently I created the Starter Kit for Skelta called as Smart Skelta Starter Kit for 2008, you can download it from http://smartskelta.codeplex.com In this kit you can able to find out History form from Item templates of Web Site.

Conclusion:

In this article we seen how to create Generic History Form for Skelta, I hope this article will help you on reduce your development time on developing custom history form for each skelta process flow. If you feel free please send your valuable feedbacks to eshock.vasan@gmail.com

Read more


Rendering a Workflow Initiator Skelta Form

Skelta provides the feature to initiate a workflow from a Skelta form by associating it. In Skelta Enterprise Console they given the provision to fill the form and submit it but this form fill page has own fence i.e Top level form only display here if you created workflow initiator form under a folder it will not display here and mainly they are not documented how to render the form fill page and initiate the workflow or provided as component, so we need to create our own Form fill page for workflow initiation if we are using the custom application. Still we can’t achieve this without Skelta APIs but things are not documented.

This section explains about how to render a workflow initiator form on an ASPX web form and initiate the workflow process. In my previous article I explained about how to show process templates (workflow initiator form to user). If you are not seen, I recommended to do a quick visit on it because it will give more idea about this article.

Let’s Get Started

In order to achieve our goal we need to do following process steps,

  1. Get metadata information about given form.
  2. Get the Form Definition.
  3. Render the Form Renderer.
  4. Initiate the Workflow process.

Metadata for Workflow Initiator Form:

In this step we need to collect all required metadata information about workflow initiator form i.e Form Id, Form Title, Application Name, User Id, Workflow Name and InstanceXML for form.

Application Name is always mandatory for most of action in Skelta because it’s a main unit or container for Skelta elements. Next important thing is Form Id without this Id we can’t move further. For a few seconds we will go back to my article about Process Templates, if we scroll down to BindProcessTemplate method, there we can able to find out following code snippet for passing Form Id and Repository Name to AddRequest.aspx page,

//TODO: Modify this according to your needs
string strPath = this.Page.ResolveClientUrl(string.Format("AddRequest.aspx?FID={0}&Application={1}",nodRequest.Value.Replace("-",""),lstForm.ApplicationName));

Let assume now we are in AddRequest.aspx so we can able to get Application Name and Form Id from Query String as below.

//TODO: Modify this according to your needs
string strFID = Request.QueryString["FID"];
string strAppName = Request.QueryString["Application"];

Now we need to get the mapping fields for form’s Title, Description, Associated Workflow and Instance XML by using following code snippet.

//Replace this code based on your requirements
                intUserId = Convert.ToInt32(Session["UserId"]);
                //Modify this code according to your user provider 
                usrContext = new Skelta.Entity.UserContext(intUserId.ToString(), new Skelta.Core.ApplicationObject(strAppName), "sqlprovider", "", "", false);
                this.listParams = new ListPageParameters();
                this.listParams.ResolveCurrentUri();
                this.appObject = new ApplicationObject(this.usrContext.Repository.ApplicationName);
                this.listParams.ApplicationName = this.appObject.ApplicationName;
                this.lstDef = ListDefinition.GetList(this.appObject, "Forms List");
                this.lstItmCollection = new Skelta.Repository.List.ListItemCollection(this.lstDef);
                this.listParams.ListName = this.lstDef.Name;
                this.listParams.ListId = this.lstDef.Id;

                foreach (KeyValuePair pair in this.lstItmCollection.GetRecordsFullQueryFieldMapping())
                {
                    if (pair.Value.Name == "Title")
                    {
                        this.colTitle = pair.Key.ToString();
                    }
                    else if (pair.Value.Name == "Description")
                    {
                        this.colDescription = pair.Key.ToString();
                    }
                    else if (pair.Value.Name == "BoundToWorkflow")
                    {
                        this.colBoundToWorkFlow = pair.Key.ToString();
                    }
                    else if (pair.Value.Name == "InstanceXML")
                    {
                        this.colInstanceXML = pair.Key.ToString();
                    }
                }

UserContext is used to share the current user information to across Skelta assemblies and web forms. ListDefinition will get the Forms List, ListItemCollection will give the Mapping fields collection and mapping fields used to access the properties of given Skelta Form.

Find the below consolidated code snippet to getting metadata information.

protected void Page_Load(object sender, EventArgs e)
    {
        try
        {
            //Let assume that we are getting Form Id and Application from Query String
            //Modify this code according to your requirements
            strFID = Request.QueryString["FID"];
            strAppName = Request.QueryString["Application"];
            if (string.IsNullOrEmpty(strAppName))
                throw new Exception("Invalid Repository Name");
            //Check user login session and replace this code with your appropriate requirements
            if (Session["UserId"] != null)
            {
                //Replace this code based on your requirements
                intUserId = Convert.ToInt32(Session["UserId"]);
                //Modify this code according to your user provider 
                usrContext = new Skelta.Entity.UserContext(intUserId.ToString(), new Skelta.Core.ApplicationObject(strAppName), "sqlprovider", "", "", false);
                this.listParams = new ListPageParameters();
                this.listParams.ResolveCurrentUri();
                this.appObject = new ApplicationObject(this.usrContext.Repository.ApplicationName);
                this.listParams.ApplicationName = this.appObject.ApplicationName;
                this.lstDef = ListDefinition.GetList(this.appObject, "Forms List");
                this.lstItmCollection = new Skelta.Repository.List.ListItemCollection(this.lstDef);
                this.listParams.ListName = this.lstDef.Name;
                this.listParams.ListId = this.lstDef.Id;

                foreach (KeyValuePair pair in this.lstItmCollection.GetRecordsFullQueryFieldMapping())
                {
                    if (pair.Value.Name == "Title")
                    {
                        this.colTitle = pair.Key.ToString();
                    }
                    else if (pair.Value.Name == "Description")
                    {
                        this.colDescription = pair.Key.ToString();
                    }
                    else if (pair.Value.Name == "BoundToWorkflow")
                    {
                        this.colBoundToWorkFlow = pair.Key.ToString();
                    }
                    else if (pair.Value.Name == "InstanceXML")
                    {
                        this.colInstanceXML = pair.Key.ToString();
                    }
                }
                if (this.Request["FID"] != null)
                {
                    this.RenderForm(new Guid(this.Request["FID"].ToString().Replace("#", "")));
                }
            }
            else
            {
                throw new Exception("Session has been expired,please login again.");
            }

        }
        catch (Exception errMsg)
        {
            //Handle exception here
        }
    }
Form Definition:

Before rendering the given form on Form Renderer we need to get form definition object otherwise form render can’t render it. We can able to get form definition object by using below code snippet.

ListDefinition lstDef = ListDefinition.GetList(this.appObject, this.listParams.ListName);
            this.frmItem = new Skelta.Repository.List.ListItem(lstDef, this.listParams.ListItemId, this.listParams.VersionStamp);
            ListTableForm frmTable = (ListTableForm)this.frmItem.ListForm.FindById("_sys_fmdef_new");
            string strDef = Convert.ToString(((ListTextDataItem)frmTable.Records[0].FindControlByID("_sys_def_xml")).Value);
            if (string.IsNullOrEmpty(strDef))
            {
                //Show error message or thorw exception here if you required
          throw new Exception("Blank Form Definition");
                Response.End();
            }
            BaseForm frmBase = BaseForm.LoadDefinitionXml(strDef);
            frmBase.TopLevelForm.FormAssociatedItemID = this.frmItem.Id.ToString();
            return frmBase;

First we need to get the Skelta List definition for Forms List (Skelta List is a generic master list to access all created Skelta elements for a repository and each Skelta elements have their own list definition, based on this definition Skelta List will display the master list). Based on this Forms List definition we can get ListItem object for given Form, by digging this list item we can get Form definition raw xml from _sys_def_xml system property then it should convert this raw xml to BaseForm instance; this is required input for Form Renderer.

Consolidated code snippet for Form Definition as follows,

/// 
    /// Get Form definition for given Skelta form
    /// 
    /// Form Definition object
    private BaseForm GetFormDefinitionXml()
    {
        try
        {
            bool isFlag = false;
            ListDefinition lstDef = ListDefinition.GetList(this.appObject, this.listParams.ListName);
            this.frmItem = new Skelta.Repository.List.ListItem(lstDef, this.listParams.ListItemId, this.listParams.VersionStamp);
            ListTableForm frmTable = (ListTableForm)this.frmItem.ListForm.FindById("_sys_fmdef_new");
            string strDef = Convert.ToString(((ListTextDataItem)frmTable.Records[0].FindControlByID("_sys_def_xml")).Value);
            if (string.IsNullOrEmpty(strDef))
            {
                //Show error message or thorw exception here if you required
                throw new Exception("Blank Form Definition");
                Response.End();
            }
            BaseForm frmBase = BaseForm.LoadDefinitionXml(strDef);
            frmBase.TopLevelForm.FormAssociatedItemID = this.frmItem.Id.ToString();
            return frmBase;
        }
        catch
        {
            return null;
        }
    }
Form Rendering:

Now we are ready for rendering the workflow initiator form on a web form, so we can use following code snippet to render it.

Skelta.Repository.List.ListItem item;
            try
            {
                item = new Skelta.Repository.List.ListItem(this.lstDef, fidForm);
            }
            catch
            {
                //Handle exception here
                return;
            }
            if (!item.IsFolder)
            {
                this.listParams.ListItemId = fidForm;
                this.listParams.VersionStamp = item.CurrentVersion.VersionStamp;
                this.listParams.ListName = this.lstDef.Name;
                this.frmRequest = new Skelta.Forms.Web.FormRenderer();
                frmRequest.ID = "frmRequest";
                frmRequest.Width = Unit.Percentage(100);
                frmRequest.Height = Unit.Percentage(100);
                this.frmRequest.FormDefinition = this.GetFormDefinitionXml();
                this.Title = this.frmRequest.FormDefinition.Name;
                frmRequest.FormDefinition.AssociatedListName = "Forms List";
                if (!base.IsPostBack)
                    this.frmRequest.FormDefinition.FormInstanceXml = "";
                this.frmRequest.InDesignMode = false;
                this.frmRequest.HeaderHidden = true;
                this.pnlForm.Visible = true;
                this.pnlForm.Controls.Add(this.frmRequest);
                this.frmRequest.OnSaveClick += new Skelta.Forms.Web.SaveClickDelegate(frmRequest_OnSaveClick);
            }

Create Form Renderer control instance and assign the Form Definition which we already generated in previous step. Set design mode false then add it to panel.

Find the method for Form rendering with full signature,

/// 
    /// Render the Skelta Form
    /// 
    /// Form Id</param>
    private void RenderForm(Guid fidForm)
    {
        try
        {
            Skelta.Repository.List.ListItem item;
            try
            {
                item = new Skelta.Repository.List.ListItem(this.lstDef, fidForm);
            }
            catch
            {
                //Handle exception here
                return;
            }
            if (!item.IsFolder)
            {
                this.listParams.ListItemId = fidForm;
                this.listParams.VersionStamp = item.CurrentVersion.VersionStamp;
                this.listParams.ListName = this.lstDef.Name;
                this.frmRequest = new Skelta.Forms.Web.FormRenderer();
                frmRequest.ID = "frmRequest";
                frmRequest.Width = Unit.Percentage(100);
                frmRequest.Height = Unit.Percentage(100);


                this.frmRequest.FormDefinition = this.GetFormDefinitionXml();
                this.Title = this.frmRequest.FormDefinition.Name;
                frmRequest.FormDefinition.AssociatedListName = "Forms List";
                if (!base.IsPostBack)
                    this.frmRequest.FormDefinition.FormInstanceXml = "";
                this.frmRequest.InDesignMode = false;
                this.frmRequest.HeaderHidden = true;
                this.pnlForm.Visible = true;
                this.pnlForm.Controls.Add(this.frmRequest);
                this.frmRequest.OnSaveClick += new Skelta.Forms.Web.SaveClickDelegate(frmRequest_OnSaveClick);
            }

        }
        catch (Exception errMsg)
        {
            //Handle exception here
        }
    }
Initiating Workflow

Form Renderer providing two event delegates and those are Submit and Cancel, Submit event will get trigger when user clicks on submit button of the rendered form, same way Cancel event will get trigger when user clicks on cancel button. According to your requirements you can choose any one of these events or both. In our current scenario we can use Submit event.

Before initiating the workflow, we need to do some prerequisites steps. First we need to create a instance for Skelta Workflow Client with Application Name and Associated Workflow Name, in next step we have to update the value for required workflow variables, this step is optional for our custom workflow variables but we must supply value to SKEventData system variable, this variable contains Event Name, Event Type, User Id, User Name, Form Id and Form Name. Workflow Client supports for execute workflow with or without return value from workflow engine, normally client’s Execute method will return the instance id of initiated workflow. We have another method called ExecuteBlocking to get custom output from workflow engine, but it’s depending upon Skelta activities. Please refer the Skelta Developer guide for more information about Client.

Code snippet for initiating the workflow process.

/// 
    /// Handle the Form Submit event
    /// 
    /// event source object</param>
    /// event argument</param>
    void frmRequest_OnSaveClick(object sender, Skelta.Forms.Web.SaveClickEventArgs e)
    {
        int executionId = 0;
        try
        {
            Skelta.HWS.Actor actor;
            Workflow.NET.Engine.Client client;
            if (string.IsNullOrEmpty(this.wfName) && !string.IsNullOrEmpty(this.listParams.ApplicationName))
            {
                Skelta.Repository.List.ListItem item = new Skelta.Repository.List.ListItem(ListDefinition.GetList(this.appObject, "Forms List"), this.listParams.ListItemId, this.listParams.VersionStamp);
                item.LoggedInUserId = new UserContext().LoggedInUserId;
                ListMainForm form = (ListMainForm)item.ListForm.Records[0].FindControlByID("_sys_forms_form");
                ListTableForm form2 = (ListTableForm)form.FindControlByID("_sys_fmdef_new");
                this.wfName = ((ListTextDataItem)form2.Records[0].FindControlByID("_sys_workflow_assn")).Value;
                this.wfVer = ((ListTextDataItem)form2.Records[0].FindControlByID("_sys_formdef_filename")).Value;
            }
            client = new Workflow.NET.Engine.Client(this.appObject.ApplicationName, this.wfName);
            VariablesCollection variables = new VariablesCollection();
            string formInstanceXml = this.frmRequest.FormDefinition.FormInstanceXml;
            variables.Add("SFFormData", "string", formInstanceXml);
            
            //If you want to send the full details about requester/current actor to workflow XML Variable
            //Its use full when user profile properties are more than what you specified in the current repository user provider entity
            //If you use this then no you need to create system variables for each activity to get their actor and you can easily refer all profile entities(User Id,User Name,First Name,Last Name, etc.) in workflow.
            //To use this option, you need create a Xml variable in your workflow with required profile schema(User Id,User Name,etc.,) then create raw xml for current user profile and uncomment the following comment then modify it
            //variables.Add("CurrentActor", "string", );

            actor = new Skelta.HWS.Actor(this.appObject, usrContext.LoggedInVirtualActorId);
            Skelta.Events.SKEventData data = new Skelta.Events.SKEventData();
            data.EventType = "OnInitiateForm";
            data.EventProvider = "Form";
            data.UserId = usrContext.LoggedInUserId.Split(new string[] { "::" }, StringSplitOptions.None)[1];
            data.UserName = actor.Resource.Properties.Name.Value.ToString();
            data.EventProperties.Add(new SKEventData.Property("FormId", this.frmRequest.FormDefinition.FormAssociatedItemID));
            data.EventProperties.Add(new SKEventData.Property("FormName", this.frmRequest.FormDefinition.Name.ToString()));
            if (!string.IsNullOrEmpty(this.versionStamp))
            {
                data.EventProperties.Add(new SKEventData.Property("FormVersion", this.versionStamp));
            }
            else
            {
                data.EventProperties.Add(new Skelta.Events.SKEventData.Property("FormVersion", this.listParams.VersionStamp));
            }
            variables.Add("SKEventData", "string", data.Serialize());
            try
            {
                if (this.frmItem != null)
                {
                    ListTableForm form3 = (ListTableForm)this.frmItem.ListForm.FindById("_sys_fmdef_new");
                    if (BaseForm.LoadDefinitionXml(((ListTextDataItem)form3.Records[0].FindControlByID("_sys_def_xml")).Value.ToString()).IsSynchronized)
                    {
                        string str3;
                        SynchronousActionData syncData = new SynchronousActionData(false, this.Context, true);
                        executionId = client.ExecuteBlocking(this.wfVer, usrContext.LoggedInUserId, formInstanceXml, variables, syncData, true, out str3);
                    }
                    else
                    {
                        executionId = client.Execute(this.wfVer, usrContext.LoggedInUserId, formInstanceXml, variables);
                    }
                }
                else
                {
                    string str4 = string.Empty;
                    string key = string.Empty;
                    Skelta.Repository.List.ListItemCollection items = new Skelta.Repository.List.ListItemCollection(ListDefinition.GetList(this.appObject, "Forms List"));
                    DataTable recordsWithAllFields = items.GetRecordsWithAllFields("", "");
                    if (recordsWithAllFields.Rows.Count > 0)
                    {
                        foreach (KeyValuePair pair in items.GetRecordsFullQueryFieldMapping())
                        {
                            if (pair.Value.Name == "Definition")
                            {
                                key = pair.Key;
                                break;
                            }
                        }
                        foreach (DataRow row in recordsWithAllFields.Rows)
                        {
                            if (row["f00100_1"].ToString() == this.frmRequest.FormDefinition.Name.ToString())
                            {
                                str4 = row[key].ToString();
                            }
                        }
                    }
                    if (string.IsNullOrEmpty(str4))
                    {

                    }
                    if (BaseForm.LoadDefinitionXml(str4).IsSynchronized)
                    {
                        string str6;
                        SynchronousActionData data3 = new SynchronousActionData(false, this.Context, true);
                        executionId = client.ExecuteBlocking(this.wfVer, usrContext.LoggedInUserId, formInstanceXml, variables, data3, true, out str6);
                    }
                    else
                    {
                        executionId = client.Execute(this.wfVer, usrContext.LoggedInUserId, formInstanceXml, variables);
                    }
                }
                e.ResponseScripts.Add("alert('Form Submitted Successfully.');");
                
            }
            catch (Exception exception)
            {
                //Handle exception here
            }
            finally
            {
                if (client != null)
                {
                    client.Close();
                }
                client = null;
            }
        }
        catch (Exception errMsg)
        {
            //Handle exception here
        }
    }

Recently I created the Starter Kit for Skelta and that is called as Smart Skelta Starter Kit for 2008, you can download it from http://smartskelta.codeplex.com. This kit contains Workflow Initiator form with all required code snippet whatever we discussed so far and you can able to find it from Item Templates for Website.

Conclusion:

So far we learned how to render a workflow initiator form and initiating the associated workflow on our custom application. I hope this article given you some idea about custom workflow initiator web form. Send your valuable feedbacks to eshock.vasan@gmail.com.

Read more


Rendering a Skelta Action Form

In Skelta BPM.Net 2009 they introduced dynamic form, we can design our required dynamic forms and attach with workflow in form of Invoke Form activity, it is very nice feature from Skelta but it has own fence. Here we have two types of forms one is workflow initiator form and another one is normal action forms, even we have two types based on whether the form created on Skelta List(Form mapper to table) or not.

This section explains about how to render an action form (custom ASPX web form for Invoke Form Activity) on an ASPX web form and submit the work item. In my previous article I explained about how to render a workflow initiator form, refer it if you already not.

Let’s Get Started

In order to achieve our goal we need to do following process steps,

  1. Get metadata information for Action Form
  2. Get Form Input variable value
  3. Get the Form Definition
  4. Render the Form Renderer
  5. Submit the work item
Metadata for Form:

Normally action forms are triggered from Skelta work list with a secured parameter, this parameter contains all required metadata about current work item, and form but this parameter encrypted to share it across pages, so we have to decrypt params before using it, here Skelta provides a adapter class to help us on this process, the adapter class is WebWorkItemAdapter.

Code snippet for processing secured parameter.

WebWorkItemAdapter webAdapter = new WebWorkItemAdapter();
webAdapter.ProcessWebChannelRequest();

Now we decrypted and initialized the WebWorkItemAdapter object from that we can get the instance for current work item and form name other details, the following code snippet help us to get the work item and form.

this.workitem = webAdapter.CurrentWorkItem;
//Get the Form Name with FormID from current invoke form activity
this.strName = this.workitem.CurrentActivity.Properties["FormDetails"].Value.ToString();
//Modify this code according your user provider
//Initilaize the user context object with desired user provider
Skelta.Entity.UserContext usrContext = new Skelta.Entity.UserContext(intUserId.ToString(),new ApplicationObject(this.webAdapter.ApplicationName), "sqlprovider", "", "", false);
//parse the form details 
string[] strArray = this.strName.Split(new char[] { '?' });
//get the from name
this.FormName = strArray[0];
//Get the id of form
this.id = new Guid(strArray[2].ToString());
Get Input Instance XML Value:

Action Form not like Workflow Initiator Form because action form can have the initial state and we must fill the required values before rendering it but in case of workflow initiator not required, but some cases it requires. Before filling values into form we need to get input xml variable name and its value, we can use following code snippet for getting input xml variable name and value.

this.inputvalue = this.workitem.CurrentActivity.Properties["InputInstanceXML"].Value.ToString();
this.inputxml = this.workitem.CurrentContext.XmlVariables[this.inputvalue].RawXml.ToString();

Once we got the value for input xml variable we can assign it to form renderer, refer the following code to assign value to xml variable.

this.fmrViewForm.FormDefinition.FormInstanceXml = this.inputxml;
Form Definition:

Before rendering the given form on Form Renderer we need to get form definition object otherwise form render can’t render the given form. We can get form definition object by using below code snippet.

ListDefinition lstDef = ListDefinition.GetList(this.appObject, this.listParams.ListName);
this.frmItem = new Skelta.Repository.List.ListItem(lstDef, this.listParams.ListItemId, this.listParams.VersionStamp);
ListTableForm frmTable = (ListTableForm)this.frmItem.ListForm.FindById("_sys_fmdef_new");
string strDef = Convert.ToString(((ListTextDataItem)frmTable.Records[0].FindControlByID("_sys_def_xml")).Value);
if (string.IsNullOrEmpty(strDef))
{
    //Show error message or thorw exception here if you required
    throw new Exception("Blank Form Definition");
    Response.End();
}
BaseForm frmBase = BaseForm.LoadDefinitionXml(strDef);
frmBase.TopLevelForm.FormAssociatedItemID = this.frmItem.Id.ToString();
return frmBase;

First we need to get the Skelta List definition for Forms List (Skelta is a generic master list to access all created Skelta elements for a repository each Skelta elements have their own list definition based on this definition Skelta List will display the master list). Based on this Forms List definition we can get ListItem object for given Form, by digging this list item we can get Form definition raw xml from _sys_def_xml system property. Finally convert this raw xml to BaseForm instance; this is Skelta API representation for Form Renderer.
Consolidated code snippet for Form Definition as follows,

///

    /// Get Form definition for given Skelta form
    /// 
    ///
Form Definition object
    private BaseForm GetFormDefinitionXml()
    {
        try
        {
            bool isFlag = false;
            ListDefinition lstDef = ListDefinition.GetList(this.appObject, this.listParams.ListName);
            this.frmItem = new Skelta.Repository.List.ListItem(lstDef, this.listParams.ListItemId, this.listParams.VersionStamp);
            ListTableForm frmTable = (ListTableForm)this.frmItem.ListForm.FindById("_sys_fmdef_new");
            string strDef = Convert.ToString(((ListTextDataItem)frmTable.Records[0].FindControlByID("_sys_def_xml")).Value);
            if (string.IsNullOrEmpty(strDef))
            {
                //Show error message or thorw exception here if you required
                throw new Exception("Blank Form Definition");
                Response.End();
            }
            BaseForm frmBase = BaseForm.LoadDefinitionXml(strDef);
            frmBase.TopLevelForm.FormAssociatedItemID = this.frmItem.Id.ToString();
            return frmBase;
        }
        catch
        {
            return null;
        }
    }
Form Rendering:

Now we are ready for rendering action form on web form but before rendering from we need to update the form parameters like execution id, execution details id, current context and current activity, we can update these information with following code snippet.

this.UpdateFormParameters(this.adapter.CurrentWorkItem.ExecutionId, this.adapter.CurrentWorkItem.ExecutionDetailsId, this.adapter.CurrentWorkItem.CurrentContext, this.adapter.CurrentWorkItem.CurrentActivity);
        

Refer the following snippet to render the action form on a web form.

///

    /// Handle Page Initialize event
    /// 
    ///
event argument</param>
    protected override void OnInit(EventArgs e)
    {
        try
        {
            try
            {
                if (Session["UserInfo"] != null)
                    usrInfo = Session["UserInfo"] as DotNetNuke.Entities.Users.UserInfo;
                //System.Diagnostics.Debugger.Break();
                //usrContext = new Skelta.Entity.UserContext(usrInfo.UserID.ToString(), new Skelta.Core.ApplicationObject(ConfigurationSettings.AppSettings["KIBApplicationName"]), "sqlprovider", "", "", false);
                this.adapter.ProcessWebChannelRequest();
                this.adapter.CurrentWorkItem.GetFormInstanceData(this.adapter.ChannelName, this.adapter.FormName, out this.formuri, out this.formstate);
                this.listParams.ProcessWebChannelRequest();
                this.workitem = this.listParams.CurrentWorkItem;
                this.strname = this.workitem.CurrentActivity.Properties["FormDetails"].Value.ToString();
                Skelta.Entity.UserContext usrContext = new Skelta.Entity.UserContext(usrInfo.UserID.ToString(),new ApplicationObject(this.adapter.ApplicationName), "sqlprovider", "", "", false);
                if (!string.IsNullOrEmpty(this.workitem.Subject))
                {
                    this.strsubject = this.workitem.Subject.ToString();
                }
                string[] strArray = this.strname.Split(new char[] { '?' });
                this.FormName = strArray[0];
                this.strFormName = this.FormName;
                this.version = strArray[1];
                this.strid = strArray[2].ToString();
                this.id = new Guid(strArray[2].ToString());
                this.actionname = strArray[3].ToString();
                this.inputvalue = this.workitem.CurrentActivity.Properties["InputInstanceXML"].Value.ToString();
                this.outputvalue = this.workitem.CurrentActivity.Properties["OutputInstanceXML"].Value.ToString();
                this.fmrViewForm = new FormRenderer();
                if (this.formstate == "")
                {
                    if (this.inputvalue != "")
                    {
                        this.inputxml = this.workitem.CurrentContext.XmlVariables[this.inputvalue].RawXml.ToString();
                    }
                    if (this.outputvalue != "")
                    {
                        this.outputxml = this.workitem.CurrentContext.XmlVariables[this.outputvalue].RawXml.ToString();
                    }
                }
                this.IsSaveAsDraft = this.workitem.CurrentActivity.Properties["SaveAsDraft"].Value.ToString();
            }
            catch (Exception exception)
            {
                //this.workitem.CurrentContext.log.LogError(exception, "Error while getting properties", !string.IsNullOrEmpty(this.adapter.ApplicationName) ? this.adapter.ApplicationName : "");
            }
            if (!string.IsNullOrEmpty(this.strname) || (this.formstate != ""))
            {
                if (this.IsSaveAsDraft.ToLowerInvariant() == "yes")
                {
                    this.fmrViewForm.ShowSaveAsDraftLink = true;
                }
                this.fmrViewForm.FormDefinition = this.GetFormDefinitionXml();
                if (!this.iFormNotFound)
                {
                    this.fmrViewForm.FormDefinition.FormAssociatedItemID = this.id.ToString();
                    if (!base.IsPostBack)
                    {
                        if ((this.inputxml == "") || (this.outputxml == ""))
                        {
                            if (this.formstate != "")
                            {
                                this.fmrViewForm.FormDefinition.FormInstanceXml = this.formstate;
                            }
                            else if (this.inputxml != "")
                            {
                                this.fmrViewForm.FormDefinition.FormInstanceXml = this.inputxml;
                            }
                            else if (this.GetFormInstanceXml() != null)
                            {
                                this.fmrViewForm.FormDefinition.FormInstanceXml = this.GetFormInstanceXml();
                            }
                        }
                        else if (this.outputxml == "
")
                        {
                            this.fmrViewForm.FormDefinition.FormInstanceXml = this.inputxml;
                        }
                        else if (this.inputxml != "")
                        {
                            this.fmrViewForm.FormDefinition.FormInstanceXml = this.inputxml;
                        }
                        else
                        {
                            this.fmrViewForm.FormDefinition.FormInstanceXml = this.outputxml;
                        }
                        if ((this.inputvalue == "") && (this.formstate == ""))
                        {
                            this.fmrViewForm.FormDefinition.FormInstanceXml = "";
                        }
                    }
                    try
                    {
                        this.UpdateFormParameters(this.adapter.CurrentWorkItem.ExecutionId, this.adapter.CurrentWorkItem.ExecutionDetailsId, this.adapter.CurrentWorkItem.CurrentContext, this.adapter.CurrentWorkItem.CurrentActivity);
                    }
                    catch
                    {
                    }
                    //this.lblFormRender.Visible = false;
                    this.fmrViewForm.HeaderHidden = true;
                    this.fmrViewForm.InDesignMode = false;
                    this.fmrViewForm.OnSaveClick += new SaveClickDelegate(this.fmrViewForm_OnSaveClick);
                    this.pnlFormRender.Controls.Add(fmrViewForm);
                }
            }
        }
        catch { }
    }

So far we filled the value to form and render the form on given web form, now it is waiting for user inputs.

Set Output Variable Value:

When user clicks on submit button we need to transfer the form data to workflow, normally here we will pass the current from state. Incase if you want to submit some extra values than from instance xml you can it. Refer the following code snippet for form submission.

///

    /// Handle the Form Submit event
    /// 
    ///
event publisher</param>
    ///
event argument</param>
    protected void fmrViewForm_OnSaveClick(object sender, SaveClickEventArgs e)
    {
        //Its optional, if you are using CurrentActor xml variable then you uncomment following lines of code
        //try
        //{
        //    XmlVariable xmlCurrentActor = this.workitem.CurrentContext.XmlVariables["CurrentActor"];
        //    if (xmlCurrentActor != null)
        //    {
        //        xmlCurrentActor.RawXml = usrInfo.GetSkeltaUserContextXml();
        //        xmlCurrentActor.Save();
        //    }
        //}
        //catch { }
       
        this.webAdapter.ProcessWebChannelRequest();
        string channelName = this.webAdapter.ChannelName;
        string formName = this.webAdapter.FormName;
        if (e.IsFromSaveAsDraftLink)
        {
            this.webAdapter.CurrentWorkItem.SetStateForFormInstance(channelName, formName, this.fmrViewForm.FormDefinition.FormInstanceXml);
            e.ResponseScripts.Add("alert('" + this.sklResMgr.GlobalResourceSet.GetString("FormSaveDraftContentAlert") + "');");
            if (this.webAdapter.CurrentWorkItem.CurrentActivity.Properties.Contains("CloseOnSaveAsDraft") && (this.webAdapter.CurrentWorkItem.CurrentActivity.Properties["CloseOnSaveAsDraft"].Value.ToString().ToLowerInvariant() == "yes"))
            {
                e.ResponseScripts.Add("window.parent.close();");
            }
        }
        else
        {
            try
            {
                bool flag = false;
                if (this.webAdapter.CurrentWorkItem.Status != "CO")
                {
                    string currentStatus = "";
                    if (this.webAdapter.CurrentWorkItem.Actor == null)
                    {
                        Actor actor = new Actor(new ApplicationObject(this.webAdapter.CurrentWorkItem.Application.ApplicationName), this.webAdapter.LoggedInActor.Id);
                        WorkItem item = this.webAdapter.CurrentWorkItem.PickItem(actor);
                        currentStatus = e.ButtonText;
                        string formInstanceXml = this.fmrViewForm.FormDefinition.FormInstanceXml;
                        string comments = "Acted on " + currentStatus + " button.";
                        SynchronousActionData syncData = new SynchronousActionData(false, this.Context, true);
                        item.Submit(this.webAdapter.ChannelName, currentStatus, "", comments, syncData, ref formInstanceXml);
                        item.SetStateForFormInstance(channelName, formName, formInstanceXml);
                        this.workitem = item;
                    }
                    else
                    {
                        currentStatus = e.ButtonText;
                        string formInstanceStateXml = this.fmrViewForm.FormDefinition.FormInstanceXml;
                        string str7 = "Acted on " + currentStatus + " button.";
                        SynchronousActionData data2 = new SynchronousActionData(false, this.Context, true);
                        this.webAdapter.CurrentWorkItem.Submit(this.webAdapter.ChannelName, currentStatus, "", str7, data2, ref formInstanceStateXml);
                        this.webAdapter.CurrentWorkItem.SetStateForFormInstance(channelName, formName, formInstanceStateXml);
                    }
                }
                else
                {
                    e.ResponseScripts.Add("alert('" + this.sklResMgr.GlobalResourceSet.GetString("FormAlreadySavedAlert") + "');");
                    flag = true;
                }
                if (this.fmrViewForm.FormDefinition.FindControlByName("hidWinParent") != null)
                {
                    e.ResponseScripts.Add("if( window.parent.opener.external != null ) window.parent.opener.external.CallParentEvent('" + e.ButtonText + "', '');");
                }
                if ((base.Request["type"] != null) && (base.Request["type"].ToString().ToLowerInvariant().Trim() == "mail"))
                {
                    if (!flag)
                    {
                        if (this.webAdapter.CurrentWorkItem.CurrentActivity.Properties.Contains("CloseOnFormSubmit"))
                        {
                            if (this.webAdapter.CurrentWorkItem.CurrentActivity.Properties["CloseOnFormSubmit"].Value.ToString().ToLowerInvariant() == "yes")
                            {
                                e.ResponseScripts.Add("window.parent.close();");
                            }
                            else
                            {
                                e.ResponseScripts.Add("alert('" + this.sklResMgr.GlobalResourceSet.GetString("FormSavedAlert") + "');");
                            }
                        }
                        else
                        {
                            e.ResponseScripts.Add("alert('" + this.sklResMgr.GlobalResourceSet.GetString("FormSavedAlert") + "');");
                        }
                        
                    }
                }
                else
                {
                    if (!flag)
                    {
                        e.ResponseScripts.Add("alert('Form submitted successfully.');");
                    }
                    e.ResponseScripts.Add("CloseInvokeFormWorkItemWindow()");
                }
            }
            catch (Exception exception)
            {
                Log logger = new Log();

                logger.LogError(exception, "Error", !string.IsNullOrEmpty(this.listParams.ApplicationName) ? this.listParams.ApplicationName : "");
                if (exception.Message.ToLowerInvariant().Trim() == "could not connect to the workflow engine. make sure that the workflow engine is running.")
                {
                    this.webAdapter.CurrentWorkItem.SetStateForFormInstance(channelName, formName, this.fmrViewForm.FormDefinition.FormInstanceXml);
                    e.ResponseScripts.Add("alert('" + this.sklResMgr.GlobalResourceSet.GetString("Engine_StoppedWithSaveAsDraft") + "');");
                }
                else
                {
                    e.ResponseScripts.Add("alert('" + exception.Message + "');");
                }
                logger.Close();
                logger.Dispose();
            }
            (sender as FormRenderer).SaveCurrentFormAttachmentsWithDetails(this.workitem.ExecutionId, this.workitem.CurrentContext.WorkflowName, this.workitem.HWSActivity.Id, null);
        }
       
    }
    

Here we have two types submission one is normal ,it is simply submit the work item another one is it will submit the work item and it will retrieve some values form engine. You can use either one. Recently I created the Starter Kit for Skelta called as Smart Skelta Starter Kit for 2008, you can download it from http://smartskelta.codeplex.com. In this kit you can able to find out Action form from Item templates of Web Site.

Conclusion:

So far we learned how to render a action form and initiating the associated workflow. I hope this article given some idea about custom action form web form. Send your valuable feedbacks to eshock.vasan@gmail.com.

Read more


How to organize your custom templates in VS 2008

Visual Studio providing a feature called Custom Template to create our own project templates(Web,Windows etc,.) and Item templates(Web Form,Class file etc,.). These Templates are used to add/deploy all prerequisities to specified project.
Organizing your custom templates are very easy in VS,we can create our own custome folder with hierarchy for projects and items. Refer the following steps to do organize your custom templates.

  1. Create your sample template contents and sample.vstemplate file
  2. Create sample.vscontent file and add your custom folder name in ProjectSubType attribute node to organize your template.Refer the following Sample.vscontent file
    Ex:
    <?xml version="1.0" encoding="utf-8"?>
    <VSContent xmlns="http://schemas.microsoft.com/developer/vscontent/2005">
    <Content>
    <FileName>SampleTemplate.zip</FileName>
    <DisplayName>Sample Test Template</DisplayName>
    <Description>Sample Template</Description>
    <FileContentType>VSTemplate</FileContentType>
    <ContentVersion>1.0</ContentVersion>
    <Attributes>
    <Attribute name="ProjectType" value="Visual C#" />
    <Attribute name="ProjectSubType" value="Sample"/>
    <Attribute name="TemplateType" value="Project"/>
    </Attributes>
    </Content>
    </VSContent>
    
    If you want add sub folder then mention same thing in ProjectSubType like Parent\child1\child2 etc. Refer the following Sample.vstemplate file
    <?xml version="1.0" encoding="utf-8"?>
    <VSContent xmlns="http://schemas.microsoft.com/developer/vscontent/2005">
    <Content>
    <FileName>SampleTemplate.zip</FileName>
    <DisplayName>Sample Test Template</DisplayName>
    <Description>Sample Template</Description>
    <FileContentType>VSTemplate</FileContentType>
    <ContentVersion>1.0</ContentVersion>
    <Attributes>
    <Attribute name="ProjectType" value="Visual C#" />
    <Attribute name="ProjectSubType" value="Sample\Child1"/>
    <Attribute name="TemplateType" value="Project"/>
    </Attributes>
    </Content>
    </VSContent>
    
  3. Install your Sample.vscontent file


  4. Open a Visual studio,go to New -> Project,now you can able to your folder hierarchy just like you mentioned in the vscontent file.


Build Action:

A Visual Studio Porject can contains different types of files like code files,normal resource files,embedded resource files, help files etc,each type will have different build action on VS, based on this VS will do the required action on these file i.e code can be compiled and added to assembly, embedded resource file will add to assembly without compile, no action for help files etc. In templates we can set the VS build action for a project item while we creating it. Follow the following steps to apply build action on your project item,

  1. Open your Sample.vstemplate file.
  2. Add ItemType attribute to ProjectItem tag with required Build action.

    Ex:
    
    <ProjectItem ItemType="None" ReplaceParameters="true" TargetFileName="Sample.cs">Sample.cs</ProjectItem>
    
  3. Install your current tempalte.
  4. Open/add project in Visual Studio then add a new item which you created just now and check the build action from the properties of created item.
If you feel free send your feedbacks to eshock.vasan@gmail.com

Read more


Displaying Dynamic Process Templates using Skelta

In this article I am going to explain how to dynamically display all available process templates (Skelta forms which associated with a workflow) to a user on a common page based on skelta list level user access rights.In Skelta there is no component or documentation to list out the process templates,I don't why they haven't given it even though they have API.

When I start working on this requirement really I don't know how to do it, so I communicated with Skelta support to get the solution then they given me a solution with hard coding Skelta form id but this is not exact solution what we want because it had two drawbacks, the first thing is in feature,if Skelta administrator create any new process template then he/she has to hard code the form id with templates showing page but it should not be and next things is we can't use build in Skelta List level security rights feature here otherwise we need a separate module to maintain the security rights externally for all process templates.

After scratching my head with Skelta APIs for a long time I found a solution and it fulfilled all our requirements.Now I ready to share my solution with you all.

Background
Skelta Repository is base container for a Skelta application and each repository contains own set of process flows, forms, lookup lists etc., we can create N no. of repositories on Skelta and these repositories are associated with one user provider (user resource/pool), currently Skelta supporting for four types of user providers those are Forms, Asp.Net user provider, Active Directory and Skelta user provider, in this articles we are using form provider. Skelta has List API to list out all Skelta repository elements like Workflows, Forms and Queues etc., and these lists has own unique list definition for populating the repository elements and Skelta List API will differentiate the repository elements by using this definition.

Since Skelta is using common list API for all repository elements, the base table will have common fields to store all lists’ attributes (name, description and element specific properties etc.).We can get all lists’ attributes by mapping attributes field with base table based on list definition. All Skelta List API build with Skelta security rights API so it can automatically take care about user rights. User security rights can be specified by Skelta repository administrator for a element list while creating it based on these settings Skelta list API displays all available items to specific user against his/her permission.

In Skelta we have staging concept for workflows and forms, those are development stage (draft) and production stage (publish), if you want render a form or create a workflow instance then the specific item should be published then only it will move to production stage to use. Skelta forms and workflow lists are supporting for folder to organize the sub items.

Let’s Get Started
ListItemCollection is main class for access the repository list items; this object required the ListDefinition object and ListDefinition required application object and list name while creating it. GetRecordsFullQueryFieldMapping method of ListItemCollection can list out the mapping fields for given list name, using these fields we can access list specific properties. GetRecordsForConsume can list out all available list items in Dataset and it required four input parameters, those are filter column, filter value, Display Items and user id. We can get a ListItem object with ListDefinition object and List Item id; with this object we can access item level properties.

Following class diagram shows the overview of class structure for listing out the process templates.



Figure: Class Diagram for Process Templates Enumerator


Here ListDefinition class inheriting from SingleTableObjectWithName and this class inherited from SingleTableObject. SingleTableObject is a table entity class and it can associate with a skelta table so we can do query with this object. ProcessTemplateItem is a custom class and it contains some basic information like Application, Associated Workflow, Description, ID and Title.
Note: Above class diagram shows only required properties and methods of ListDefinition, SingleTableObjectWithName, ListItemCollection and ListItem.

Process Templates Class:

using System;
using Skelta.Repository.List;
using Skelta.Core;
using Workflow.NET;
using Skelta.Entity;
using Skelta.Repository.Web.List;
using System.Collections.Generic;
namespace Skleta.ProcessTemplate
{
    public class ProcessTemplates
    {
        public ProcessTemplates()
        {
        }
        /// 
        /// List out all available process templates for given user in a skelta repository
        /// 
        /// Skelta repository name        /// user id        /// Collection of process templates
        public List GetProcessTemplates(string strApplicationName, int intUserId)
        {
            try
            {
                string colDescription = "";
                string colBoundToWorkFlow = "";
                string colTitle = "";
                ApplicationObject appObject = new ApplicationObject(strApplicationName);
                ListDefinition lstDef = ListDefinition.GetList(appObject, "Forms List");
                Skelta.Repository.List.ListItemCollection lstItmCollection = new Skelta.Repository.List.ListItemCollection(lstDef);
                foreach (KeyValuePair pairFields in lstItmCollection.GetRecordsFullQueryFieldMapping())
                {
                    if (pairFields.Value.Name == "Title")
                    {
                        colTitle = pairFields.Key.ToString();
                    }
                    else if (pairFields.Value.Name == "Description")
                    {
                        colDescription = pairFields.Key.ToString();
                    }
                    else if (pairFields.Value.Name == "BoundToWorkflow")
                    {
                        colBoundToWorkFlow = pairFields.Key.ToString();
                    }
                }
                try
                {
                    //Status = 3 published items
  //Status = 1 draft items
//Get all published items from form list table
                    DataTable dtForms = lstItmCollection.GetRecordsForConsume("Status", "3", Skelta.Repository.Display.AllItems, string.Format("sqlprovider::{0}", intUserId));
                    dtForms.DefaultView.Sort = colTitle;
                    List lstFroms = new List();
                    foreach (DataRow frmRow in dtForms.DefaultView.ToTable().Rows)
                    {
                        Skelta.Repository.List.ListItem itemList = new Skelta.Repository.List.ListItem(lstDef, new Guid(frmRow["Id"].ToString()));
                        ListMainForm frmMain = (ListMainForm)itemList.ListForm.Records[0];
                        if (!itemList.IsFolder)
                        {
                            ListTableForm frmList = (ListTableForm)frmMain.FindById("_sys_fmdef_new");
                            if (string.IsNullOrEmpty(((ListTextDataItem)frmList.Records[0].FindControlByID("_sys_workflow_assn")).Value))
                                continue;
                        }
                        lstFroms.Add(new ProcessTemplateItem() { BoundToWorkflow = Convert.ToString(frmRow[colBoundToWorkFlow]), Description = HttpUtility.HtmlEncode(frmRow[colDescription].ToString()), ID = Convert.ToString(frmRow["Id"]), Title = Convert.ToString(frmRow[colTitle]), ApplicationName = strApplicationName });
                    }
                    return lstFroms;
                }
                catch { }
                return null;
            }
            catch (Exception)
            {
                throw;
            }
        }
    }

ProcessTemplateItem Class:

    public class ProcessTemplateItem
    {
        public string Title { get; set; }
        public string BoundToWorkflow { get; set; }
        public string ID { get; set; }
        public string Description { get; set; }
        public string ApplicationName { get; set; }
        public override bool Equals(object obj)
        {
            ProcessTemplateItem tempItem = obj as ProcessTemplateItem;
            if (tempItem == null)
                return false;
            return tempItem.BoundToWorkflow.Equals(this.BoundToWorkflow) && tempItem.ID.Equals(this.ID);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public override string ToString()
        {
            return this.Title;
        }
    }
}


Above code snippets shows the actual implementation of Process Template Enumerator, here GetProcessTemplates list out all available process templates for given user, and it takes two input parameters, one is application name and another one is user id (forms provider). Same way you can customize this for other user providers.

Before implementing this in UI,we required following prerequisites.
Workflow:
Follow the following steps create your required workflow.
  1. Login as administrator in Enterprise console or your custom application.
  2. Navigate to Worflow List


    Figure: Skelta Workflow List

  3. Click on Workflow Ribbon Button then it will popup a window as below.


    Figure: New Workflow Window

  4. Enter your Name and Description, here description is mandatory for
    workflow initiator form because we going to use while displaying Process
    Templates.
  5. Click on Save & Continue, then it will display following screen.


    Figure: New Workflow Action Form

  6. Click on Finish button then it will popup the Skelta process designer.


    Figure: Skelta Process Designer

  7. Now you design your process flow then close the process designer after
    publishing the designed workflow.

Skelta Form:
Follow the following steps to create Skelta form(s).
  1. Navigate to Skelta Form List.


    Figure: Skelta Form List

  2. Click on Form ribbon button ,it will pop up the following screen.


    Figure: New Form Creation Window

  3. Enter the form name and description here, and then click on save button
    then it shows the following screen.


    Figure: New Form Action Form

  4. Click on finish button, it will bring the form designer screen.


    Figure: Skelta Form Designer

  5. Here you design your form based on your requirements then publish it.

Workflow Initiator Form:
Do the following steps to associate the created workflow with created form.
  1. Navigate to Skelta Form List then right click on workflow initiator form then select Associate then it will show the following screen.


    Figure: Skelta Form List-Initiator Form


  2. Select Bind to an existing workflow then select your workflow from drop down list then click on Save button.


    Figure: Workflow Mapping Screen



Workflow Initiator Form(Process Template) User Access Rights:
Do the following steps to set user access rights for workflow initiator form.
  1. Navigate to Skelta Form List then select workflow initiator form.


    Figure: Skelta Form List

  2. Click on Security Ribbon button.
  3. Select Advanced Settings.


    Figure: Security Map Settings Screen

  4. Click on Enable List item Specific Security.


    Figure: Security Map Settings Screen

  5. Select Resource Mapping then click on Next, it will redirect to Security Group Mapping List.


    Figure: Security Map Settings Screen

  6. Click on New Ribbon button to map to new user(s) or role(s).


    Figure: Security Group Mapping

  7. Map screen will display as follows


    Figure: Roles/Users Map Screen

  8. Find role(s) or user(s) based on your requirement then select the contributor for security group drop down then click on Save button.
    • Click on Map Users tab to add user(s) then click on User Lookup Icon it will bring a new popup user lookup screen.


      Figure: User Lookup Screen

    • Select your appropriate filter then enter value for filter to search then click on Search button,now user lookup will list out all matched users based on your filter,select users who has the access to use this process template.Finally click on Update button.


      Figure: User Lookup Screen

    • Click on Save button then it will show the successful alert message.


      Figure: User Map Screen

    • Successful Message.


      Figure: Successful Message

    • Now you can see the item(s) which recently mapped on Security Group Mapping List.


      Figure: Security Group Mapping List

  9. Repeat the step 8 for adding more roles or users.

Once above actions are completed,then we can start UI implementation to displaying Process Templates. Add a new page or user control based on your requirement then add a dropdown list or Listbox or Tree view or Gird on selected container, finally bind the process template list with added data binder control. Here form id is mandatory so it is key/value field, text field may be Title or Description and other fields are optional.
The following code snippet shows the sample Process Template list binding with Telerik’s RadTreeview.
    private void BindProcessTemplate(string strAppName, int intUserId)
    {
        //Get the root node of Rad Treeview
        RadTreeNode nodCompose = this.radLeftMenu.Nodes[0];
        if (nodCompose != null)
        {
            //Create the reference for Process Template Enumerator
            ProcessTemplates prsTemplates = new ProcessTemplates();
            List lstForms = prsTemplates.GGetProcessTemplates(strAppName, intUserId);
            //Iterate thorugh Porcess template list
            foreach (ProcessTemplateItem lstForm in lstForms)
            {
                //Set all necessary attributes to current tree node
                Telerik.Web.UI.RadTreeNode nodRequest = new Telerik.Web.UI.RadTreeNode(lstForm.Title, lstForm.ID);
                nodRequest.LongDesc = lstForm.ApplicationName;
                nodRequest.ImageUrl = "images/request.png";
                nodRequest.ToolTip = lstForm.BoundToWorkflow;
                nodRequest.Expanded = false;
                nodRequest.PostBack = false;
                //Generate the URL Form render page and add to current node,here Form id and application are mandatory for rendering a form
                string strPath = this.Page.ResolveClientUrl(string.Format("AddRequest.aspx?FID={0}&Application={1}", nodRequest.Value.Replace("-", ""), lstForm.ApplicationName));
                nodRequest.NavigateUrl = string.Format("javascript:ShowProcessTemplate('{0}');", strPath);
                nodApplication.Nodes.Add(nodRequest);
            }
        }
    }

Same way you can use other controls like Grid,dropdownlist and according to your choice,you need change the above code.

The following screen shows the sample output of above implementation.


Figure: Process Templates

When user click on available process templates,the selected template form will render on the popup window then user can fill the form and submit to the skelta workflow engine to create a new instance.Refer my article for more information on rendering a Skelta form.

Conclusion:
Now we can able to list out all available process templates to a specific user with out hardcoding the required infromation i.e Form Id, Application Name. Send your feedback to eshock.vasan@gmail.com. Forgive me for my english and you always welcome to edit this article for grammetical and technicle mistakes.

Read more

Powered by Blogger.

About

About

Follow us

Popular Posts