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.


Powered by Blogger.

About

About

Follow us

Popular Posts