Search This Blog

Monday, November 11, 2013

Reading SharePoint web.config

Usage Scenarios:
 
Read configuration information like SQL connection string, custom application settings etc. from SharePoint web.config file
 
Here is the code snippet:
 
-----------------------------------------------
 
using System.Configuration;
using System.Web.Configuration;
...
...
 
string webAppName = SPContext.Current.Site.WebApplication.Name;  // use SPSite object hierarchy to get web application name.
 
Configuration config = WebConfigurationManager.OpenWebConfiguration("/", webAppName);
string dataFromAppSettings = config.AppSettings.Settings["Setting Name to read"].Value;

 
 
-------------------------------------------------------

That's all for this post. Until next time...

-- Mohan





Wednesday, October 30, 2013

Send customized email Alerts from SharePoint Workflow (2007 / 2010 / 2013)

The purpose of this blog post is to discuss a design which can send customized email alerts / reminders from a SharePoint workflow.

Skills Needed: 
C# and SharePoint custom workflows


Let's have a look at the different methods:

a) Use SPAlert
This works okay however the problem using SPAlert object is the customization of the message.
There are ways to modify/create custom messages by changing the XML files in 14 hive but i prefer not to change those.

b) Using Listen activity

We need a customized email message to be sent to the workflow users i.e. be it approvers or delegates etc. and here is a design which can provide that functionality in a custom SharePoint workflow.



The CreateTask activity (not shown in the image above) is above the While Activity i.e. while loop.

Legends for the Image:
1:   Listen Activity
2.  Delay Activity
3.  Code Activity


Nested inside the While loop is a Listen Activity (1)  with two branches as shown above. Both the branches must start with a Event Driven activity. Example: OnTaskChanged, DelayActivity etc.

The listen activity will wait for any of the branch to wake up and whichever wakes up first - completes the activity.

Hence in above case either the user completes the task (ontaskchanged1 fires) OR the time in delay activity is up (delayActivity1 completes) and hence the code activity fires up. The code activity above i.e. codeActivitySendReminder contains custom email code i.e. Email reminders for Task

Here is what the delay Activity handler looks like:

  private void delayActivity1_InitializeTimeoutDuration(object sender, EventArgs e)
        {
            DelayActivity dAct = sender as DelayActivity;
            dAct.TimeoutDuration = new TimeSpan(<Number of Days here>, 0, 0, 0);             

        }


Now let's say we want to send a reminder every 2 days  if the task has not completed,  then the above line would look like:
     dAct.TimeoutDuration = new TimeSpan(2, 0, 0, 0);       

Going further more:
The above code can be replaced with the days based on
actual Due date of the task in CreateTask activity.
i.e.
Example: 
TimeSpan ts = TaskObject.DueDate -  DateTime.Today (Calculate ts before the While loop above);
and hence use 

 ts.Days above i.e.
dAct.TimeoutDuration = new TimeSpan(ts.Days, 0, 0, 0);       


How to send custom emails is out of scope for this article since that is very common piece of code around and easily available on internet.


And that's all for this post.

Any questions / comments are welcome.

Please write back...

-- Mohan


( By the way I saw this on linkedin:
Question: Why do Java developers need eye glasses?
Answer:   Because they do not C# 
)

Saturday, June 15, 2013

Display workflow history in custom InfoPath task forms

If you have worked on custom InfoPath task forms with code behind, here is a ready made enhancement which users loved and hope your users will too..

What it does:
Retrieves the workflow history items associated with a workflow instance and shows them in a Grid in InfoPath form.

How is it useful:
For a given custom workflow in SharePoint - the InfoPath task form and associated workflow history are 2 different pages.  If you already have a custom task form in IP,  then using this method we can display workflow history so that every approver can see what has happened so far in the workflow (with latest on the top so that they can quickly see the task history prior to their approval), and this could help in making informed decision on task approval. Additionally the dates are shown similar to SharePoint 2013 format i.e. for example:
Today at 3:45 PM
Yesterday at 12:10 PM
Day before yesterday at 12:15 PM

Sample output:


Skills needed to implement this:
SharePoint workflows, InfoPath code behind knowledge

Can it be used on ASPX task forms?
Yes, it can be. Please change the yellow highlighted lines below which are specific to InfoPath

Our User Feedback:
They love it.

