# Tuesday, August 17, 2010

Sharepoint OWA Webparts Prompting for Username And Password

If you want to use OWA webparts with Integrated Authentication or Basic and have it default to the user that is logged on, the steps are simple - leave the username blank (see below).

But if you have done that you may be surprised to find that it continually prompts you to log in on that page EVEN THOUGH YOU ARE ALREADY logged on.

Not putting in the info gives you a beautiful 401 error: 

Luckily the resolution is simple.

What you want is that you will be logged on to the mail server with the current user name and password. But if you open up the security settings on your browser you will find out that this is not the option.

There are two ways to resolve this - both involve changing the setting. You can either change the zone that it defaults to (ie make Local Intranet always do 'Automatic logon with current user name and password') or you can put the mail server in the Trusted Zones.

I like the latter.

Go into the Securities tab, click on 'Trusted Sites' icon and then click on the 'Sites' button.


Once you are in there you can add the FQDN of the mail server (since you will be using its cert)...

Voila!!! 

Note: if you want to do this on a larger scale I would suggest you turn to your handy-dandy Group Policy editor. You can find what you are looking for User Configuration / Windows Settings. I will leave the rest of the steps up to you...

# Friday, July 31, 2009

Anonymous Form Submissions to Sharepoint 2007, or another MOSS issue on the Internet

I was exceedingly excited to think about using Sharepoint 2007 (MOSS to some) as our Internet facing site. I had written the code for our previous site in Cold Fusion over the a few years and was looking forward to laying down that burden...
 
MOSS seemed to have almost everything we did, plus a whole lot more. Imagine my surprise when I discovered that as an Internet-facing site it leaves a lot to be desired (and that at $40k). Now imagine me staying up until 2am for a few nights running trying to find solutions. Now imagine me blowing milk out my nostrils... maybe not...
 
For a variety of reasons (which I will not go into here) I selected a "Publishing Site with Workflow". Those of you who have worked with MOSS know that this automates LOCKDOWN on all Lists so that Anon users can not view them. What they don't tell you is that no matter what you do, even if you give them permissions to VIEW the list, they can not ADD to the list.
 
Now this is a problem because one of the reasons (among many) that I chose Sharepoint was their integration with Infopath to easily create and publish forms. Now I was discovering that anonymous users could pull up the form, they just could not submit it. Unless, of course, we allowed them access to all the forms. The reason seems to be tie back into Sharepoint's rather complex permission schemes. There are actually three areas that need to be checked for permissions, sort of like three distinct committees. Each has a stranglehold on one area and one type of connection. Since Sharepoint does not recognize that there can be a variety of Anonymous users, and it can not distinguish them, it becomes all or nothing.
 
I have tried a number of solutions - note these rather creative solutions -

1 - http://kwizcom.blogspot.com/2007/06/anonymous-users-cannot-access-list.html. Which does not work for submitting but does allow viewing. Note the steps on "unlocking", "set permissions", "lock". Not easy or fun...

2 - Alternately you can use email -> http://www.click2learn.ch/blog/Lists/Posts/Post.aspx?List=6b8a723c-02e0-48bb-a075-8f9eb21dbfbe&ID=13 which basically means they can fill out the form, but not submit it the library.

3 - My favorite is this one -> http://www.sharepointblogs.com/ervingayle/archive/2006/10/13/enabling-anonymous-users-to-open-and-submit-data-via-infopath-forms-published-to-sharepoint-2007.aspx WHICH DID NOT WORK FOR ME!!!!!!!!!!!!! However, it does display the really cool thing about changing the querystring from DOCLIB to LIST. Who woulda thunk? If only it worked...
 
So, I am desperately asking, WHAT DO I DO????
 
