20 November 2009

dinner party download: ice breakers 17 - 18



A man and a woman are playing golf.  They are playing on this old, rustic course and they come to this old barn on the side of this course. The guy hits the ball over there, and he goes over:




Guy: You know, I'm going to take a drop and just forget about the barn, just take the stroke penalty.


Wife: Wait a minute, honey. What if you just open up the doors and hit right through?  You don't have to take the penalty. 






So he hits the ball, and it ricochets off something and ricochets off something else, hits his wife in the head, kills her dead.


One year later, the guy is playing the same course.  Comes to the same hole.  He is playing with a buddy of his.  He pushes the ball into almost the same place.  And he tells his buddy




Guy: Hey man, I'm just going to pick it up and take the stroke penalty.  


Buddy: Hey man, no, you know what you should do?  Open up the doors and hit it right through - then you won't have to take the penalty. 


Guy: Are you kidding man?  I did that last year.  I made a 5 on this hole.





Larry Wilmore, dinner party download, ep 17.








16 November 2009

Default Document Content Type always loading as Document

The problem statement is in MOSS 2007, when the user adds custom content types to an existing document library and then set them up to appear in the new document dropdown, MOSS still uses the default Document content type (though after saving, you can edit properties to set the correct content type - how undesirable!). Stephen Muller pointed me in the right direction for a fix, but mine differs slightly from his.

Solution:
Create a document library and set the default Document Template to None. i.e. do not select Microsoft Word, Microsoft Office Word 97-2003 template, etc.




After the document library is created, do this within the Document Library Settings:
  1. Create Columns or Add from existing site columns
  2. go Advanced Settings > Allow managment of Content Types=Yes,  
  3. then Add from existing site content types
  4. then Change new button order and default content type and set the new content type up as #1 (for example)



11 November 2009

Displaying workflow item information from a custom ASPX task form



When building custom workflows, often we want custom Task edit screens as well.  A custom Task Content Type allows us to collect much deeper data than 'completed'.  However, since we are ditching the default Task edit pages, we lose a couple of useful features; a link to the underlying Workflow item as well as the implied context of the Task.

Given the need to provide context to the user when overrriding the default task form, you may come across the desire to display some file information, custom columns/properties, links, etc on the task edit page.  

To accomplish this, we need to gain two pieces of understanding:

  1. How and when to modify ASPX page elements from the code-behind c# class
  2. How to extract the underlying information from the Workflow's original item