How to configure (please refer the image below):
a) Select main data source in InfoPath
b) Create a group element as shown below with name HistParent
c) Under HistParent group create an element with name "hs".  Data type is text and check on "repeating"
d) Remaining i.e. pos, un, oc, cm, dt are all text type and are "attributes" (not element)




e) Copy and paste the function below in code behind of your InfoPath task form.
f)  Call the method from form load passing in the WorkflowInstanceId and HistoryList Id
     How to pass in the above values:
      f1) Assuming you are using itemMetadata.xml for the task form - create 2 new fields for
            workflowInstanceId and historyListId
      f2) from the custom code workflow pass in the values like
   taskProperties.ExtendedProperties["workflowInstanceId"] = workflowproperties.WorkflowId.ToString("B") ; // Important to put B here since we need { } to be included in string
     
  taskProperties.ExtendedProperties["historyListId"] = workflowProperties.HistoryList.ID.ToString() ;


Source Code (included as is - no warranties)

        /// <summary>
        /// Function to display Workflowhistory for a given workflow instance in reverse chronological order
  /// </summary>
        /// <param name="workflowId">Contains the workflow instance guid with { } in string format</param>
        /// <param name="historyListId">GUID for history list without { } </param>
        public void GetWorkflowHistory(string workflowId, string historyListId, string siteURL, string webRelativeURL)
        {
            SPList historyList = null;
            // Open site collection
            using (SPSite site = new SPSite(siteURL))
            {
                // Open web
                using (SPWeb web = site.OpenWeb(webRelativeURL))
                {
                    try
                    {
                        // Check for existence of history list
                        historyList = web.Lists[new Guid(historyListId)];
                    }
                    catch // History list has been deleted or renamed
                    {
                        throw new ApplicationException("History List not found");
                    }
                    SPQuery qry = new SPQuery();
                    qry.ViewFieldsOnly = true; // Optimize performance
                    qry.ViewFields = "<FieldRef Name='User'/><FieldRef Name='Occurred'/><FieldRef Name='Outcome'/><FieldRef Name='Description'/>"; // Get specific fields only -  user name, date, task outcome and, task comments
                    // WorkflowInstance field contains the GUID of the workflow instance in history
                    qry.Query = string.Format("<Where><Eq><FieldRef Name=\"WorkflowInstance\"/><Value Type=\"Text\">{0}</Value></Eq></Where><OrderBy><FieldRef  Name=\"ID\" Ascending='False' /></OrderBy>", workflowId);
                    qry.DatesInUtc = false;
                    SPListItemCollection coll = historyList.GetItems(qry);
                    SPListItem item = null;
                    StringBuilder sb = new StringBuilder();
                    string userName, outcome, comments, sDT;
                    DateTime dt;
                    SPUserCollection uColl = web.AllUsers;
                    SPFieldUserValue uVal;
                    SPFieldUser uField = historyList.Fields.GetFieldByInternalName("User") as SPFieldUser; 
                    int count = coll.Count;
                    for (int ctr = 0; ctr < count; ctr++)
                    {
                        item = coll[ctr];
                        uVal = uField.GetFieldValue(item["User"] as string) as SPFieldUserValue; // get SPFieldUserValue object using User from history list
                        if (uVal != null)
                        {
                            userName = uVal.User.Name;
                        }
                        else
                        {
                            userName = "Not known";
                        }
                        outcome = item["Outcome"] as string;
                        dt = DateTime.Parse(Convert.ToString(item["Occurred"])); // Convert date occurred
                        /*The following block displays the date in SP 2013 kind of format i.e.
                         * when current date == Today then displays "Today at 3:45PM" (assuming time is 3:45 in occurred field).
                         * Similary for Yesteray and Day before yesterday. 
                         * When none of the if are satisfied - display the Long Date string format
                         */
                        if (dt.Date == DateTime.Today)
                        {
                            sDT = "Today at " + dt.ToShortTimeString();
                        }
                        else if (dt.AddDays(1).Date == DateTime.Today)
                        {
                            sDT = "Yesterday at " + dt.ToShortTimeString();
                        }
                        else if (dt.AddDays(2).Date == DateTime.Today)
                        {
                            sDT = "Day before yesterday at " + dt.ToShortTimeString();
                        }
                        else
                        {
                            sDT = dt.ToLongDateString();
                        }
                        comments = item["Description"] as string;
                        comments = (comments == null ? string.Empty : comments);
                        /*  The assumption here is that we have fields in our InfoPath main data source with these names.
                         * Abbreviations are:
                         * pos -- position (in reverse) 
                         * hs -- History
                         * un -- UserName (display name)
                         * oc -- Task outcome
                         * cm -- Comments by approving user or system
                         * dt -- date of history item - in reverse chronological order
                         */
                        sb.AppendFormat("<my:hs my:pos='{0}' my:un='{1}' my:oc='{2}' my:cm='{3}' my:dt='{4}' />", count - ctr, userName, outcome, comments, sDT);
                    }
                    // If there are any history items
                    if (sb.Length > 1)
                    {
                        XPathNavigator nav = CreateNavigator();
                        // Set the generated XML to InfoPath main datasource "Group" element i.e. History Parent
                        nav.SelectSingleNode("/my:myFields/my:HistParent", NamespaceManager).InnerXml = sb.ToString();
                    }
                }
            }
        }