You can always use surveys (which won't work for a LOT of things), or you can do some ninja-backdoor-coding, which I found on this amazing site for you -->
http://www.paylasimnoktasi.com/en/anonymousinfopathforms.aspx
 
Basically you must
  1. setup a separate IIS Web App running a Webservice (it does not need to be exposed externally)
  2. Write a webservice to handle this (it will use identity impersonation and the app pool account to convince the List that you really ARE someone).
  3. Muck with InfoPath forms to pass the necc data to the webservice when submitting to it.
I must admit I probably would never have thought of this - so big thanks to Nezih Tinas! I love techies on the web!!!
 
So here (as an example) is my very simple webservice code
 
<%@ WebService Language="C#" Class="AnonFormSubmission" %>
using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Security.Principal;
using System.IO;
using System.Text;
using Microsoft.SharePoint;
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class AnonFormSubmission  : System.Web.Services.WebService {
    [WebMethod]
    public void SubmitToFormLibrary(string siteName, string webName, string formLibraryName, string formXml)
    {
        WindowsImpersonationContext wic =
        WindowsIdentity.GetCurrent().Impersonate();
        string formName = Guid.NewGuid().ToString();
        using (SPSite site = new SPSite(siteName + "/"))
        {
            site.AllowUnsafeUpdates = true;
            using (SPWeb web = site.OpenWeb(webName))
            {
                SPFolder folder = web.GetFolder(formLibraryName);
                foreach (SPFile file in folder.Files)
                {
                    if (file.Name.Replace(".xml", "") == formName)
                        throw new Exception("File name exists.");
                }
                folder.Files.Add(formName + ".xml", UnicodeEncoding.UTF8.GetBytes(formXml));
                web.Dispose();
            }
            site.Dispose();
        }
        wic.Undo();
    }
}
 
Note 1 - If you are tweaking this, remember to either use
Dispose your Web and Site objects or do the using container (which Disposes of them for you). Otherwise you will start hemorraghing memory. I am paranoid and do both. Incidently Disposing will automatically Close.
 
Note 2 - I use a random GUID to create the Form name because it must be unique, but as long as you make sure it is unique you should be good to go.
 
Note 3 - You will need to tweak your web.config (at least I did) to include a username/password that has permissions. This does not need to submit to the web app extension that is Internet facing (I submit it to the root app which using NTLM since it all goes into the same list and then use an NTLM account that I know has access). Ex:

<compilation debug="false">
    <assemblies>
        <add assembly="Microsoft.SharePoint,
           Version=12.0.0.0, Culture=neutral,
           PublicKeyToken=71E9BCE111E9429C"/>
        </assemblies>
    </compilation>
<authorization>
<allow users="?" />
</authorization>
<identity impersonate="true"
      userName="myDom\myUser"
      password="mypassword" />
<authentication mode="Windows"/>
 
This should enable you to submit to that list as myDom\myUser. You can encrypt the web.config to be paranoid. Remember, paranoia is not a problem in IT, it is a job requirement.
 
You can follow Nezih's directons for creating the infopath form. I should note that this will have to be an administratively approved form.
 
WAIT!!! You're not done!!!
 
What you then need to do is go into the form library that you want to submit it to and set this up as the default form. Then you can use all these nifty fields in whatever view you want!!! Plus you have to modify the form library itself to not launch it as Infopath. Then you will want to grab the URL. O, and DON'T FORGET TO CHANGE THE TIMEOUT SETTINGS FOR INFOPATH!!!

# Tuesday, July 28, 2009

Msoft AddRule example is Incorrect

I could not find a mention anywhere that with SP1 you still need to use Addrule.exe for Forms based authentication crawls. I have hunted and hunted to verify, but yes, Virginia, it appears you still do need to still use Addrule.exe with its XML.
 
(Don't know what I am talking about -check it out here --> http://technet.microsoft.com/en-us/library/bb852172.aspx)
 
Incidently - this whole mess with not being able to crawl FBA sites and having to create a specific trimmer is another point demonstrating how it appears that Microsoft's inclusion of the "internet facing site" FBA site in MOSS was an afterthought.
 
Suffice to say, not merely is there no documentation pointing out that you STILL NEED TO USE ADDRULE, but the sample XML is wrong.
 
Here is the INCORRECT XML sample
<rules>
  <rule>
    <path>http://YourFormsAuthSite/*</path>
    <type>FORM</type>
    <error_pages>
      <error_page>Logon.aspx</error_page>
    </error_pages>
    <auth_url>Logon.aspx</auth_url>
    <login_type>POST</login_type>
    <parameters>
      <param name="__VIEWSTATE">dDw0OTQzMjI0MjQ7O2w8UGVyc2lzdDs%2BPvhWhKKTnHpM3RIvgkgC9jJVpN%2Bg</param>
      <param name="Login1%24UserName">FormsAuthUserName</param>
      <param name="Login1%24LoginButton">FormsAuthPassword</param>
      <param name="Login1%24LoginButton">Log+In</param>
    </parameters>
  </rule>
</rules>
Here is a CORRECT XML sample
<rules ssp="SharedServices for MyServer">
 <rule>
  <path>http://www.myserver.com/*</path>
  <type>FORMS</type>
  <auth_url>http://www.myserver.com/_layouts/login.aspx?ReturnUrl=/</auth_url>
  <login_type>POST</login_type>
  <error_pages>
   <error_page>login.aspx</error_page>
  </error_pages>
  <parameters>
   <param public="true" name="__VIEWSTATE">%2FwEPDwUKMTc0NDQ2ODkFgJmD2QWAmYYCAgMPZBYCAjU
PZBYCAgEPZBYCZg9kFgICDQ8QDxYCHgdDaGVja2VkaGRkZGQYAQUeX19Db250cm9sc1JlcXVpcmVQb3N0QmFja
0tleV9fFgEFJmN0bDAwJFBsYWNlSG9sZGVyTWFpbiRsb2dpbiRSZW1lbWJlck1lYuMscsbPMGpHkju7j4uv5Gv%2BR
ds%3D</param>
   <param public="true" name="ctl00%24PlaceHolderMain%24login%24UserName">myFBASearcherName</param>
   <param public="true" name="ctl00%24PlaceHolderMain%24login%24password">myFBASearcherAccount</param>
   <param public="true" name="ctl00%24PlaceHolderMain%24login%24login">Sign+In</param>
   <param public="true" name="__EVENTVALIDATION">%2FwEWBQLxxc7nDwLE96mtBQLLtsPBAgLkkP7MCgK%2FlZyy
Bxv%sdf2B3qFhTCz8CUMXQiMVw</param>
  </parameters>
 </rule>
</rules>
 
What are the differences?
  1. They have incorrect fieldnames (missing the Placeholders) and note the repetition of LoginButton for the Password portion of the XML demo. Obviously someone simply cut and pasted sections rather than pasting a complete, correct, XML
  2. They do not have __EventValidation. Testing this with Fiddler emphasized the need for that
  3. They do not have public="true" in their params which can be useful - public: If this value is not present, the parameter specified will be encrypted and stored in the search system. For encrypted parameters, the size limit is 1,024 characters. If you specify public = "true", the parameter will not be encrypted before storing in the search system. Also, the parameters size limit increases to 4,096 characters. 
Other Caveats
# Thursday, June 11, 2009

Installation Error if Diagnostics Log location moved...

I was installing a new Sharepoint Server in the farm, mostly because the currently had was too small.

I ran into errors with the Office Server Web Services (it kept saying it could not find the path, although the path to that website was fine (I checked it three times). The Root virtual directory could not be found...

I tried many things. Finally I ran the Office wizard to remove the new server from the farm. I then ran it again to reinstall it (after making sure that various dbs had the Sharepoint Server as a user - see previous log). And I got the error below:

The error in the log informed me that I it was missing the location of the Diagnostics Logs that I had moved to the E: drive on the old, slow server. So I moved the Diagnostics logs to the D: drive before running the wizard to try to join the farm again. No dice. Same error. I moved the logs back to there original locale in the 12-spot. No dice.
 
I try renaming the D: drive to E: - it refuses.
 
On the point of uninstalling/reinstalling MOSS on the new server I hesitate...
 
Searching through Regedit in the NEW server (not currently on the Farm) I find an entry in HKLM\Software\Microsoft\Shared Tools\Web Server Extensions\12.0\WSS called LogDir. And guess what is in it? You got it - a reference to the entry on the E: drive...
 
Manually changed the regedit entry to match to the new d: location (on both servers) and VOILA!!!
 
So be warned - if you move the Diagnostics Logs location in a farm, a folder must be available on ALL servers (makes sense).
 
And the Office Server IIS site is gone. Probably a carryover from before...

# Tuesday, June 02, 2009

Cannot open database requested in login 'SharedServicesDatabase'. Login fails. Login failed for user MYDOMAIN\MyServer$

You may get an error like:
 
Cannot open database requested in login 'SharedServicesDatabase'. Login fails.
Login failed for user ' MYDOMAIN\MyServer$'.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.Data.SqlClient.SqlException: Cannot open database requested in login 'SharedServicesDatabase'. Login fails.
Login failed for user ' MYDOMAIN\MyServer$'.
 
This is triggered by the odd fact (which I can not find documented anywhere) that the server that is housing the Sharepoint site must have its computer account listed as a user within the Shared Services database for the search to function.

# Monday, June 01, 2009

Note to self - watch out for ISA 2006 Listeners and Sharepoint AAM

I had setup AAM w/ Sharepoint and our ISA servers. What had not occured to me was that the Connection Verifier was tied to port 80 (I had not put it somewhere else). So when I removed that site *boom*.
 
Changed ISA Verifier to the correct port and it works fine...

# Thursday, May 28, 2009

Unable to add selected web part(s). Cannot import this Web Part.

Recently ran across this trying to install a customized webpart. Starting going over everything with a fine tooth comb. Even mocked up a .wepbart xml to get it to run.
 
Finally ran it in VS2005 on a 'test' site - turns out I had forgotten to add PUBLIC to the class so that it could be accessed.
 
ooops. won't do that again. actually I probably will...
 
;-)
 
Once again, another verbose Sharepoint Message...

# Wednesday, May 27, 2009

Encrypting your Web.Config on an FBA Site

I was being a good boy *SHOCK* and encrypting my web.config sections on my MOSS server like I was supposed to. Except...
 
Except...
 
It worked like a charm on the Windows authentication side, but the FBA side was all wacky. Throwing:

Failed to decrypt using provider 'MossCustomKey'. Error message from the provider: The RSA key container could not be opened.

So what to do? After a bunch o hunting I kept coming across this issue - it is permissions - need to allow the account to have permissions.
 
This is easly accomplished using the aspnet_regiis -pa "MyCustomKey" "AccountToGiveAccess" command line. But what account?
 
I tried "Nt Service". I tried "ASPNET". I tried the Application Pool account. But here is the clincher, because this allows Anonymous access, the account is whatever you have the Anonymous Account set to in IIS - EVEN AFTER YOU HAVE LOGGED ON USING FBA!!!!
 
This comes from the <identity impersonate='true' /> line in the web.config and how Sharepoint uses it. Note that you COULD set a specific impersonate there, but I am not sure you really want to (although I may experiment more in my quest to enable Sharepoint to assign anon permissions at a page, not subsite, level).
 
So what was the account - simply the %machinename%\IUSR_%machinename% account...
 
Remember to do this on ALL your servers on the farm... (as well as exporting the XML!)
 
***** FURTHER INFO *****
 
I appear to be running into a problem with my Intranet access and encryption. Occasionally it will throw the error above almost as if it has lost its ability to authorize people. Have to iisreset, and sometimes reboot, in order to resolve. Perhaps trouble with enumerating Domain Users group membership?
# Tuesday, May 26, 2009

Sharepoint 2007 Properties Snippet

Here is a snippet I created that I like to use when defining properties for webparts. Have fun with it...

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
   <CodeSnippet Format="1.0.0">
      <Header>
         <Title>Sharepoint WebPart Property</Title>
         <Shortcut>wpprop</Shortcut>
         <Description>Code snippet for Sharepoint Webpart Property.</Description>
         <Author>Matthew Mcknight</Author>
         <SnippetTypes>
            <SnippetType>Expansion</SnippetType>
         </SnippetTypes>
      </Header>
      <Snippet>
         <Declarations>
            <Literal>
               <ID>type</ID>
               <ToolTip>Property type</ToolTip>
               <Default>string</Default>
            </Literal>
            <Literal>
               <ID>property</ID>
               <ToolTip>Property name</ToolTip>
               <Default>MyProperty</Default>
            </Literal>
            <Literal>
               <ID>field</ID>
               <ToolTip>The variable backing this property</ToolTip>
               <Default>_myProperty</Default>
            </Literal>
            <Literal>
               <ID>personalizable</ID>
               <Type>System.Web.UI.WebControls.WebParts.PersonalizationScope</Type>
               <ToolTip>Personalization Level for this webpart?</ToolTip>
               <Default>PersonalizationScope.Shared</Default>
            </Literal>           
            <Literal>
               <ID>browsable</ID>
               <Type>System.Boolean</Type>
               <ToolTip>Is this webpart browsable?</ToolTip>
               <Default>true</Default>
            </Literal>           
     <Literal>
               <ID>description</ID>
               <ToolTip>The description for this webpart.</ToolTip>
               <Default>My Description</Default>
            </Literal>
            <Literal>
               <ID>defaultValue</ID>
               <ToolTip>The default value for this property.</ToolTip>
               <Default></Default>
            </Literal>
     <Literal>
               <ID>category</ID>
               <ToolTip>The category to display for this webpart in the webpart editor.</ToolTip>
               <Default>My Category</Default>
            </Literal>
     <Literal>
               <ID>friendlyName</ID>
               <ToolTip>The friendly name to display for this webpart.</ToolTip>
               <Default>My Friendly Name</Default>
            </Literal>
         </Declarations>
         <Code Language="csharp"><![CDATA[
private $type$ $property$;

[Personalizable($personalizable$)]
[WebBrowsable($browsable$)]
[Description("$description$")]
[DefaultValue("$defaultValue$")]
[Category("$category$")]
[FriendlyName("$friendlyName$")]
public $type$ $property$
{
 get { return $field$; }
 set { $field$ = value; }
}
$end$]]>
         </Code>
      </Snippet>
   </CodeSnippet>
</CodeSnippets>

# Friday, May 22, 2009

Curse you PublishingPageLayout!!!!!! Or the discovery of a wonderful "feature" of Sharepoint 2007

*** Please Note that this is done on a "publishing" site, it may be that this issue (I mean 'feature') does not occur on other types of sites. But I doubt it.
 
For those of you unfamiliar with Sharepoint, it combines Master Pages (for overarching look and feel or every page in the site) with Page Templates (which help define how a specific page should be layed out but not the content itself). One way to think of it is that each page in Sharepoint has three levels:

  1. The master page is the highest level and applies to all pages (unless overriden by a page or subsite)
  2. The page template layout determines what 'type' of page (ie web parts page, or article page, or welcome page, or ...)
  3. The actual page content.

Remeber that Master Pages can have parent Master Pages. Page Template Layouts are not considered Master Pages by Sharepoint, but for those of you familiar with the concept of parent/child Master Pages, Page Template Layouts act in the manner similar to a Child Master Page. 

Using Page Template Layouts works wonderfully on Sharepoint unless you have ...

  • more than one server, or ...
  • at least one extension to the server, or ...
  • you move to a new server, or ...
  • you move files from one server to another (via publishing, Sharepoint Designer, manual, etc).

In other words, unless you have a single url to use which never, ever changes, you may be in for trouble.
 
Why? Well it's all relative. Or more specifically it is NOT relative, it is absolute.
 
Basically whenever you create a page it prepends the page layout template section with the URL that you created the page on, turning it into an absolute reference. In other words if you take a look at your page (ex by downloading a copy) you will find that there is a section that reads like this:
 
<mso:CustomDocumentProperties>
... stuff here
<mso:PublishingPageLayout msdt:dt="string">http://theurlthatyouused/_catalogs/masterpage/PageFromDocLayout, Article page with body only</mso:PublishingPageLayout>
... more stuff here
</mso:CustomDocumentProperties>
 
Note that it has prepended the url, whether it be interal - http://myserver:8080 - or external one - http://www.myfriedmind.com.
 
So what does this mean for you? I have good news and bad news.

  • The good news: even if the URL of the page is different (eg calling the page from http://myserver:9090 and not http://myserver:8080 if you extended your site) the page will still show up. Even though the absolute url is different it still pulls it from the database as if it was a relative url. Strange but true.
  • The bad news: You will not be able to edit the page settings from other than the original URL.

If you do, expect this to appear:


 
WHA????????????? Where did that come from? Did someone else modify it? Nope.
 
After choosing Exit Without Saving you might get it to work. Maybe.
 
You might also get this error if you have a webpartpage and decide to edit one of the webparts. Keep clicking on choices until one of them works...
 
Does this remind anyone of being lost in the caverns of Adventures of Zelda? You are in a maze of twisty little passages, all alike...
 
The best is if you try to modify the Page Settings on such a page...

*BOOM*

What has happened is that Sharepoint is looking at the PageTemplateLayout property of the page (which is an absolute reference). If the Server Name and/or Port that you are editing from do not match that exactly then Sharepoint assumes that someone has modified the page between when you started editing and when you try to save it.

Again, one of the ways that Sharepoint tries to determine if your copy is 'dirty' (ie someone has made a change between when you first tried to edit and now) is that it looks at the PageTemplateLayout property. If there is not an exact match it rejects it. If you try to modify the Page Settings it explodes.

  • Scenario #1 - you copy a page from a different URL using Sharepoint Designer - it simply copies the file, including the now INCORRECT URL.
  • Scenario #2 - You are moving your sharepoint to a bigger, faster server (or you are restoring). The new server has a different name...
  • Scenario #3 - You are publishing the pages out to a different front end server but want to be able to edit it from there...
  • Scenario #4 - you are extending a web app to be used on the internet, and you want both internal and external people to be able to edit the same pages, but using different URLs...

etc, etc, etc...
 
What is the solution? Well, there are a couple. One is to use this nifty addition to STSADM - http://stsadm.blogspot.com/2007/08/fix-publishing-pages-page-layout-url.html. Gentleman Gary has done a wonderful job creating a flexible way to repair these beasties. The only caveat - it is an incomplete fix. Basically it still uses an absolute URL, it just replaces the wrong one with the correct one (see his note at the bottom in response to my question). It will fix #1 and #2 above (although if you move it again you will have to rerun it again), but it does nothing for #4. I am not sure about #3 <g>.

Again, note that this does not fix the issue with multiple access, only where a page has been moved. However, if it is simply a matter that you have moved your pages to a different server than this is a pretty rocking solution.
 
I have to deal with #4 after doing #2, so what do I do?

Initially I personally download each file as I uncovered the issue and manually removed the absolute url from the text and replaced it with a relative url and then uploaded the file. I expected a handful to show up, but the number kept increasing. In addition it was a pain for our staff. Sometimes I could spot it after they had made a non-blowing up modification by checking the nvarchar14 in the content database (Select tp_id, nvarchar14 from AllUserData Where nvarchar14 LIKE 'http://%') , but sometimes I would only catch if when they called me to complain.

Btw, if you are thinking that you can just modify the nvarchar14 instead of extracting and modifying the page, it does not work. It will be overwritten once another change is made to put it back to the absolute url. Trust me.

My solution ultimately was to write some code in c# that:

  1. extracted each page
  2. checked it out
  3. inspected the PageTemplateLayout text for an absolute URL
  4. replaced it with a relative URL if that was the case
  5. uploaded the page
  6. checked in the page
  7. published the page
  8. approved the page
I may publish that code in another blog on the off chance anyone is curious about how to do such a thing. This solves Scenario #2 and #4 for me.

Long term? Microsoft???? 

Maybe this is part of the design, or maybe they just missed this. I am just hoping they will fix it. There appears to be absolutely no loss of functionality with a relative reference and even thought there *might* be a case where a page template is housed on another site (and hence a different URL) this is far too serious an issue to sweep under the carpet.
 
I will enter more on another PageTemplate issue in another blog...

# Monday, May 18, 2009

Sharepoint Backup Permissions or SqlException: Cannot open backup device '\\myserver\backups\spbr0000\00000024.bak'. Device error or device off-line

For those of you who are using Sharepoint's 'built-in' backup feature, you probably already know that one of the biggest problems with it is that you can not automate it directly. In order to do that you need to used AT ->
AT 01:00 /every:1,2,3,4,5,6,7 cmd /c "stsadm.exe -o backup -directory file://myserver/mybackupshare -backupmethod full"
 
This is not a showstopper. It works. However unless you have the correct perms it will fail. Or, to be more specific, the Sql Service Account needs to be able to read/write to that share. Again, this is the account that the Sql Service runs under. Hopefully a domain account.
 
Also, if you are going to run it from the Sharepoint Central Administration Website, the account you are logged in with needs those perms as well.
# Thursday, May 14, 2009

Sharepoint 2007 and Ajax

Recently someone asked about using Ajax on Sharepoint. I should note that a number of resources are out there on this which I used, so - as brilliant as I am <g> - the credit for this has to go to those who have gone before...

Like a lot of people I have been working on both since their betas, so whether or not I should install Ajax on Sharepoint was a no-brainer. Unfortunately, when Sharepoint 2007 first came out it was the very end of 2006 and Ajax was not officially released until January 23, 2007. As a result, in order to utilize Ajax on Sharepoint (pre-SP1) you needed to jump through a whole host of hoops. This is not to say it could not be done, merely that it was a cumbersome and painful process.

Step 1: Install SP1 on Sharepoint

Luckily the folks on the Sharepoint team made that a priority, and even though we had to wait a bit for it, they did include 'Ajax support' in SP1. So the first step to using Ajax on Sharepoint 2007 is, quite simply, install Sharepoint 2007 SP1. You can do it without SP1, but really, why would you?

Note that simply installing SP1 does not automatically turn on Ajax (there are more steps) but it removes some of the bugs that popped up.

Step 2: (Recommended): Install Asp.Net framework 3.5 SP1

The next thing that I strongly suggest is that you upgrade to .Net 3.5 (sp1). Why? Simply because if you are going to be doing any Ajax you will want to.

  • It includes it directly in the framework, no need to install it separately
  • It has awesome debugging - you will still use Fiddler, but less often
  • Intellisense
  • Much, much easier means of creating a 'tracking history' of Ajax calls

If you decide to install .Net 3.5, note that you will not see a Web Service Extension for it in the IIS Manager. This is not a flaw, this is because 3.5 is an extension of 2.0. Let me state that again - if you install 3.5 you will actually use 2.0 as the .Net version.

Doing these two steps does not mean that Ajax is automatically up and running, just that the necessary dlls have been put into the GAC.

One final note - if you install 3.5 you will be using version 3.5.0.0 of System.Web.UI.* in a number of your web.config entries. If you are using an earlier version, make sure to check the GAC and find out what the specific version is. Most of the rest of the steps involve modifying your web.config, so make sure you know your System.Web.UI.Extensions version before you being

Step 2a: Make a back up of your web.config

We are all good techies here, right? Which means we are all paranoid, right? Never, ever modify your web.config unless you have a rollback copy at hand. Remember, paranoia is a job-requirement.

Step 3: Add the system.web.extensions sectionGroups to the configSections of your web.config

If you are not familiar with Sharepoint, this will be located in your c:\inetpub\wwwroot\VirtualDirectories\wss\{your site's port here}. So if you have your Sharepoint on port 8080 your web.config will be located at c:\inetpub\wwwroot\VirtualDirectories\wss\8080. Remember to make a copy of it like a good, paranoid nerd. Also, I tend to remark what I am adding and why to my web.config, but you don't have to.

Add this in the <configSections> portion, which should be at the top. You will already see other <sectionGroup... > entries there, just add this one at the end (above the </configSections> entry), wrapped for readability...

<!-- ======= Added for Ajax start ====== -->
<
sectionGroup name="system.web.extensions"
          type="System.Web.Configuration.SystemWebExtensionsSectionGroup,
          System.Web.Extensions, Version=3.5.0.0, Culture=neutral, 
         
PublicKeyToken=31bf3856ad364e35">
     <
sectionGroup name="scripting" 
             
type="System.Web.Configuration.ScriptingSectionGroup,
             
System.Web.Extensions, Version=3.5.0.0,  
              Culture=neutral, PublicKeyToken=31bf3856ad364e35
">
         <
section name="scriptResourceHandler"
               
type="System.Web.Configuration.ScriptingScriptResourceHandlerSection,
                 System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
                 PublicKeyToken=31bf3856ad364e35
" requirePermission="false" 
                
allowDefinition="MachineToApplication" />
         <sectionGroup name="webServices"
                
type="System.Web.Configuration.ScriptingWebServicesSectionGroup,
                 System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
                 PublicKeyToken=31bf3856ad364e35
">
             <
section name="jsonSerialization"
                    
type="System.Web.Configuration.ScriptingJsonSerializationSection,
                     System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
                     PublicKeyToken=31bf3856ad364e35
" requirePermission="false"
                    
allowDefinition="Everywhere" />
            
<section name="profileService"
                   
type="System.Web.Configuration.ScriptingProfileServiceSection, 
                    System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
                    PublicKeyToken=31bf3856ad364e35
" requirePermission="false"
                   
allowDefinition="MachineToApplication" />
            
<section name="authenticationService"
                   
type="System.Web.Configuration.ScriptingAuthenticationServiceSection,
                    System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
                    PublicKeyToken=31bf3856ad364e35
" requirePermission="false"
                   
allowDefinition="MachineToApplication" />
          </
sectionGroup>
     </
sectionGroup>
</
sectionGroup>
<!--
======= Added for Ajax end ====== -->

Step 4: Add System.Web.Extensions as a "SafeControl" to your web.config

SafeControls are those assemblies that should be considered "Safe" by Sharepoint. Do a quick find for <SafeControls> and you will see a whole lotta them. Simply add this line to the bottom, above the </SafeControls>.

NOTE!!! If you are using a different version than .net 3.5, make sure that you verify what version it is in the GAC. This needs to be specified in the "Version" section or Sharepoint will throw an error. Because I am using 3.5, my version is 3.5.0.0

<!-- ====== Added for Ajax  ====== -->
<
SafeControl Assembly="System.Web.Extensions, Version=3.5.0.0,
       
Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
       
Namespace="System.Web.UI" TypeName="*" Safe="True" />

Step 5: Add the needed "verbs" to your <httpHandlers>

Note that this is NOT the <httpModules> section. There should already be some entries there, so just stick this at the end, above the </httpHandlers> line....

<!-- ====== Added for Ajax start ====== -->
<
add verb="*" path="*.asmx" validate="false"
       
type="System.Web.Script.Services.ScriptHandlerFactory,
        System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
        PublicKeyToken=31bf3856ad364e35
" />
<
add verb="*" path="*_AppService.axd" validate="false"
       
type="System.Web.Script.Services.ScriptHandlerFactory, 
        System.Web.Extensions, Version=3.5.0.0,
        Culture=neutral, PublicKeyToken=31bf3856ad364e35
" />
<
add verb="GET,HEAD" path="ScriptResource.axd"
       
type="System.Web.Handlers.ScriptResourceHandler, 
        System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
        PublicKeyToken=31bf3856ad364e35
" validate="false" />
<!--
====== Added for Ajax end ====== -->

Step 6: Add the <controls> reference to the <pages...> section of the web.config

This will enable your pages to recognize the controls that come with Ajax (such as the ScriptManager, etc). Locate the <pages ...> section and add this at the end above the </pages> entry. Again, keep an eye on the Version.

<!-- ====== Added for Ajax  ====== -->
<
controls>
    <
add tagPrefix="asp" namespace="System.Web.UI"
         
assembly="System.Web.Extensions, Version=3.5.0.0,
          Culture=neutral, PublicKeyToken=31bf3856ad364e35
" />
</controls>
<!-- ====== Added for Ajax  end ====== -->

Step 7: Add this at the very end of your web.config, just above the </configuration> line

Of course you could stick it somewhere else, but let's be neat here... This is the section that first thing that you put into your web.config (the <sectionGroup...> referenced. There is documentation out there on sectionGroups, and they are very handy, if you get interested. I have left a lot of commented lines in there for if you need them. And, once more repeat after me, if you are not using 3.5, check the Version!

<!-- ====== Added for Ajax start ====== -->
<
system.web.extensions>
    <
scripting>
        <
webServices>
            <!--
Uncomment this line to enable the authentication
                service. Include requireSSL="true" if appropriate.
-->
                <!--
<authenticationService enabled="true" requireSSL = "true|false"/> -->
             <!--
Uncomment these lines to enable the profile service.
                   To allow profile properties to be retrieved and modified 
                   in ASP.NET AJAX applications, you need to add each property
                   name to the readAccessProperties and 
                   writeAccessProperties attributes.
-->
                <!--
<profileService enabled="true"
                           readAccessProperties="propertyname1,propertyname2"
                           writeAccessProperties="propertyname1,propertyname2" />
-->
 
       </webServices>
        <!--
<scriptResourceHandler enableCompression="true" enableCaching="true" /> -->
   
</scripting>
</
system.web.extensions>
<
system.webServer>
    <
validation validateIntegratedModeConfiguration="false" />
        <
modules>
            <
add name="ScriptModule" preCondition="integratedMode"
               
type="System.Web.Handlers.ScriptModule, 
                System.Web.Extensions, Version=3.5.0.0, Culture=neutral, 
                PublicKeyToken=31bf3856ad364e35
" />
        </
modules>
        <
handlers>
            <
remove name="WebServiceHandlerFactory-Integrated" />
            <
add name="ScriptHandlerFactory" verb="*" path="*.asmx"
                 
preCondition="integratedMode"
                 
type="System.Web.Script.Services.ScriptHandlerFactory,
                  System.Web.Extensions, Version=3.5.0.0, Culture=neutral, 
                  PublicKeyToken=31bf3856ad364e35
" />
            <
add name="ScriptHandlerFactoryAppServices" verb="*"
                 
path="*_AppService.axd" preCondition="integratedMode"
                 
type="System.Web.Script.Services.ScriptHandlerFactory,
                  System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
                  PublicKeyToken=31bf3856ad364e35
" />
            <
add name="ScriptResource" preCondition="integratedMode"
                 
verb="GET,HEAD" path="ScriptResource.axd"
                 
type="System.Web.Handlers.ScriptResourceHandler,
                  System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
                  PublicKeyToken=31bf3856ad364e35
" />
        </
handlers>
</
system.webServer>
<!--
====== Added for Ajax  end ====== -->

Step 8: Add a ScriptManager to your Master Page

You can always do it dynamically, and I always check in my code to see if it exists before whatever webpart I am writing loads, but it is much simpler just to add it on your Master Page. Just stick it below the <form id="Form1"... > entry :

<asp:ScriptManager runat="server" ID="ScriptManager1 />

Please note that there are a LOT of other goodies you can add to your ScriptManager, especially in .net 3.5, so feel free to expand it. Just make sure it is in there.

Step 9: Write Ajax pages (or webparts).

If you have followed these directions you should be ready to roll. I have not included how to add the AjaxControlToolkit, but if you are this far it is fairly a no-brainer. Add the .dll to the GAC; add the assembly reference to the web.config 'assemblies', add the namespace entry to the web.config 'controls' section and you are ready to rock! 

Final NOTE!!! UpdatePanels on Pre-SP1 ...

You may need to add the following code to get UpdatePanels to work properly, just put it in your Master Page

<script type="text/javascript">
   
_spOriginalFormAction = document.forms[0].action;
   
_spSuppressFormOnSubmitWrapper=true;
</script>