When: during the page_load() of inherited class;
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!Page.IsPostBack)
            {

                    ...
                    //string _paramTaskListItemID Represents the ID Task List Item being worked on 
                    this._paramTaskListItemID = Request.Params["ID"];

                    //SPListItem _TaskListItem Represents the item within the task list 
                    this._TaskListItem = this._TaskListAttachedTo.GetItemById(System.Convert.ToInt16(this._paramTaskListItemID));

                    //Guid _workflowInstanceGuid Represents the ID of the running workflow
                    this._workflowInstanceGuid = new Guid(Convert.ToString(this._TaskListItem["WorkflowInstanceID"]));

                    // SPWorkflow _activeWorkflow Represents the current workflow 
                    // Instantiate the workflow, retrieve the SPworkflow object for the web 
                    this._activeWorkflow = new SPWorkflow(this._myTeamSite , this._workflowInstanceGuid );

                    // protected System.Web.UI.WebControls.Label labelFileURL is a reference to the aspx form's label
                    ...



                    ...
                try
                {
                    labelFileURL.Text += "Job ID is: " + this._activeWorkflow.ParentItem.File.Properties["Job ID"].ToString();
                }
                catch (NullReferenceException)
                {
                    labelFileURL.Text += "Job ID is not set.";
                }
                    ...



How do we modify the aspx page in flight - insert a placeholder aspx object within the task form - choose a good candidate object like a label;

<%@ Page Language="C#" AutoEventWireup="true" EnableSessionState="true" ValidateRequest="False"  Inherits="BI_IA_NS.BI_TaskApprovalForm" %>
...
<asp:content contentplaceholderid="PlaceHolderMain" runat="server">
        <p class="subheader">Job Information 
         <div class="greytext">
            <asp:Label ID="labelJobID" runat="server"></asp:Label>
            <asp:Label ID="labelFileURL" runat="server"></asp:Label>
         </div>
        </p>

These labels are placeholders that we can manipulate based on actions or events sent to the code-behind class. Now that we have the ability to modify the form in flight, this brings us to how we extract the workflow document data while editing a task.

The key is to build the correct SPWorkflow object - as it turns out, there are two constructors for this object. If you use the constructor based on the tasks' list item, all the information within the SPWorkflow object is relative to the Task List. So we want to use the SPWorkflow constructor with the SPWeb parameter.

SPWorkflow (SPListItem, Guid)
SPWorkflow (SPWeb, Guid)


The SPWorkflow object can be created like this:

                    ...
                    //string _paramTaskListItemID Represents the ID Task List Item being worked on 
                    this._paramTaskListItemID = Request.Params["ID"];

                    //SPListItem _TaskListItem Represents the item within the task list 
                    this._TaskListItem = this._TaskListAttachedTo.GetItemById(System.Convert.ToInt16(this._paramTaskListItemID));

                    //Guid _workflowInstanceGuid Represents the ID of the running workflow
                    this._workflowInstanceGuid = new Guid(Convert.ToString(this._TaskListItem["WorkflowInstanceID"]));

                    // SPWorkflow _activeWorkflow Represents the current workflow 
                    // Instantiate the workflow, retrieve the SPworkflow object for the web 
                    this._activeWorkflow = new SPWorkflow(this._myTeamSite , this._workflowInstanceGuid );


Then, by combining the techniques of modifying the label, and extracting the document metadada, gets us to our goal:
                try
                {
                    labelFileURL.Text += "Job ID is: " + this._activeWorkflow.ParentItem.File.Properties["Job ID"].ToString();
                }
                catch (NullReferenceException)
                {
                    labelFileURL.Text += "Job ID is not set.";
                }

To help understand what properties your file has, you can enumerate the whole set of properties (as a debugging step) - bonus code:
                System.Collections.Hashtable collHashes = this._activeWorkflow.ParentItem.File.Properties;
                System.Collections.ICollection collKeys = collHashes.Keys;
                string strList = "File properties are 
";

                foreach (object oKey in collKeys)
                {
                    strList += (SPEncode.HtmlEncode(oKey.ToString())
                    + " :: " +
                    SPEncode.HtmlEncode(collHashes[oKey.ToString()].ToString())
                    + "
");
                }
                labelJobID.Text = strList;


Reference on MSDN - Item or SPWorkflowActivationProperties from SPWorkflow

10 November 2009

Security and Audit Trail of Workflow Tasks in SharePoint 2007 & Visual Studio 2008





Two requests that are probably part of every SharePoint workflow design:
  1. Permit only the users who are assigned a WF tasks to edit (i.e. complete) them
  2. Record the user name of the person who completes a task (instead of the System Account)
Not suprisingly, neither of these pretty simple, seemingly obvious design concepts can be done out of the box / without some customization.  Also, certain configuration screens might trick you into thinking you can do this directly within the SharePoint UI (you can't; more on this in the deeper blog posts).

Part 1: Security of Workflow Tasks in SharePoint 2007 and Visual Studio 2008



Part 2: Audit Trail of Workflow Tasks in SharePoint 2007 and Visual Studio 2008

 
There are a couple of good blog sources for each of these customizations, but I didn't find any of them to be thoroughly detailed with screenshots, pitfalls, etc.  So what I hope to do in this two part blog post is cover some portions I think those sources left off, and provide updates relevant to Visual Studio 2008. 

CodePlex module?

At a high level, one would like to specify these widely useful features in the Workflow Settings screen or even within the Task List settings.  I may eventually release this into an extension on codeplex so if someone might find this useful, let me know. 

E.g.
Package an extension to the content type = Workflow Task List's General Settings page that pre-packages the code and intercepts the creation of new task items by tacking on special permissions.  It'd go right here:






04 November 2009

HOWTO beautify code syntax with automatic formatting within blogger

Admittedly, I've been lazy about just dropping code into blogger (unformatted), but now I'm glad to say that I've finally gotten around to adding a bit of css and javascript magic into the blogger template.

The syntax highlighter code is created and maintained by Alex Gorbatchev and to get it up and running quickly, I more or less used Carter Cole's instructional write up.  After modifying the theme choice and putting the css and js on a host, we are cooking with a Foreman!  Note, I'm using the standard distribution which includes a smaller set of brushes, whereas there are many more out there, and others in development.




So here is exactly what I did:
  1. Download the current distribution of the sytnax highlighter above, or decide if you are okay with letting someone else host that css & javascript for you

    • if chose to downloaded it, upload/ftp/host it at your own provider (e.g. I use 01solutions.com)

  2. Log in to your blogger account (try using google's beta), look for the blog in question, click Layout
  3. The URL should read http://draft.blogger.com/rearrange?blogID=magicnumber
  4. Click Edit HTML
  5. Backup your current template to a local file, by clicking Download Full Template
  6. Edit the template: I inserted the below javascript & css declarations after the HTML tag, and Before the Head tag

<link href='http://www.01solutions.com/images/syntaxhighlighter_2.1.364/styles/shCore.css' rel='stylesheet' type='text/css'/> 

<link href='http://www.01solutions.com/images/syntaxhighlighter_2.1.364/styles/shThemeDjango.css' rel='Stylesheet' type='text/css'/>
<script src='http://www.01solutions.com/images/syntaxhighlighter_2.1.364/scripts/shCore.js' type='text/javascript'/> 
<script src='http://www.01solutions.com/images/syntaxhighlighter_2.1.364/scripts/shBrushCpp.js' type='text/javascript'/> 
<script src='http://www.01solutions.com/images/syntaxhighlighter_2.1.364/scripts/shBrushCSharp.js' type='text/javascript'/> 
<script src='http://www.01solutions.com/images/syntaxhighlighter_2.1.364/scripts/shBrushCss.js' type='text/javascript'/> 
<script src='http://www.01solutions.com/images/syntaxhighlighter_2.1.364/scripts/shBrushJava.js' type='text/javascript'/> 
<script src='http://www.01solutions.com/images/syntaxhighlighter_2.1.364/scripts/shBrushJScript.js' type='text/javascript'/> 
<script src='http://www.01solutions.com/images/syntaxhighlighter_2.1.364/scripts/shBrushPhp.js' type='text/javascript'/> 
<script src='http://www.01solutions.com/images/syntaxhighlighter_2.1.364/scripts/shBrushPython.js' type='text/javascript'/> 
<script src='http://www.01solutions.com/images/syntaxhighlighter_2.1.364/scripts/shBrushRuby.js' type='text/javascript'/> 
<script src='http://www.01solutions.com/images/syntaxhighlighter_2.1.364/scripts/shBrushSql.js' type='text/javascript'/> 
<script src='http://www.01solutions.com/images/syntaxhighlighter_2.1.364/scripts/shBrushVb.js' type='text/javascript'/> 
<script src='http://www.01solutions.com/images/syntaxhighlighter_2.1.364/scripts/shBrushXml.js' type='text/javascript'/> 
<script src='http://www.01solutions.com/images/syntaxhighlighter_2.1.364/scripts/shBrushPerl.js' type='text/javascript'/> 
<script language='javascript'> 
SyntaxHighlighter.config.bloggerMode = true;
SyntaxHighlighter.config.clipboardSwf = 'http://www.01solutions.com/images/syntaxhighlighter_2.1.364/scripts/clipboard.swf';
SyntaxHighlighter.all();

So now to post something with code, while preparing the post:
  1. Click Edit HTML
  2. If you are posting HTML source itself, your results may vary (depending if you are using a beta version of blogger, etc) - but I'd recommend to escape all < and > with &lt; and &gt; which can be automatically done for you via many webpages such as the one Carter Cole points out by Accessify: http://accessify.com/tools-and-wizards/developer-tools/quick-escape/default.php
  3. Decide which brush / language you are using, and preface & postface the code you are dropping in with the appropriate pre tag (use the alias list in the first image above), e.g.:


great HTML code goes here..

<table width=100% cellpadding=0 cellspacing=0 border=0>
<tr>
<td width=143 rowspan=3>
<h1>
<a href="?">
<img src="/mail/help/images/logo1.gif"
width=143 height=59 border=0
alt="Gmail by Google">
</a>
</h1>
</td>
<td width=1 rowspan=3> </td>
<td height=25 colspan=2 align=right valign=top>



Gmail by Google

 


or


        // insert your c# code here
        public Workflow1()
        {
            InitializeComponent();
        }

        public Guid workflowId = default(System.Guid);
        public SPWorkflowActivationProperties workflowProperties = new SPWorkflowActivationProperties();
Easy, eh? Not sure if I'm going to go back and update old code within posts yet, but if anyone comments on whether that'd be helpful, I could be convinced. 

03 November 2009

Workflow LogToHistoryList UserID and Task Activity Executor in SharePoint

This topic is within a SharePoint 2007 workflow, how do we get the Workflow History to reflect the user who modified a task, rather than the "System Account".  I've successfully implemented the solution suggested by Julie Kramer, from the office dev blog.  Here we go:

snippet of a workflow design in VS2008 - focus on the two red activities




  1. Assign a new field to the Executor property of the OnTaskChanged activity in question (you'll see this property in the property window on the activity). Note we use this new field in a later LogToHistoryListActivity's Method_Invoking() by assigning the UserID property of the LogToHistoryListActivity's object





  2. Add a userid lookup/helper function to guarantee a valid SharePoint user id is in place
        private SPUser GetUserObject(string accountID)
        {
            try
            {
                if (accountID.IndexOf(@"\") > 0)
                {
                    SPUser user = this.workflowProperties.Web.SiteUsers[accountID];
                    return user;
                }
                else
                {
                    //replace DOMAIN 
                    SPUser user = this.workflowProperties.Web.SiteUsers[@"DOMAIN\" + accountID];
                    return user;
                }
            }
            catch
            {
                //replace DOMAIN and administrator
                SPUser adminUser = this.workflowProperties.Web.SiteUsers[@"DOMAIN\administrator"];
                return adminUser;
            }
        }
As you assign a new field to each TaskChangedEvent's executor property, you'll see this declaration inserted to the bottom of the workflow class.


public String RVPReviewTaskChangedEvent_Executor1 = default(System.String); 

Later when the LogToHistoryListActivity's Method_Invoking event is triggered, you use this new field and the helper function to modify the UserID property on the LogToHistoryListActivity's object;



        private void logRVPReviewed_MethodInvoking(object sender, EventArgs e)
        {
            SPUser executor = GetUserObject(RVPReviewTaskChangedEvent_Executor1);
            logRVPReviewed.UserId = executor.ID;

        }