Finally please let me know your feedback:

Saturday, May 11, 2013

Display Ratings control inside Data View Web Part (DVWP - SharePoint Designer) aka Data Form Web Part (DFWP)

Though this looks straightforward i.e. drag and drop the rating field from data source in data view web part and it will show the rating stars (asterisks) but the output does not show as expected in data form web part.

There are few posts on internet to create a custom code data view web part that can display the ratings (by inheriting from data view web part). Well how about doing it out of the box. Yes - there is a 3 step solution  we have and that is what this post is about. So here we go:

Steps
1.  Modify the web.config file for SharePoint web application. Open web.config in a editor and
search for <Controls> section. There is already one line there for asp.net. We add another one as below:

  <add tagPrefix="Portal" namespace="Microsoft.SharePoint.Portal.WebControls" assembly="Microsoft.SharePoint.Portal, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />

Why we do the above: Data View Web Part does not use the import / register tags defined on top of .aspx page.  It has its own independent system (built within its compiled code) to use the namespace it wants to use. By adding the above line in <Controls> section it makes those namespaces available.

2. In SharePoint Designer open the page containing data view web part
    Switch to code view: On the top of DVWP in xslt, there are all the namespaces that are defined.
    We will add the namespace so that we can use any control from

    "Microsoft.SharePoint.Portal.WebControls" defined in controls section in step 1) above

xmlns:Portal="Microsoft.SharePoint.Portal.WebControls

Why : This is because for any dll methods we want to use in DVWP, it must define the namespace for that in XSLT.


3. Finally to display the ratings controls add the following tag in the XSLT wherever we need to display the ratings:


<Portal:AverageRatingFieldControl  runat="Server" itemid="{@ID}"  id="MyRating{generate-id()}" FieldName="AverageRating" ControlMode="Edit" />

Few things above: 
a) {@ID} links the ID of the listitem for which we want to display or display/edit the ratings
b) FieldName must be AverageRating only
c) ControlMode: If we want to only display the ratings use "Display" however for display as well as allow  user to modify the rating use "Edit"


Additionally 
a) The user profile service must be is running since rating data is stored in Social Database, and ups should be associated with the web application.
 b) The Users Profile Social .... timer services must be running (2 of those). By default the update time is in hours i.e. when user clicks the asterisk, it would take few hours to show the updated rating. Change the settings to make it update faster (the downside of running more frequently means more load on server).

Hope this will be useful to anyone looking to get ratings without writing any code.


 -- Mohan



Thursday, March 21, 2013

Export a list using Powershell

Well this post is more for a syntax reference.

Using Export-SPWeb is one of the ways copy list schema and data from one web app to another Or one server to another.


Onw of the reasons I have used this many times is for a list that is in production - copy that list only to a Dev environment to work and develop artifacts for that list like workflow, event handlers etc.

Here is the sample that explains the syntax:

Export-SPWeb -Identity "http://str-sp2010/subsite" -ItemUrl "/subsite/lists/List Title here"
-path "c:\folder\filename.dat"


Where:

-Identity: full url (absolute) of the site where the list exists
-ItemUrl:  relative path of the list from subsite level. Do not forget to include the leading "/"
-path: path of the output file to save the data in. This does not need any extension but I usually give it .dat meaning data

There are lot more additional parameters but above are the minimum for copying a list


To recreate the list in other environment - we use Import-SPWeb command.