Tuesday, August 13, 2013

Creating a custom WCF REST service for SharePoint 2013 without a Visual Studio template

Overview

Recently a colleague asked me how I built REST services for SharePoint 2013, since there is no template for this in Visual Studio 2012. While I have worked with some of the community built templates in the past, they generally require installing additional things on your development server, which really isn't necessary. You can actually stand up a REST service in SharePoint 2013 rather quickly, with just Visual Studio 2012.

Let's start with a video walking you through the process. The video follows the process outlined further down the post. If you want to view the full resolution video, you can download it from Dropbox. You can also download the code built in the video from Dropbox.



Creating the REST service

The basic process for creating your REST service are the following steps, which were demonstrate in the video.

  1. Create a new SharePoint 2013 Empty Project as a Farm Solution
  2. Add a WCF Service Application project to your solution
  3. Add the ISAPI mapped folder to your SharePoint project
  4. Drag the Service1.svc and Service1.svc.cs files from the WCF project to the SharePoint project under the ISAPI mapped folder
  5. Drag the IService1.cs file from the WCF project to the SharePoint project
  6. Add the WCF references to your SharePoint project
    • System.Runtime.Serialization
    • System.ServiceModel
    • System.ServiceModel.Web
  7. Change the namespace from WcfService1 to your assembly's namespace (e.g., MyRestService) in Service1.svc.cs and IService1.cs
  8. Build your project
  9. Using the Visual Studio Command Prompt, go to the project output folder of the SharePoint project
  10. Get the PublicKeyToken for your assembly using the strong name utility
    • sn -T MyRestService.dll
  11. In Service1.svc, change the Service attribute on the ServiceHost directive to be in the following format:
  12. <%@ ServiceHost Language="C#" Debug="true" Service="{NameSpace}.{ServiceName}, {AssemblyName}, Version=1.0.0.0, Culture=Neutral, PublicKeyToken={PublicKeyToken}" CodeBehind="Service1.svc.cs" %>
    
    • Replace the tokens here, represented by {Token} with the appropriate values for your project. e.g., 
    <%@ ServiceHost Language="C#" Debug="true" Service="MyRestService.Service1, MyRestService, Version=1.0.0.0, Culture=Neutral, PublicKeyToken={PublicKeyToken}" CodeBehind="Service1.svc.cs" %>
  13. Deploy your solution
  14. Verify the service loads
    • Note: see below for troubleshooting a common error.
  15. Add a HelloWorld operation to your contract, IService1.cs that is set up for REST.
  16. [OperationContract]
    [WebGet(ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Wrapped, UriTemplate = "HelloWorld")
    string HelloWorld();
    
  17. Implement the operation in Service1.svc.cs
  18. public string HelloWorld()
    {
        return "Hello World";
    }
    
  19. Update web.config with the appropriate info for your service
<behaviors>
    <serviceBehaviors>
        <behavior name="Service1ServiceBehavior">
            <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
            <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
    </serviceBehaviors>
    <endpointBehaviors>
        <behavior name="jsonBehavior">
            <webHttp />
        </behavior>
    </endpointBehaviors>
</behaviors>
<services>
    <service name="MyRestService.Service1" behaviorConfiguration="Service1ServiceBehavior">
        <endpoint address="" binding="webHttpBinding" behaviorConfiguration="jsonBehavior" contract="MyRestService.IService1" />
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
    </service>
</services>
  1. Test your HelloWorld operation and you should see the following:


Troubleshooting

  • If you get an error that says "This collection already contains an address with scheme http.  There can be at most one address per scheme in this collection." this is due to having multiple bindings in IIS, which is common in SharePoint when you have multiple Alternate Access Mappings.
This collection already contains an address with scheme http.  There can be at most one address per scheme in this collection.
    • In web.config, add the multiplesitebindingsenabled attribute to the servicehostingenvironment element.
    • <servicehostingenvironment aspnetcompatibilityenabled="true" multiplesitebindingsenabled="true"%></servicehostingenvironment%>
      

Monday, May 13, 2013

SharePoint 2013 Searches return no results even though content sources were crawled successfully

Recently, a client of mine using SharePoint 2013 changed the URL of their site. A couple of weeks later, they noticed that search was not returning any results. I checked in ULS and noticed some errors indicating that the Content Source had not been updated with the correct URL. I made the change in search administration and did a recrawl. The crawl found several thousand items and had no errors.

Unfortunately, we still weren't getting any search results. The problem didn't appear to be with the index, but I cleared the index and rebuilt it just to be sure. Still no results. I also noticed that there was nothing useful in ULS to help me track down the problem.

On a suggestion from a colleague, I created a new web application and associated it with the same search service application. I added some dummy content to that new web application and indexed it. When I ran a search from within that search application, I was getting results. Progress - now I knew that my search service application appeared healthy.

We decided to see if the problem was the content or the site itself. We took a backup of the content from the production web application and restored it to the new web application we had just created. We then rebuilt the index and ran a test. Search worked perfectly. So our problem wasn't the content.

Now that we had narrowed down the problem to our web application, we had a pretty easy fix. We waited for a maintenance window and then removed the web application. We then provisioned a new web application with the correct settings for our production environment and attached the content database from the original. After another rebuild of our index, we had working search results.

Friday, November 16, 2012

Difficulties with SP2013

I'm a little late to the party this time, but I've finally carved out some time to play with SharePoint 2013. I've noticed two things so far:
  1. SharePoint 2013 chews CPU a lot more than SharePoint 2010 did.
  2. The install has been flaky for me, perhaps due to point 1.
I've had several failed installs so far and they present weird errors when they do. The first failure I had wouldn't let me create managed accounts. I did a reinstall and failure number two wouldn't provision site collections correctly .

The site I created using the wizard resulted in this:


I created a new web application and then got this during the site collection creation:

That Correlation ID led to this error in the logs:

A large block of literal text was sent to sql (length 93794).  This can result in blocking in sql and excessive memory use on the front end.

I did three things that seem to have addressed the issue:
  1. Uninstalled SharePoint and deleted the 14- and 15-hives.
  2. Upped the RAM on my dev box from 4Gig to 8Gig.
  3. Reinstalled SharePoint

Monday, November 29, 2010

SharePoint 2010 - Replace missing Publishing Site Columns and Content Types

While working on a new content type that inherited from Article Page, I somehow lost a lot of Site Columns. When I tried to view the Article Page content type, it had no columns listed!

This ended up being a simple fix. Run the following command to reinstall the Site Columns and Content Types that are used by Publishing:

stsadm -o activatefeature -name PublishingResources -url http://MySharePointSite -force

Once that operation completes, the missing columns should now be back. Don't forget to fix the feature that caused the problem!

Wednesday, November 24, 2010

SharePoint 2010 - Content Deployment Woes Part 2: Custom Features

If you are reading this post, please read through my previous post on this topic to get some background on tricks to solving the "Could not find Feature" issue with built-in SharePoint features.

If you are still reading, then you have probably built and deployed custom features to your farm, such as a web part. You are then running into problems during Content Deployment jobs between farms that have different SharePoint versions on them, such as deploying from Enterprise to Standard.

The solution that worked for the built-in features was appropriate because the features that were causing the problems didn't need to be installed on your destination farm. But with custom features, that is not the case. You built them precisely because you need to be able to run them on the destination farm.

I encountered this same problem and it took me a little while to figure out why it was unhappy. Here are the steps I'd suggest you follow:

  • Check the 14-hive on the destination Web Front End to verify your feature is there.
  • Verify that your feature is installed in the destination site. For instance, check the Site Features page for features scoped to the Site level
  • Try enabling the feature and making sure it doesn't thrown any errors

Maybe you've done all those things and the feature looks good, but it's still not working? At this point, you should be confused. I know I was. I decided to check the settings on my package in Visual Studio. The Deployment Server Type was set to WebFrontEnd, which was correct. I then checked the 14-hive on the destination App Server, which is where Content Deployment jobs are run. No feature folder there.

Now I know what you're thinking - isn't that what we want? Solutions that are targeted as WebFrontEnd are marked that way for a reason - they don't need to be on the App Server because it's not serving up those web parts. Well, when it comes to Content Deployment, apparently they DO need to be there. I assume this is because the Content Deployment job is going through Central Administration, which is living on your App Server.

The solution I came up with was to enable the Microsoft SharePoint Foundation Web Application service on my App Server, effectively making it a Web Front End as well. This ensures that when I add solutions to my farm that the features get deployed to my App Server as well, effectively squashing the Content Deployment job errors I was receiving.

My App Server is already segmented off from the Web Front Ends by a firewall to keep it from being accessible from the internet. However, to ensure that my App Server is never used as a Web Front End, I am also making sure it is never listed in the load balancer and I am blocking traffic to port 80 on that box. This means there should be no real impact to the machine and my Content Deployment jobs can now run successfully.

SharePoint 2010 - Content Deployment Woes

Lately I've been working a lot with Publishing sites, which means I've been using Content Deployment jobs to move content between my farms. Unfortunately, I've learned the hard way that this part of SharePoint is rather particular about farm setups.

I have an Enterprise development farm that I use for consulting work. I built some pages on it and then wanted to deploy them to my customer's test farm, which runs a Standard license. This broke on me every single time. Specifically, I kept getting errors that features didn't exist on my destination farm.

One message I got was "Could not find feature IPFSSiteFeatures". Here are the screenshots from my deployment report:








These are all the features that existed on my Enterprise development farm but not on my Standard test farm:
  • IPFSSiteFeatures
  • WACustomReports
  • PPSWorkspaceCtype
  • PPSMonDatasourceCtype
  • PPSWebParts
  • PPSSiteCollectionMaster
To get my content deployment working again, I enabled them temporarily on the destination farm, performed the deployment, and then disabled them again. You might also be able to disable/uninstall those features on your Enterprise farm before doing the content deployment job and achieve success, but I didn't test that option.  
Note: Make sure you disable these features on your destination farm after deployment, or you may be in violation of your SharePoint license.

If you made the mistake of installing Excel Services and PerformancePoint Services on your Enterprise environment, your problems are more difficult. If you run Get-SPFeature, you will see more items in the list that will cause your content deployment to fail, such as BizAppsListTemplates.

You might be tempted to disable them on your source farm. Unfortunately, when I tried, I learned you cannot disable them with PowerShell because they are still in use. However, those features aren't activated on any of my sites. Because of my failure to disable them on the source farm, I did not test enabling them on the destination farm because I wasn't sure I'd be able to get rid of them and I didn't want to have to rebuild the entire farm again and lose my content.

I believe these features might be activated inside the service application itself, but I have not found a way to confirm this theory. I tried deleting the Service Application, but the features still couldn't be removed. If you figure this out, let me know.

Summary

In short, here are the things to keep in mind if you want to do a Content Deployment job between two farms:
  • Source and Destination farms should be on the same version of SharePoint
  • You must deploy solutions that exist on your source farm to your destination farm prior to running the job.
  • Features that are enabled on your source farm will automatically get enabled on your destination farm, but if you encounter an error with a feature, verify that the feature is installed on the destination
There are some additional quirks if you receive the "Could not find feature X" for custom features that you have developed. I'll cover those in a followup post.

    Wednesday, October 6, 2010

    SharePoint 2010 - Dynamic JavaScript problems with Inline Editing

    SharePoint 2010 allows inline editing of your HTML on Publishing pages, which is a nice boost to productivity. However, there are some quirks with this feature that can really trip you up if you aren't ready for them.

    Here's a scenario I ran into this week. I have a simple Publishing Page with a Content Editor Web Part on it. Inside this web part, there is an HTML element that I target with some jQuery to display tooltips. The JavaScript here is really quite simple and just dynamically adds a relative positioned div to the page above the target element.

    The code for this is tested and works fine outside of SharePoint. But when you edit a page, you'll find an interesting side-effect. Specially, when editing the page, the JavaScript that adds the dynamic elements to the DOM still runs. When the page is saved, even if you didn't even edit that particular Content Editor Web Part, the updated DOM elements get saved - including the tooltip changes!

    I have tested this with a lot of scenarios, but it's pretty consistent across any SharePoint components that provide inline editing.

    So, how do you allow JavaScript to dynamically update content on publishing pages? This is really going to depend on the nature of your content. Here are a few different scenarios I've employed in my site:
    • Extract the dynamic content and make it a web part.

      This works well if your content doesn't change that often and has few properties. A great example here would be having a web part that uses swfobject.js to load a flash file rather than placing the script call inside a Content Editor
    • Skip the JavaScript calls that update your page when in edit or design mode.


      This works really well for scenarios like my tooltip. You can do this either in JavaScript or C#, depending on how your page works.
    Here is an example of checking your page mode with JavaScript, using jQuery:
    if ($('#MSOSPWebPartManager_DisplayModeName').val() == 'Design' || $('#MSOSPWebPartManager_DisplayModeName').val() == 'Edit') {
            alert('Don't do anything');
        } else {
            alert('Safe to run JavaScript');
        }
    

    Saturday, July 31, 2010

    SP2010: Unable to publish workbook to Excel Services

    While trying to publish a workbook to SharePoint 2010 to use with Excel Services, I ran into some problems. Specifically, it appeared that Excel wasn't able to access my Document library.

    To publish my workbook, I followed instructions you might find anywhere on the web, using the Office 2010 Backstage to save to SharePoint. However, none of my libraries showed up by default. Not a problem! I can just type the URL into the address bar, right? Apparently Excel says that it "can't open this location using this program".

    Generally problems like this are permissions related. Well, I was logged into the machine as my farm administrator, who has full rights to the Documents library. I was also able to click on files from SharePoint and have them open Excel automatically.

    I decided to take a new approach. I opened the demo Excel workbook that came with the Business Intelligence Center template. I then made a small update and saved it back to SharePoint. Now when I opened Backstage to save to SharePoint, the Documents library showed up in the Recent Locations section.


    I was hoping by doing this, I could trick Excel into finding the location. So I then tried to save my new workbook by just clicking on that recent location. However, Excel was still not able to do it, and told me it was unable to open my site:


    After looking around on the web, I found a forum post that seemed promising, as I'm using a Windows Server 2008 R2 machine. I opened Server Manager and installed the Desktop Experience, which required me to install the Ink and Handwriting Services as a prerequisite. I was then prompted to reboot upon completion. After rebooting, I attempted to save my workbook to SharePoint again and it worked.

    SP2010: Excel Services Error - Unable to Process the Request

    I've been playing a lot with Excel Services for the last week and while it is nice, it is also temperamental. Most of this can be chalked up to inexperience on my part as I discover the closest to least privileges you can get for a SharePoint 2010 Excel Services Service Application, in light of the ever popular bug, "The workbook cannot be opened". However, I'm also convinced it's a little more particular than the previous version.

    By default when I installed Excel Services using a PowerShell script, it had an entry for a Trusted File Location at "http://". After playing with the Business Intelligence Center template for a bit, I created my own workbook that had a PivotTable and a PivotChart that talked to my SSAS installation. Naturally I decided to update my Trusted File Location settings. So I deleted the default entry and created a more specific one that pointed directly to the Documents library that came with the template. Then I checked on the Excel example that came with the template and found that Excel Services was no longer working because it was "Unable to process the request".


    Event Viewer showed nonstop critical errors and ULS had some gems in there pinning the blame on the Secure Store Service: "Request for security token failed with exception". I tried refreshing the key figuring that my WFE and App server were out of sync, but that didn't fix the problem. Lacking a better idea, I did an IIS reset on the WFE - and it started working again.

    I'm not sure why it was unhappy, but at least it was only a 10 min troubleshooting span and a simple fix!

    Monday, July 26, 2010

    SharePoint 2010 - Error Removing Managed Account

    Recently, while trying to experiment with Excel Services in SharePoint 2010, I decided to remove the service and do a reinstall with a PowerShell script. Since I like my scripts to create managed accounts as well, I removed the account that was running my service. Apparently, SharePoint didn't like this and I received an error stating that my SPManagedAccount could not be deleted because other objects depend on it when I loaded the Managed Accounts page in Central Administration. Well, not being able to load the page sort of limits my options for correcting the problem, doesn't it, Microsoft?


    I double checked and the service application had been removed from SharePoint, the Excel Calculation Service had been stopped on the App Server, and the ApplicationPool had even been removed from IIS. Looking up the CorrelationID in the logs also didn't tell me very much.

    I decided to pop open PowerShell and see what I could pull off. Turns out it was a rather simple fix. First, I looked up my Managed Account. I then tried to remove it and found out the dependency was an application pool. After removing the dependency I was about to remove the Managed Account! The PowerShell Commands to do this were:
    • Get-SPManagedAccount
    • Get-SPServiceApplicationPool
    • Remove-SPServiceApplicationPool
    • Remove-SPManagedAccount
    Here is a screenshot of the PowerShell at work:

    Thursday, June 17, 2010

    SP2010 Dispose Patterns and Broken WebParts

    Today I was building a web part for SharePoint 2010 and ran across an interesting problem. The web part was working fine for authenticated users, but was failing for anonymous users. My first thought was that Lockdown might have been the problem, but it turned out after looking through SharePoint log files that I was running into a dispose problem:

    Trying to use an SPWeb object that has been closed or disposed and is no longer valid.

    It's an interesting error message, considering that I was disposing of my object, but only after I'd finished with it. I believe the problem wasn't that my code was using a disposed SPWeb object, but rather that I had closed an SPWeb that SharePoint was relying upon.

    Here was the original code, which, according to Best Practices, was closing objects that don't need to be closed:

    using (SPSite site = SPContext.Current.Site)
    {
        using (SPWeb web = site.RootWeb)
        {
            SPList myList = web.Lists["myList"];
            //code here to find a list item
        }
    }

    And the fix was as simple as changing it to this:

    SPSite site = SPContext.Current.Site;
    SPWeb web = site.RootWeb;
    SPList myList = web.Lists["myList"];
    

    I'm not so sure this would have broken a SharePont 2007 site, but it's a good reminder to make sure you are following Best Practices for disposing objects!

    Thursday, June 3, 2010

    SharePoint 2010 Custom Error Messages for Public Facing Deployments

    It's a fairly common requirement when building a public facing SharePoint site to make sure all pages share branding elements. This includes the SharePoint error pages. While working on a publishing site, you've probably encountered an article that touches on one of the error pages, but for some reason I haven't seen one that tries to cover all of them. So hopefully this will do the job!

    Error Pages

    If you are just looking to control the basic error pages, then look no further than the SPWebApplication.SPCustomPage enumeration, which provides a list of commonly updated pages. You use the SPWebApplication.UpdateMappedPage() method to set which pages SharePoint should serve up for each of the values within the enumeration. For a good example of this, see this post.

    HTTP 404
    But what about other HTTP status codes? Well the easiest would be the 404. SharePoint already has support for a custom 404 page, as long as it is pure HTML. The default 404 is located in 14\TEMPLATE\LAYOUTS\1033\sps404.html. The quickest way to override this is to use a feature to deploy your own HTML page into the same folder. A feature receiver can then update the SPWebApplication.FileNotFound property with a relative path to your file. There are just a couple points to keep in mind when you do this:
    1. Your custom page should be greater than 512 bytes. This is because the default feature in Internet Explorer to show "friendly" error messages will sometimes ignore pages smaller than this. You can read more about this problem at Microsoft Support.
    2. Your custom page, if encoded as UTF-8, should not have a BOM. Even if you save your file as UTF-8 without BOM, editing it later in Visual Studio will add the BOM back, so be careful. You can read more about that problem here.

    If you'd like to have a custom 404 that performs server side code, such as to include web parts, you'll need to get a little more creative. There are two common ways to handle this. The first would be to have a static html page that performs a client side redirect with either a META tag or javascript. If you'd like to see that approach in action, here is an example.

    A more robust solution would be to use an HttpModule. With the HttpModule, you have two more possibilities for how you serve your page. You can use a Response.Redirect, which is what the linked article suggests. The only downside to this is that the URL changes, which may or may not be desired. Alternatively, to keep the URL, you would need to use Response.Write.

    HTTP 401
    Now that you have custom error pages for the SPCustomPage enumered pages as well as a 404 page. But what about a 401? The simplest way you might think to do this would be to edit the web.config CustomErrors element. In any normal ASP.NET application, that would have been sufficient. Unfortunately it doesn't work in SharePoint.

    Your next instinct might be to try IIS, as it provides the ability to set custom error pages. If you edit the property for the 401.2 status code and point it to a custom HTML page on your site, it may or may not work. In testing, it turns out that having Anonymous Access enabled in SharePoint (which then sets it in IIS) prevents a custom 401.2 page from being used. However, if Anonymous Access is disabled, such as with an Intranet site, then the custom page will show just fine. Since we are talking about a public facing deployment you are probably using the Publishing template, so you're going to need a different approach.

    The trick for a custom 401 message when you have Anonymous Access enabled is using a custom http module, much like you could have done for the 404.

    Here is an example:

    public class CustomPageMappingsHttpModule : IHttpModule
    {
     private const string Custom401File = "/_layouts/1033/Custom401_CSS.htm";
    
     private HttpApplication _application;
    
     public void Dispose()
     {
     }
    
     public void Init(HttpApplication context)
     {
      this._application = context;
      this._application.PreSendRequestHeaders += new EventHandler(_application_PreSendRequestHeaders);
     }
    
     protected void _application_PreSendRequestHeaders(object sender, EventArgs e)
     {
      HttpResponse response = this._application.Response;
    
      if (response.ContentType.Equals("text/html", StringComparison.CurrentCultureIgnoreCase))
      {
       string message = string.Empty;
    
       switch (response.StatusCode)
       {
        case 401:
         message = File.ReadAllText(this._application.Server.MapPath(Custom401File));
         this._application.Response.Clear();
         this._application.Response.Write(message);
         break;
       }
      }
     }
    }
    

    Once you hook that up in your web.config, go ahead and try to load http://YourSite/_layouts/settings.aspx. You should get the same authentication prompt you'd expect for an anonymous user, but if you hit cancel you'll now see your custom 401 message. Note that the way you register an HttpModule in 2010 is slightly different. If you add the module to the httpModules element in web.config, it may not work. In testing, I was only able to get my module to work by adding it to the modules element with the other SharePoint HttpModules.

    As a final level of customization, you could consider having no prompt at all on your public site when users request restricted content. The way to do this is to extend the site and have a private site as well. Since we are just talking about two different web sites in IIS (with corresponding settings and web.config files), you can control their authentication and error settings independently while still serving up the same content. The public site could then have Anonymous Access enabled and Windows Authentication disabled. This removes the login prompt when a 401 occurs on the public site. The private site would have Anonymous Access disabled but Windows Authentication enabled, allowing users to log in and manage content as needed.

    Quirks
    Since you are probably using the Publishing template, it's worth mentioning the Lockdown feature. This feature stops users from being able to see your Form pages. It also locks down lists and libraries, such as the Style Library. So make sure if you use CSS in your custom error pages that you know whether you have lockdown enabled and plan locations for your assets appropriately.

    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.

    Thursday, April 29, 2010

    SP 2010 - Phonetic Search Only for People Scope?

    I have a custom scope I've created that I am querying against with the FullTextSqlQuery object. Recently I was asked if we could allow a phonetic search. With all the hype around this new feature in SharePoint 2010, I thought it would be very easy.

    I looked, and sure enough, there is an EnablePhonetic property right on FullTextSqlQuery. I set it to true, ran my search and got back...nothing. I figured maybe it didn't like my query, which had a LIKES keyword. That wasn't it. I tried looking on the web and it seems all the mentions of phonetic search seem to be closer to press releases than coding snippets.

    Finally I found this nugget on MSDN:
    For FAST Search Server 2010 for SharePoint, this property is only applicable for People Search.
    Well, I'm not using FAST, but I am using a custom search scope. It would appear that might be the limiting factor here. While I am searching for people, they are stored in a custom list, since they are not members of my SharePoint site. Therefore I was using a custom scope to get to them.

    It would seem phonetic search is not available yet to customize in this way.

    Wednesday, April 28, 2010

    SP 2010 - Configure and Use a TaxonomyWebTaggingControl

    If you are using the Managed Metadata Service and developing a custom web part, you may be interested in using that nice term picker that you see when you create a new list item that contains a Managed Metadata Column.

    First, let's cover the bare minimum code you'll need to get the term picker to show up in your web part. First you'll want to add a reference to Microsoft.SharePoint.Taxonomy to your project. Then you'll need a Register directive in your ascx, like this:

    <%@ Register Tagprefix="Taxonomy" Namespace="Microsoft.SharePoint.Taxonomy" Assembly="Microsoft.SharePoint.Taxonomy, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> 
    

    Now you can add the picker to your web part just like any other control.

    
    

    You'll notice that I didn't set any properties on the TaxonomyWebTaggingControl. Well there are three properties you'll need to set, but we'll do it programatically. Those properties are:
    • SSPList - This property parses your string and builds a List to set the SspId property
    • GroupId - A Guid
    • TermSetList - This property parses your string and builds a List to set the TermSetId property

    Now, you can set these properties declaratively if you wish, but it won't be a very portable web part, as the Guids aren't part of the import CSV format for the Managed Metadata Service, nor do any of the Taxonomy Create methods allow setting of Ids. Because of this, it's best to have your web part set these properties on the TaxonomyWebTaggingControl programatically. If you just want to see how you would go about setting these properties declaratively, scroll to the bottom of this post, as I've included it for reference.

    In order to make your web part portable, you're going to need to query the Managed Metadata Service and get these values yourself. Moreover, you're going to have to do it on every load of your web part since these properties are not stored anywhere between page loads.

    Note: If you only set these properties on page load, this will work as long as there are no scenarios where you need to redraw the control again, such as having a custom server validator. In that instance, your picker would work before the postback, but the look-ahead feature would be broken after the postback.

    In order to make loading our properties a little easier, here is an extension method for TaxonomyWebTaggingControl that allows you to quickly set the properties. The path part of this was inspired by PHolpar. Also, remember that extension methods must be defined in static classes.

    public static bool Configure(this TaxonomyWebTaggingControl twtc, string termSetPath)
    {
     bool configured = false;
    
     TaxonomySession session = new TaxonomySession(SPContext.Current.Site);
    
     string[] parts = termSetPath.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
     if (parts.Length > 0)
     {
      TermStore termStore = GetTermStore(session, parts[0]);
      if (termStore != null && parts.Length > 1)
      {
       twtc.SSPList = termStore.Id.ToString();
    
       Group group = GetGroup(termStore, parts[1]);
       if (group != null && parts.Length > 2)
       {
        twtc.GroupId = group.Id;
    
        TermSet termSet = GetTermSet(group, parts[2]);
        if (termSet != null)
        {
         twtc.TermSetList = termSet.Id.ToString();
         configured = true;
        }
       }
      }
     }
    
     return configured;
    }
    

    In order to load MyTermSet, which lives within MyGroup, which resides within MyTermStore, you will use this extension method similar to the following:

    protected void Page_Load(object sender, EventArgs e)
    {
     bool configured = twtcSpecialty.Configure("MyTermStore/MyGroup/MyTermSet");
     if (!configured)
     {
      throw new ApplicationException("Unable to find target TermSet");
     }
    }
    

    The result looks just like the picker from other parts of SharePoint, with functioning look-ahead.


    As promised, here is the markup if you want to configure your TaxonomyWebTaggingControl declaratively.

    
        0d8382cf-2d63-4421-a37a-b9386d0be5c8
        7adeced1-b73e-47dc-b695-82bb28e2f48f
    
    

    Tuesday, April 27, 2010

    SP 2010 - Randomize the order of your search results from FullTextSqlQuery

    I had a need recently to perform a query with FullTextSqlQuery but to limit the results and then display them in a random order. The FullTextSqlQuery class does support a RowLimit property, though this would not work in my case because I didn't want to get the exact same answers everytime.

    Remembering that LINQ to SharePoint often requires Two-Stage Queries, I decided to employ a similar approach here and pull back the data I could then perform more culling server side.

    DataTable searchResults = PerformSearch(query, queryRowLimit);
    if (searchResults != null && searchResults.Rows.Count > 0)
    {
     int limit = 10; //todo: this should be a webpart property
     var results = (from row in searchResults.AsEnumerable()
           orderby Guid.NewGuid()
           select row).Take(limit);
    }
    

    The PerformSearch method is just a standard setup for using FullTextSqlQuery to return a DataTable. Note that I limit my resultset as much as I can with the FullTextSqlQuery.QueryText property to try to avoid hitting a throttling exception.

    With the limited results returned, I then want to randomize the order of the records. LINQ allows us to do this the same way we would in SQL, and I just order by a random Guid. Once the results are randomized, we just grab the number of records we need with the Take() extension method.

    Friday, April 23, 2010

    SP 2010 Managed Metadata TermSet.CreateTerm Throws Error

    I've been working on importing a set of Terms into my Managed Metadata Term Store from a 3rd party database. However, I ran into a snag. When I execute the following code, I get an error, "There is already a term with the same default label and parent term."

    public static void CreateTermIfNotExists(TermSet termSet, string termName)
    {
     if (termSet != null && !string.IsNullOrEmpty(termName))
     {
      Term term = null;
    
      //This throws an exception if the Term doesn't exist
      try
      {
       term = termSet.Terms[termName];
      }
      catch { }
    
      if (term == null)
      {
       Term t = termSet.CreateTerm(termName, 1033);
       termSet.TermStore.CommitAll();
      }
     }
    }
    

    I attached my debugger and found out that even though I was checking for my term, the code would say it wasn't there, even though it was! The problem, was that the specific term causing me problems had an ampersand. The term name I supplied was "Foo & Bar", but the value put into the TermStore actually contained unicode version of the ampersand.

    Looking through the documentation, I found this relevant comment:

    The name value will be normailized to trim consecutive spaces into one and replace the & character with the wide character version of the character (\uFF06). The leading and trailing spaces will be trimmed. It must be non-empty and cannot exceed 255 characters, and cannot contain any of the following characters ; "<>|&tab.

    That was indeed the behavior I was seeing. Thinking I had just found a limitation of the TermSet.Terms collection, I changed my code to this:

    public static void CreateTermIfNotExists(TermSet termSet, string termName)
    {
     if (termSet != null && !string.IsNullOrEmpty(termName))
     {
      Term term = null;
    
      TermCollection tc = termSet.GetTerms(termName, 1033, true, StringMatchOption.ExactMatch, 1, false);
      if (tc != null && tc.Count > 0)
      {
       term = tc[0];
      }
    
      if (term == null)
      {
       Term t = termSet.CreateTerm(termName, 1033);
       termSet.TermStore.CommitAll();
      }
     }
    }
    

    I ran again, and this time instead of blowing up on just that one case, my code went crazy trying to insert a bunch of different terms that were working fine before and throwing way more exceptions. A little research on this method led me to this post, which suggests the TermSet.GetTerms method does not work in Beta 2, which seems to be what I just discovered as well.

    I decided to explore the reference to normalizing term names from the MSDN link. My final pass at the code became:

    public static void CreateTermIfNotExists(TermSet termSet, string termName)
    {
        if (termSet != null && !string.IsNullOrEmpty(termName))
        {
            Term term = null;
    
            try
            { 
                string normalizedTermName = TermSet.NormalizeName(termName);
                term = termSet.Terms[normalizedTermName];
            }
            catch { }
    
            if (term == null)
            {
                Term t = termSet.CreateTerm(termName, 1033);
                termSet.TermStore.CommitAll();
            }
        }
    } 
    

    Finally, success! Moral of the story - when you query your TermSet for a specific Term you need to normalize your name first, because that is how it will be stored internally.

    Monday, April 19, 2010

    SP 2010 - Using LINQ to SharePoint to Find List Items with Specific Managed Metadata Terms

    Now that I can effectively use LINQ to access my Managed Metadata Columns, I'd like to only pull back those columns that contain values I need. For single valued Managed Metadata Columns, this is very straightforward:

    var examples = from d in context.Examples
          where d.Specialty is TaxonomyFieldValue && ((TaxonomyFieldValue)d.Specialty).Label == "Value One"
                   select d;
    

    For multi valued Managed Metadata Columns, my first attempt was a bust. I tried the following expression, but received a compiler error, "An expression tree may not contain an anonymous method expression".

    var examples = from d in context.Examples
                   where ((TaxonomyFieldValueCollection)d.Specialty).Exists(delegate(TaxonomyFieldValue tfv){
                 return tfv.Label == "Value One";
                   }) != null
          select d;
    

    The hint here from the compiler is that I can do this as long as I don't use an anonymous method. So I created a method to test for the term I wanted and changed my expression to call this method.

    private static bool ContainsMetadataTerm(object o, string termLabel)
    {
     bool exists = false;
    
     if (o is TaxonomyFieldValueCollection)
     {
      TaxonomyFieldValueCollection tfvc = (TaxonomyFieldValueCollection)o;
      exists = tfvc.Exists(delegate(TaxonomyFieldValue tfv)
      {
       return tfv.Label == termLabel;
      });
     }
    
     return exists;
    }
    
    var examples = from d in context.Examples
          where d.Specialty is TaxonomyFieldValueCollection && ContainsMetadataTerm(d.Specialty, "Value One")
          select d;
    

    Now I can query specifically for list items that use specific terms. Keep in mind that this filtering does not happen until after the list items have been pulled down, so you may want to do some additional filtering to narrow the results so you don't run afoul of the new Throttling feature of SharePoint 2010.

    Here was the CAML generated by the last LINQ query, which shows that the additional filtering for Term was done after the records were pulled.

    
      
        
          
            
            0x0100
          
        
      
      
        
        
        
        
        
        
        
        
        2147483647