Thursday, May 31, 2012

Copying additional dlls (assemblies) to GAC with SharePoint project deployment

Frequently, we reference some external assemblies in any project event SharePoint projects as well. The need in the SharePoint project is that we may need to copy those dependent assemblies to GAC with the SharePoint project assembly.

We do not want to manually copy all the dependent assemblies to GAC by ourselves. How to copy the assemblies to the GAC with the deployment itself?

The trick is to add the reference in thePackage.package file to tell the SharePoint to copy the assemblies also to the GAC. Just edit the Package.package file in a notepad or any xml editor and add the reference just like below.


Things to Note:
  • All the assemblies to be copied to GAC needs to be signed with a strong key.
  • The "sourcepath" value is the relative path of the location of the referenced assembly
  • "SafeControl" entry may be needed based on how the control is being used. (mostly needed to be used as any UI control)

Tuesday, May 29, 2012

Manage SPListItem publishing programatically

Recently, I had to work on converting a regular SPList to have similar to a publishing feature. Like, have an item edited, review it and then publish the page for public.

I made the below modifications to the list settings to enable versioning of list items.
Go to the List Setting page and select "Versioning Settings" and set the options to the same as below.

 
This enables the list items in the list to be version controlled. The author can save a version of the list item, review it and publish it at any point of time or revert to its previously published version.

I wanted to do it programatically using a layouts page (application page) since we are using the application pages as the default display form of the particular content type.

For saving a new SPListItem, the code should look similar to this. This will save the list item content and assign the status to "Pending".

var item = SPContext.Current.ListItemitem["Title"] = SPHttpUtility.ConvertSimpleHtmlToText(this.txtTitle.Text, 255);
//this will set the status of the current item to "Pending"
item.Update();


To know how to get the CurrentListItem in an app page, please see my other post here

To publish the list item, the code would look like this.

if (SPContext.Current.ListItem.ModerationInformation != null)
{
    SPContext.Current.ListItem.ModerationInformation.Status = SPModerationStatusType.Approved;
    SPContext.Current.ListItem.ModerationInformation.Comment = String.Format("Published on {0} by {1}" 
                                                   DateTime.Now.ToString(), SPContext.Current.Web.CurrentUser.Email);
    SPContext.Current.ListItem.Update();
    
    //Added to refresh the page content and hide the publishing controls 
    Response.Redirect(SPContext.Current.ListItem.Url);
}
 
To revert a saved list item to it's previously published version, the code would look like below:

if (SPContext.Current.ListItem.ModerationInformation != null && SPContext.Current.ListItem.Level != SPFileLevel.Published)
{
    //Getting the last published version of the current item
    var lastPublishedVersionInfo = SPContext.Current.ListItem.Versions.Cast<SPListItemVersion>()
                                                                            .Where(v => v.Level == SPFileLevel.Published)
                                                                            .OrderByDescending(l => l.Created).Take(1);
    SPContext.Current.ListItem.Versions.RestoreByID(lastPublishedVersionInfo.First().VersionId);
 
    SPContext.Current.ListItem.ModerationInformation.Status = SPModerationStatusType.Approved;
    SPContext.Current.ListItem.ModerationInformation.Comment = String.Format("Reverted on {0} by {1}", 
                                                               DateTime.Now.ToString(), SPContext.Current.Web.CurrentUser.Email);
    SPContext.Current.ListItem.Update();
 
    Response.Redirect(BlogManager.GetBlogPostUrl(SPContext.Current.Web, SPContext.Current.List.ID, SPContext.Current.ListItem.ID));
}

Hope this helps some one or even myself somewhere in the future.

Thanks
Senthil S

Tuesday, May 8, 2012

Filtering list items by user id using CAML in SPSiteDataQuery

I had some struggle finding out how to filter the list items by a particular user (SharePoint user). I am sure that this can be done but the filter does not seem to work for me if it is using the field value as "Integer". It was working very fine if the filter is applied for current logged in user as below:

<Query>
  <Where>
    <Eq>
      <FieldRef Name="Author" />
      <Value Type="Integer">
        <UserID Type="Integer" />
      </Value>
    </Eq>
  </Where>
</Query>
 
Of course, this only works if the filtering is applied for the currently logged in user. so, what does need to happen when the query needs to filter based on another user?

I tried the above CAML by replacing the <UserID Type="Integer" /> with the particular user's id value (SPUser.ID). This does not seem to work. Always it returns no items.

<Query>
  <Where>
    <Eq>
      <FieldRef Name="Author" />
      <Value Type="Integer">
        75
      </Value>
    </Eq>
  </Where>
</Query>


It turns out to be an attribute "LookupId" needs to be added to the <FieldRef> element as below.
 
<Query>
  <Where>
    <Eq>
      <FieldRef Name="Author" LookupId="True"/>
      <Value Type="Integer">
        75
      </Value>
    </Eq>
  </Where>
</Query>
 
Happy Coding.
Senthil S

Tuesday, May 1, 2012

Get items from multiple webs using SPSiteDataQuery

I know there is no easy way to get list items from multiple webs using SPSiteDataQuery. But I had to work on a similar situation for one of my projects. Looking deep into the list items attributes I came to know that there is a possibility to do the filtering based on the web itself (given some other small conditions)

Properties "ServerUrl" (server Relative Url) and "EncodedAbsUrl" (Encoded Absolute Url) cannot be used since the field values are of type "Computed" and SPSiteDataQuery does not filter based on "Computed" field values (there are some workarounds for some computed fields as well)

So I started looking into "FileRef" (URL Path) and "FileDirRef" (Path) for filtering. It seems to be working for me.

For a particular SPFile/SPListItem the above field values would give the exact location of the item.the values would look similar like below:


Publishing Content Types:
FileDirRef: SubSite1/SubSite12/Pages
FileRef: SubSite1/SubSite12/Pages/default.aspx

Non-Publishing Content Types: (normal SPListItems)
FileDirRef: SubSite1/SubSite12/Lists/{ListRootFolderUrl}
FileRef: SubSite1/SubSite12/Lists/{ListRootFolderUrl}/{ItemName}

Using the above field values and with proper CAML filter xml, items can be filtered from multiple webs.

In my case I was querying against non-publishing content types. So I was applying ContentTypeId filter primarily to filter based on content type. Above that, I was applying filters for "FileRef" values to get the items from multiple webs.

Sample CAML Query would look like this:
(The first one with value "Lists" is to filter the items in the root web)
In addition to the above filters, I was also using the "ContentTypeId" filter to get list items of particular type.

NOTE: If you are using just "SubSite1/SubSite12" to filter on the items, it would include the sub webs as well. Include the "/Lists" or "/Pages" at the end of web relative url to restrict to a single site.

If you have any questions, please add a comment.

Thanks
Senthil S