Thursday 21 June 2007

Sharepoint Development Tips 2-CAML Power!

CAML(Collaborative Application Markup Language) is the tool you want to use before you mess around with querying your sharepoint list and views with foreach(s). Experience prooved that foreach the whole list or in case you have to use embedded foreach statement, the performance is quite disappointing. The alternative is to use CAML which is used by sharepoint by default when creating views for lists.

The usage is easy. It's like the NHibernate's HQL if you use it before.
In here, I have a list called "Team List". Now I want to obtain the item that has the Job No.(job number)of 12 and Customer Name(customer's name )is equal to "Your Name", the CAML statement will be like following


the syntax is relatively easy apart from one wicked requirement, the ASCII characters are needed to be encoded to hexidecimal value and with both prefix and suffix of '_', in our case the space character is encoded as x0020, and the '.' as x002e.

So the usage of above CAML will be by putting the CAML statement into a string e.g. teamQueryString:



SPQuery query = new SPQuery();
SPListItem resultItem =null;
query.Query= teamQueryString;

//in our case the result is unique
resultItem = teamList.GetItems(query);

By this way , you get the team with job number is 12 and customer name is "Your Name"

If you wonder how you could figure out what hex value to encode all the characters out there or you are too lazy to write the CAML at all, I recommend using U2U's CAMLBuilder, they are pretty handy and most importantly, it's free! (You can tell that I am that lazy by sensing the tone :P )

Tuesday 19 June 2007

Sharepoint Development Tips 1-Workflow status code

When you want to retrieve the value of your sharepoint inbuilt workflow and use it somewhere else, say Infopath client/browser forms, the result of getting the string value of them are not like what you see in the web page as "in progress","Rejected",etc, it's all integer status code representing different status. So this is what I found out until now.

In Progress: 2
Approved: 16
Rejected: 17
Canceled: 4

Friday 8 June 2007

Customize Sharepoint Designer Workflow activity

Document management is the soul of all sharepoint project which is content management based or simple intranet application. Sharepoint Designer 2007 shipped along with MOSS is a powful tool which allows greater user involvment to customize the site and more importantly, build customized workflow without much coding effort. This shares the same thought with Expression Web that complies with .net 3.5 technologies which web designers can accomplish the tasks that had to be done by developers. This following piece of text will demonstrate how to use sharepoint designer's workflow designer to augment the existing actions provided by WSS 3.0.





As said above, sharepoint developers will run into implement document handling across different list through the lifecycle of document management whatever the project is like. Copy or move file items /list item from one to another according to different event (document change, document created etc.) is almost a must-have functionality. Workflow built in sharepoint designer can do this without a single line of code.









and if you want to move item, simply add another action "delete item in this list".

However, almost certainly you will want to have more actions or conditions when building the workflow in realworld. For example, above action will only copy the item to the list's root folder, what if you want to add it to certain folder or create one if not exist? This is where the customized workflow comes in, there are two alternatives: Customized Workflow created by Visual Studio and deployed as feature, this kind of workflow will not be restricted to certain list and therefore reusable;Workflow created by visual studio but deployed by using sharpeoint designer. This one will be tied up with certain list and not as reusable, yet easier to implement & deploy. This post will introduce how to create sharepoint designer workflow custom activity using based upon the copy item to folder action.








  1. First of all, you will need to install Visual Studio 2005 Extensions for WF, after that, you will have the work flow library template for you to start build your new project on. Give it a name, let's call it "DispatchActivity"


  2. On the design view of the workflow, drag and drop a code activity for your first and only activity.from your toolbar, give it a name of "CopyListItemAct"


  3. Double click the code activity to edit the code-behide , the CopyListItemAct_ExecuteCode event handler will be one the code to be executed in this step. You can find the event wire-up in InitializeComponent() as expected in designer.cs.

  4. Find the Microsoft.sharepoint.workflowactions.dll in ISAPI folder in your MOSS's c:\program files\common files\Microsoft shared\web server extension\12\ISAPI folder, and add it to your project as reference. The workflow context property will need it later


  5. A SPD activity or a condition needs a textual descrition to represent the literal meaning of action, which is called a "sentence". In our case, the sentence will be "Copy this Item in one list to a folder in another list". The bold keywords will be the properties that are needed in our custom activity. So in order to "promote" these properties, we need to have properties called DependencyProperties which is necessary for promoting properties to SPD workflow. This is a requirement from windows workflow foundation, See MSDN for more information.


    So by looking at the sentence that is needed in SPD, we need to have the context of sharepoint so we can get the list item and list objects from the site. Also, we need to have the reference of current listitem and the target list. So the dependency properties will look like following.




    //current sharepoint context
    public static DependencyProperty __ContextProperty =
    DependencyProperty.Register("__Context", typeof(WorkflowContext), typeof(DispatchActivity));
    [Description("context")]
    [ValidationOption(ValidationOption.Required)]
    [Browsable(true)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    public WorkflowContext __Context
    {
    get { return ((WorkflowContext)(base.GetValue(DispatchActivity.__ContextProperty))); }
    set { base.SetValue(DispatchActivity.__ContextProperty, value); }
    }

    //list id
    public static DependencyProperty ListIdProperty =
    DependencyProperty.Register("ListId", typeof(string), typeof(DispatchActivity));
    [Description("List Id")]
    [ValidationOption(ValidationOption.Required)]
    [Browsable(true)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    public string ListId
    {
    get { return ((string)(base.GetValue(DispatchActivity.ListIdProperty))); }
    set { base.SetValue(DispatchActivity.ListIdProperty, value); }
    }

    //list item
    public static DependencyProperty ListItemProperty =
    DependencyProperty.Register("ListItem", typeof(int), typeof(DispatchActivity));
    [Description("List Item")]
    [ValidationOption(ValidationOption.Required)]
    [Browsable(true)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    public int ListItem
    {
    get { return ((int)(base.GetValue(DispatchActivity.ListItemProperty))); }
    set { base.SetValue(DispatchActivity.ListItemProperty, value); }
    }

    //TolistId item
    public static DependencyProperty ToListIdProperty =
    DependencyProperty.Register("ToListId", typeof(string), typeof(DispatchActivity));
    [Description("Target List Name")]
    [ValidationOption(ValidationOption.Required)]
    [Browsable(true)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    public string ToListId
    {
    get { return ((string)(base.GetValue(DispatchActivity.ToListIdProperty))); }
    set { base.SetValue(DispatchActivity.ToListIdProperty, value); }
    }



  6. These properties will simply be used as normal c# properties.

  7. Before we start coding on CopyListItemAct_ExecuteCode to copy item over to the folder in the target list, we need to create a .ACTION file to map all the dependency properties in the class so Sharepoint can recogize the custom sentence and present the sentence to the user to fill it in . The xml file should look like as following.


    What is worth mentioning is that you should always keep in mind putting the __context property in front of other properties, either in ACTIONS file or in your class. I will explain this later. Also, in the first fieldBind, we notice that we should use the format string of "ListId,ListItem" to get the current item in the current list. The result in SPD will be a dropdownbox prompting to confirm/select other list items to copy from.




  8. Now we code the CopyListItemAct_ExecuteCode() in your activity .cs file for folder create and copying activity.

    private void CopyListItemAct_ExecuteCode(object sender, EventArgs e)
    {
    try
    {
    /*setup context and get reference
    of current list SPD is attached to
    and the item we are dealing with*/
    SPSite thisSite = __Context.Site;
    SPWeb thisWeb = __Context.Web;
    SPList thisList = thisWeb.Lists[new Guid(this.ListId)];
    SPListItem thisItem = thisList.GetItemById(this.ListItem);
    //get the target list
    SPList targetList = thisWeb.Lists[new Guid(this.ToListId)];

    /*e.g. we can use the name of curent item to create folder
    in target list*/

    if(targetList !=null)
    {
    string folderName = thisItem.Text;
    //check if the folder exist in target list
    if (!ExistingOrder(folderName, targetList))
    {
    //create a folder with the folderName in the target list
    string listURL = thisWeb.Url+"/" + targetList.Title;

    //the folder in Sharepoint can only be created this way
    //by specifying the leafname as the folder name
    SPListItem newFolder = targetList.Items.Add(listURL.Trim(), SPFileSystemObjectType.Folder, folderName);

    if (newFolder != null)
    {
    //reflect the change
    newFolder.Update();
    targetList.Update();


    //copy item over to the folder we just created
    foreach (SPListItem targetItem in targetList.Folders)
    {

    if (targetItem.Folder.Name == orderNumber)
    {
    AddFileToFolder(thisItem.File, targetItem.Folder);
    }
    }
    }


    }
    else // if the folder is there already, copy item
    {
    //copy item over to the folder we just created
    foreach (SPListItem targetItem in targetList.Folders)
    {

    if (targetItem.Folder.Name == orderNumber)
    {
    AddFileToFolder(thisItem.File, targetItem.Folder);
    }
    }
    }
    }




    }
    catch(Exception)
    {...}
    }




    Then we code the AddFileToFolder method, which is only couple of lines of code, breezy...

    private void AddFileToFolder(SPFile _thisFile, SPFolder _thisFolder)
    {

    string fileURL = _thisFolder.Url + "/" + _thisFile.Name;
    try
    {
    byte[] fileByte = _thisFile.OpenBinary();

    _thisFolder.Files.Add(fileURL, fileByte);

    }
    catch (IOException ioe)
    {
    throw new IOException(ioe.Message);
    }

    }

    As for how to check if the folder exists, the code is very straightforward, it will not to be posted here to save space.


  9. After signing your assembly, we need to make sure it is deployed correctly.

    1. After you make the ACTION file, make sure you put it either in a independent xml file with extension of .ACTIONS under the server's folder %SYS_VOL%:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\1033\Workflow, or simply just copy the part of elements to the WSS.ACTIONS file under the same folder as above. The reason why I said that the __context should go first is that sharepoint will automatically change your properties order in ACTIONS file, while making sure it's the same order as the one in your activity class file, the __context always comes the first. (This is true when you put your custom activity in WSS.ACTIONS, never tried to prove if it's true in separated file. )
    2. In your web.config file, you need to specify your assembly is an authorized control as others in Sharepoint, you can achieve this by adding in following line
    in,


    You will need to make sure above is 100% correct against your assembly name and pubilc key, otherwise, you might find out that the action is not showing up in workflow designer when you try to add it to the SPD action in steps.


  10. Last thing to do is to deploy the dll you made to the GAC, and do an IISRESET so the sharepoint will pick up your new workflow activity.



Now if you open up Sharepoint designer, when you try to add in your action, you can click "more actions"->choose "Custom SPD Activities" in the category dropdown , you should be able to see your own custom activity if everything in this article is followed. Good Luck!

Monday 4 June 2007

Impersonation with Sharepoint Portal Server

Still another one for sharepoint security. In order to let our code to run, we have to know CAS(Code Access Security) , (see another one in my blog about CAS) to configure the permission of assemblies we made properly so sharepoint will know which assembly should access which part and how much of the system resources. Another level of this will be sharepoint in-build access control which is enforced by the sharepoint group and users. Take webpart for instance, if we put a webpart on the sites under the portal (assuming you are using SPS) and just access the content(list, document lib etc) but not in any subsites which doesn't use the same permission as this site, you will be fine without any impersonation. However, if you do want to use cross-site data to complete the logic in your webpart, say, you want to put your webpart in your portal front page and show the subsites data which you might not have permission. The purpose of this might be an overview or statistics for site information.You will have to come across the override of permission issues so you will get your code running without being stopped by security guard :) .

If you are using MOSS, you are lucky enough to be able to take advantage of in-build API method: RunWithElevatedPrivilege like this:


//using directives
using Microsoft.Sharepoint;
using Microsoft.Sharepoint.WebControls;

SPSecurity.CodeToRunElevated code = new SPSecurity.CodeToRunElevated(method_need_elevation);
SPSecurity.RunWithElevatedPrivilege(code);


public void method_need_elevation()
{...}


By using that, you can easily obtain the application pool identity.

In SPS, you have to hijack the system dlls in order to get hold of the windows identity which will be pointing to app pool. First of all, you need to make a class to contain this logic. Please note that we need to implement IDisposable interface so when the code exits the method, the impersonation will be restored to previous state. Also because we are using DLLImport, the system resource will not be garbage collected if we don't explicitly manually recycle the used objects. This will be mentioned later.

Namespaces needed in this article
we need to create the Windows Identity to represent the application pool account. WindowsIdentity object can be found within System.Security.Principal namespace, besides, as we need the entry point to the system dlls, System.Runtime.InteropServices namespace is needed for using DllImport attributed methods. At last we add in System.ComponentModel namespace to support win32Exceptions and application exceptions.

Code Explanation

As we discussed above, add in windows identity account variable _appPoolIdentity, and also the identitycontext objects for both current user's and app pool's . The reason of promoting these as member variables are later on we need to GC the unmanaged objects using these contexts as the reference.



using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Principal;

public sealed class Identity: IDisposable
{

private static WindowsIdentity _appPoolIdentity;
private WindowsImpersonationContext _WindowsImpContext,_AppPoolImpSelfContext;





}

Then we need to import the dll and use the attributed methods DuplicateToken to copy the token of app pool to impersonate app pool's account.

//duplicate a token for code to impersonate
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
private static extern bool DuplicateToken(IntPtr hToken_, int impersonationLevel_, ref IntPtr hNewToken_);


and of course, we need to manually release the resource used by token copying.


//free unmanaged allocated resources
[DllImport("kernel32.dll", CharSet=CharSet.Auto)]
private static extern bool CloseHandle(IntPtr handle);


now we are ready to make the application pool account property


private static WindowsIdentity AppPoolIdentity
{

get
{
lock(typeof(Identity))
{
if(_appPoolIdentity==null)
{
//get token for this identity
IntPtr token = WindowsIdentity.GetCurrent().Token;
if(token == IntPtr.Zero)
{
throw new ApplicationException("Cannot fetch the token of this application pool");
}

//duplicate token in order to impersonate
if(! DuplicateToken(token, 2, ref token))
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "Unable to duplicate AppPool's identity token !");
}

//if didn't get the token, throw exception
if(token == IntPtr.Zero)
{
throw new ApplicationException("Unable to duplicate AppPool's identity token !");
}
// Store app pool's identity with the retrieved token
_appPoolIdentity = new WindowsIdentity(token);

//free the unmanaged resources
CloseHandle(token);
}
return _appPoolIdentity;
}
}
}


Then we make a call to execute impersonation using this apppool account identity. We can make a public constructor and write impersonation logic. Or We can make a static method to call a private constructor, which is much more elegant when using impersonation. Here it was using the second method.

//private constructor which will be called by the static impersonateAdmin method to impersonate
private Identity()
{
try
{
_AppPoolImpSelfContext= WindowsIdentity.Impersonate(IntPtr.Zero);
_WindowsImpContext = AppPoolIdentity.Impersonate();

}
catch
{

UndoImpersonate();
throw;
}
}


Note above that in case of exception occurs, the impersonate should be undo and therefore revert to previous user account identiy. This will be called in the manual GC process by simply recycle all the windowsidentitycontext we got from the return of impersonation. Notice not only the context needs to be set to null, but also the Undo() method of windowsidentycontext needs to be called to revert back to previous account.


private void UndoImpersonate()
{
//clear the context
if(_WindowsImpContext!=null)
{
_WindowsImpContext.Undo();
_WindowsImpContext = null;
}
if(_AppPoolImpSelfContext!=null)
{
_AppPoolImpSelfContext.Undo();
_AppPoolImpSelfContext = null;
}


}




We are almost there to finish the identity class, second to last thing is to implement IDisposable methods to carry out GC


public void Dispose()
{
CustDispose(true);
//self-controled Gabage collection
GC.SuppressFinalize(this);
}
public void CustDispose(bool _disposing)
{
if(_disposing)
{
this.UndoImpersonate();
}
}


and finally we write a public static method to execute the whole impersonation process, which is easy, we simply create a object of Identity class which calls the constructor.


public static Identity impersonateAdmin()
{
return new Identity();
}



Using Impersonation class we just made!

By using "Using" keyword, we surround the code that needs the permission of app pool account in the namespace we created using the class above and GC occurs after exits the namespace , the impersonation is done with breeze.


using (Identity impersonation = Identity.impersonateAdmin())
{
//code to run under impersonation context
}