Wednesday, May 26, 2010

SharePoint 2010 Content Deployment Jobs Missing Deployment Options Section

I've been working with Content Deployment jobs lately, and noticed that in 2010, the section on the Create Jobs page where you can specify whether to perform an incremental or full content deployment was missing. Just to make sure I wasn't crazy, I looked up the old screen from 2007, which looks like this:


As you can see, there is a section there for setting the types of content that are deployed. The first option, "Deploy only new, changed, or deleted content" is the incremental deployment. The second option, "Deploy all content, including content that has been deployed before", is the full deployment.

Now compare that screen to the 2010 equivalent:

For some reason, the Deployment Options section is missing. This is not gonna work. If you recall from 2007, doing a full deployment after the initial deployment can cause problems. See this article for more information about why.


So, how do we create an incremental job? The answer, courtesy of Becky Bertram, MVP:
You can't do it through Central Administration, but you can specify whether you want to do a full or incremental deployment using PowerShell. Go to your SharePoint PowerShell prompt and type get-help new-spcontentdeploymentjob -detailed and then take a look at the IncrementalEnabled parameter.

Here is a simple script to create an incremental content deployment job, assuming you have already defined a content deployment path of "Authoring to Production":

New-SPContentDeploymentJob -Name "Authoring to Production - Incremental" 
-SPContentDeploymentPath "Authoring to Production" -IncrementalEnabled:$true

Looking in Central Administration, you'll see your created job, but you still can't see if it's incremental or full. To do that, run Get-SPContentDeploymentJob "Authoring to Production - Incremental". You should notice the following line in your output:
  • ExportMethodType             : ExportChanges
That sounds awfully like an incremental doesn't it? Let's try setting the  IncrementalEnabled parameter to false and creating another job:
New-SPContentDeploymentJob -Name "Authoring to Production - Full" 
-SPContentDeploymentPath "Authoring to Production" -IncrementalEnabled:$false 
Now run Get-SPContentDeploymentJob "Authoring to Production - Full" and you should see a slightly different export method type:
  • ExportMethodType             : ExportAll
So, this means that your Content Deployment job is going to be incremental by default. The only way to get a full job is to create it via PowerShell. Given the limitations and caveats with full jobs, this seems like a good change.

Thursday, May 6, 2010

SP 2010 - Web Parts and Embedded Resources Not Working

I ran into a problem recently trying to use Embedded Resources with SharePoint 2010 web parts. I was using code I had directly used in non SharePoint ASP.Net server controls, which if you aren't familiar with, is pretty simple. I'll briefly describe how you'd use an embedded resource, then describe my problem and how I fixed it.

First, you need to just add your resource, which in my case was a stylesheet, to your project. Then, using the properties pane, set the Build Action to Embedded Resource. Once you've done that, you'll need to edit the AssemblyInfo.cs file within your project and enable the embedded resource.

[assembly: WebResource("MyAssembly.styles.MyStyleSheet.css", "text/css")]

Now you have a resource that will be embedded in your DLL. The path to that resource is specified in the WebResourceAttribute above. I usually compile and open my DLL in reflector at this point to confirm that the path I've picked is correct. Once it is, you can include your stylesheet like this:

string styleSheetUrl = Page.ClientScript.GetWebResourceUrl(this.GetType(), "MyAssembly.styles.MyStyleSheet.css");
LiteralControl styleSheetLink = new LiteralControl(string.Format("LINK SYNTAX HERE - BLOGGER WONT LET ME EMBED THE HTML", styleSheetUrl));
                Page.Header.Controls.Add(styleSheetLink);

In theory, that should work, right? It works in server controls, and it's worked for me in older web parts for MOSS 2007. But, when I built my 2010 web part, it wasn't working.

I viewed source on my page and could see that the embedded resource was added to the markup. But when I tried to load that URL myself, I got an error, implying my resource wasn't there at all! I checked and rechecked my path inside the DLL. Everything was right.

But then I noticed something different about 2010 web parts. They use a LoadControl mechanism. I was accessing my embedded resources, using the above code, directly inside the webpart.ascx.cs, not the webpart.cs file. On a hunch, I moved the code into the webpart.cs file. Success!

This got me curious, so I looked at the source for the generated HTML and noticed that the generated URL for the embedded resource had changed. Apparently, the path is based somewhat on that first parameter to GetWebResourceUrl(), which is a type. Tinkering a little more, I learned that I could actually leverage the embedded resource from the webpart.ascx.cs by making a minor adjustment.

string styleSheetUrl = Page.ClientScript.GetWebResourceUrl(this.Parent.GetType(), "MyAssembly.styles.MyStyleSheet.css");

Notice that I'm not passing the type of the parent, which for our webpart.ascx.cs, is the wrapper class that calls LoadControl.

Tuesday, May 4, 2010

SP 2010 - LINQ versus CAML Joins and the Nuances of Projected Fields

When working with relational lists in SharePoint 2010, you have the option to use LINQ to SharePoint or CAML to join those lists to pull data out. While LINQ is easier to use and will leverage CAML under the covers, it is not always capable of performing queries that CAML can directly.

For instance. Let's say you have a list, called Parent. This list has a single valued lookup column, PrimaryChild, that refers to the Children list. LINQ can very easily perform a query with a where clause based on PrimaryChild:

var parents = from p in context.Parents
       where p.PrimaryChild.Title == "My First Child"
       select p;

However, if you were to create a second lookup column, OtherChildren, that allowed multiple values, LINQ would run into difficulties because the lookup is now represented as an EntitySet. With a single valued lookup field, you would instead have just a strongly typed object, with direct access to the fields within that list item.

var parents = from p in context.Parents
       where p.OtherChildren.Any(c => c.Title == "My Other Child")
       select p;

Running this query will throw an exception that semi-efficient queries are not allowed. If you recall, this is because LINQ leverages Two-Stage Queries. So, you can continue with LINQ and perform the query in two stages, or you can change the query to use CAML.

Here is the same query in CAML, but this time, the query will not cause an exception.

using (SPSite site = new SPSite(SPContext.Current.Site))
{
 SPWeb web = site.RootWeb;
 if (web != null)
 {
  SPList list = web.Lists["Parents"];
  if (list != null)
  {
   SPQuery query = new SPQuery();
   StringBuilder sbQuery = new StringBuilder();
   sbQuery.Append("");
   sbQuery.Append("");
   sbQuery.Append("My Other Child");
   sbQuery.Append("");
   query.Query = sbQuery.ToString();

   StringBuilder sbJoins = new StringBuilder();
   sbJoins.Append("");
   sbJoins.Append("");
   sbJoins.Append("");
   sbJoins.Append("");
   sbJoins.Append(" ");
   sbJoins.Append(" ");
   query.Joins = sbJoins.ToString();

   StringBuilder sbProj = new StringBuilder();
   sbProj.Append("");
   query.ProjectedFields = sbProj.ToString();

   StringBuilder sbView = new StringBuilder();
   sbView.Append("");
   sbView.Append("");
   query.ViewFields = sbView.ToString();

   if (!string.IsNullOrEmpty(query.Query))
   {
    SPListItemCollection matches = list.GetItems(query);
    foreach (SPListItem match in matches)
    {
     Console.WriteLine(match["Title"]);

     string rawNickname = (string)match["OtherChildrenNickname"];
     if (!string.IsNullOrEmpty(rawNickname))
     {
      SPFieldLookupValue nickname = new SPFieldLookupValue(rawNickname);
      Console.WriteLine(nickname.LookupValue);
     }
    }
   }
  }
 }
}

You'll notice that in the example above I'm performing a CAML join and also leveraging Projected Fields. Here are some guidelines/rules to keep in mind:
  • The CAML has several attributes that ask for the list name or list alias. This is NOT the actual name of the list. Rather, it is the internal name of the lookup field within your list. So in our example, the list was named Children, and the field was OtherChildren. We used OtherChildren to build the join and projected fields.
  • Projected Fields used in your query do not have to match up to the Projected Fields you have specified in the Child list. Those are a UI convenience and not used by your CAML.
  • If you want to display a value from the Child list, you need to make sure you have a Projected Field in your CAML. Only those fields which are projected are eligible to be View fields.
  • All Projected Fields will become SPFieldLookupValue objects (or perhaps SPFieldLookupValueCollection, though I haven't yet had one of my lists do this). Those lookups will always contain the ID of the child list item, but the value of the selected field within that child item.