Friday, September 28, 2007

Workaround - "Error" in navigation when creating a publishing site from code

Since I started working with sharepoint 2007 I was puzzled by this bug, and recently I asked more people how said they have that too. The problem happens when you use the API to create a site out of a template that has the publishing feature activated in it. The site is created ok, but the first user to open the site will see a very odd tab and quick-launch:






So far, I have no explanation, but I have a workaround. Make a call to the site in the code. I know - this adds process time and resources, but it beats having your users ask you about the "error tab".
In your code, add the following function, and call it just after the site creation code:



public static void OpenUrl(string url)
{
try
{
HttpWebRequest request = (HttpWebRequest)
WebRequest.Create(url);
request.UseDefaultCredentials = true;
HttpWebResponse response = (HttpWebResponse)
request.GetResponse();
}
catch(Exception ex) {
//do something with the error
}
}


Here is an example how to use this function:



public static void CreateSite(SPWeb parentWeb)
{
try
{
//get the template for the site using the GetWebTemplate function I wrote
SPWebTemplate template =
SharePointFunctions.GetWebTemplate(parentWeb,
this.SiteTemplateName,
ConfigVariables.SitesLCID);
//create the site using the class properties
newWeb = parentWeb.Webs.Add(this.ShortName,
this.SiteTitle,
this.SiteDescription,
ConfigVariables.SitesLCID,
template,
false,
false);
OpenSiteHomePage(newWeb.Url);
}
catch(Exception ex) {
//do something with the error
}
}









Wednesday, September 26, 2007

Joined BlogRush

Not sure if that will make any difference, but I joined this site (BlogRush) in hope of getting more visitors. I will let you know if it works!

Back from vacation

Hello my readers!

You may have noticed I didn't blog this month, and that was because I was on my annual leave, visiting my family and friends in hot Israel!

I took time in the trip to present at the Israeli SharePoint User Group in Microsoft Israel, and I was very surprised to see how many people turned up! We had more than 50 people there, a lot of past clients and past collegues.



Here is a picture of the lowest sharepoint expert in the world (me floating in the dead sea -416 meters below sea level! to get lower, you need to go on a submarine):





And here are a couple of pictures I took that I think would make great desktop backgrounds:




































Tuesday, September 04, 2007

Just for me - excel macro to generate an XML

If you are here for a sharepoint tip, disregard this post- I am posting this to myself so I will not lose this code. Basically, it is an excel macro that genenrates an xml file from the current sheet. If you do want to use it, add a reference to microsoft xml in the VBA environment, and paste the code below to a module.
There are many way to improve it, but I just wrote this to quickly achieve a small task that I needed done.

Sub makeXml()
    ActiveCell.SpecialCells(xlLastCell).Select
    Dim lastRow, lastCol As Long
    lastRow = ActiveCell.Row
    lastCol = ActiveCell.Column
    
    Dim iRow, iCol As Long
    
    Dim xDoc As New DOMDocument
    Dim rootNode As IXMLDOMNode
    Set rootNode = xDoc.createElement("Root")
    Dim rowNode As IXMLDOMNode
    Dim colNode As IXMLDOMNode
    
    'loop over the rows
    For iRow = 2 To lastRow
        Set rowNode = xDoc.createElement("Row")
        'loop over the columns
        For iCol = 1 To lastCol
            If (Len(ActiveSheet.Cells(1, iCol).Text) > 0) Then
                Set colNode = xDoc.createElement(GetXmlSafeColumnName(ActiveSheet.Cells(1, iCol).Text))
                
                colNode.Text = ActiveSheet.Cells(iRow, iCol).Text
                rowNode.appendChild colNode
            End If
        Next iCol
        rootNode.appendChild rowNode
    Next iRow
    xDoc.appendChild rootNode
    xDoc.Save ("c:\temp\temp.xml")
    set xDoc = Nothing
    
End Sub
Function GetXmlSafeColumnName(name As String)
    Dim ret As String
    ret = name
    ret = Replace(ret, " ", "_")
    ret = Replace(ret, ".", "")
    ret = Replace(ret, ",", "")
    ret = Replace(ret, "&", "")
    ret = Replace(ret, "!", "")
    ret = Replace(ret, "@", "")
    ret = Replace(ret, "$", "")
    ret = Replace(ret, "#", "")
    ret = Replace(ret, "%", "")
    ret = Replace(ret, "^", "")
    ret = Replace(ret, "*", "")
    ret = Replace(ret, "(", "")
    ret = Replace(ret, ")", "")
    ret = Replace(ret, "-", "")
    ret = Replace(ret, "+", "")
    
    GetXmlSafeColumnName = ret
End Function

Monday, September 03, 2007

The security validation for this page is invalid.

When writing code for sharepoint, you may encounter the error "The security validation for this page is invalid. Click Back in your Web browser..." and so on. A lot has been written on this, with many different people touting different solutions. For example SpiderWool says to turn on security validation for the application.
This is not recommended, and not required as one of the comments said - all you need to do before you update the list item or web object is to set "AllowUnsafeUpdates" to true for the SPWeb and SPSite objects.
But some of the other comments in the same post complain that it didn't help them. Well, most probably the reason is that they created the SPWeb or SPSite objects in another function and then tried to update an object that was returned from the function. Here are two examples of the same code, where one will work and the other will not:
Bad Example:
The code calls a function to get the list, where the SPWeb and SPSite are created in the function.

using (SPSite site = new SPSite(parentSiteUrl))
{
        site.AllowUnsafeUpdates = true;
        using (SPWeb web = site.OpenWeb())
        {
            web.AllowUnsafeUpdates = true;
            SPList list = GetList("mylist");
            _listItem = clientsList.Items.Add();
            _listItem["Title"] = "test";
            _listItem.Update();                    }
        }
}
private SPList GetList(string name)
{
   using (SPSite site = new SPSite(parentSiteUrl))
   {
        site.AllowUnsafeUpdates = true;
        using (SPWeb web = site.OpenWeb())
        {
            web.AllowUnsafeUpdates = true;
            SPList list = web.Lists["mylist"];
            return list;
        }
   }
}

Good Example:
Loading the SPSite and SPWeb object only once and using them to get the list.

using (SPSite site = new SPSite(parentSiteUrl))
{
        site.AllowUnsafeUpdates = true;
        using (SPWeb web = site.OpenWeb())
        {
            web.AllowUnsafeUpdates = true;
            SPList list = web.Lists["mylist"];
            _listItem = clientsList.Items.Add();
            _listItem["Title"] = "test";
            _listItem.Update();                    }
        }
}

Wednesday, August 29, 2007

Web Services on SharePoint - making F5 Work

I was getting many questions on best practices to write custom web services for sharepoint, and I wanted to write an article about it for some time now. Also, I just had a chance to fiddle around with making F5 (run from visual studio into debug mode) work for a web service I am testing on a sharepoint site. This involved a few tricks, so I am documenting them:

Note - only do this on your development box!

Note - code lines marked in red means that you will need to change the values to your environment values.


I hold that the best practice is not to use the web service as a web application template (that is the default with visual studio) because when deploying to sharepoint, you'r web service usualy needs to sit in the layouts folder, and you do not want to deploy your code files there. You also don't want to use the visual studio publishing mechanism that uses the frontpage server extensions.
The alternative is creating a web service that is deployed as an asmx file to the layouts folder, pointing to a DLL that is deployed to the GAC. That makes it safe and secure, and easier to deploy and track versions.



Step 1 - Create a Web Service (DLL) Project

To create the web service, use Visual Studio 2005, and click "File-New-Project" and select ASP.NET Web Service Application.



If you don't have that project type installed, you may need to change the installed features of Visual Studio on your machine. The good think about this project type is that it creates a web service as a DLL and an asmx and not as a web site.



After the project is created, change the file names, namespace and assembly names as needed (make sure the namespace of the webservice attribute is changed - you don't want "http://tempuri.org/" as your namespace...) and most importantly - sign the assembly as strong name (right click the project, properties, signing tab, sign the assembly).




Now we have to find out the key that was given to the assembly when it was signed. To do that you must first build the web service (I use ctrl-shft-B, or right click the project and select build) and then either drag and drop the DLL to the assembly folder, right click and get the key:



Or you can run a command line from the visual studio SDK (start-programs-visual studio 2005-visual studio tools-Visual Studio 2005 Command Prompt) and type

sn -T "c:\temp\WebService1\WebService1\bin\WebService1.dll"



Once we have the public key token, we can change the web service's asmx file to use the DLL that will be deployed to the gac. Right click the service1.asmx file and choose "View Markup". You need to change the markup to point to the DLL that will be in the GAC, so this is the format you need to use:



<%@ WebService Language="C#" Class="WebService1.Service1, WebService1, Version=1.0.0.0, Culture=Neutral, PublicKeyToken=db33ac6baa259170" %>

Expalantion:

  1. The "WebService1.Service1" part is the "namespace dot classname" of your code. To get that, you will need to open your .cs file, and copy the namespace you are using, add a dot after it, and add the class name.

  2. The "WebService1" (after the comma) is the name of the DLL that you may have set in the project properties under the "application" tab (the "assembly name")

  3. The public key token is the key we got earlier.





Note - I am not sure if this is needed, but I also registered my web service as a safe control in the web.config, before I used any sharepoint code. It's worth checking if this is required or not, and I will update if I have time to test.



Step 2 - Setting up the Build Events

While in the project's properties, switch to the "Build Events" tab. Paste the following in the "post-build event command line" box:


"c:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe" /i "$(TargetPath)" /f

copy "$(ProjectDir)\*.asmx" "C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\LAYOUTS"

"C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\bin\RecycleAppPools.vbs"

"c:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\disco" http://localhost/_layouts/service1.asmx /o:"C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\LAYOUTS"




Explanation:


  1. The first line puts the DLL in the GAC.


  2. The second line copies the web service asmx file to the layouts folder. You should make sure your projects do not create same names for asmx files, or they will overwrite eachother!


  3. The third line recycles the application pools. I am doing this with a vbs file that I have written, and placed in the 12 hive's "\bin" folder for my comfort. Here are the contents of the file:


    Set locator = CreateObject("WbemScripting.SWbemLocator")

    Set Service = locator.connectserver(strServer, "root/MicrosoftIISv2")

    Set APCollection = Service.InstancesOf("IISApplicationPool")

    For Each APInstance In APCollection

    APInstance.Recycle

    Next

    echo "Recycle Complete."


  4. The last line build a discovery file for the web service in the layouts folder. if you installed your visual studio 2005 in a different location, you will need to change the folder path to the disco.exe file, and if you changed the name of the asmx (or if you have more than one) you will need to modify that as well. You will also want to change the url to point to a sharepoint site on your machine if you are not using the "localhost" header. In my case, I use the header "portal", so I change is to "http://portal".



Step 3- Change the F5 behavior

To change what happens when you press F5, switch to the "Web" tab, and change the start url to the url to the web service (I use http://portal/_layouts/service1.asmx), then change the "use IIS web server" and change the project url to the rool sharepoint url (I use "http://portal") and tick the "override application root URL and type the same url as the project url:





Last Step - set up your web.config to allow debugging

If you want F5 to work, and have visual studio debug the web service when you press it, the sharepoint web.config should be changed to allow debugging. To do that, you will need to open the web.config file for the virtual server you are using (in my case "http://portal" - which means the web.config is under "c:\Inetpub\wwwroot\wss\VirtualDirectories\portal80\web.config") and find the "<compilation>" tag, and set the attribute "debug" to true:




This is it!

If you didn't miss anything, you should be able now to press F5 and the web service will launch in the sharepoint context, with debugging in the visual studio! You can set breakpoints and debug the web service.







Tuesday, August 28, 2007

Passed another exam

Hi all,
I had passed yet another MSCTS 70-631 Windows SharePoint Services 3.0, Configuring. This completes the set, and now I have all four sharepoint exams under my belt.
I have to say that this one was the most difficult one for me, since it had many questions about topics I try not to touch - for example load balancing. I really don't understand why a sharepoint expert needs to know load balancing and what is the difference between multicast and unicast, and how you should configure the network adapters. On the other hand, some of the questions were easy for me because they had more to do with development (for example, if a custom web part gets installed and crashes with an unhandled error - what should the administrator do? The answer was easy for me, because I am a developer - but is it fair for an admin to get asked that?)
So all in all I am happy about this, and I plan to promptly forget everything I know about WSS and MOSS configuration and installation. Heck, people keep asking me questions about this - "Ishai, how do I configure forms authentication?" or "Ishai, I configured anonymous and it doesn't work" - and I am sick of it. My answer from now on will be "I don't know, but I will write you a web part to make it all better!".

Speaking of webparts, I didn't get any complaints about my enhanced content query webpart, so I am moving on and will start thinking of my next open source project. It is still in infancy, but I hope to let you know about it soon.

On an unrelated matter, I will be speaking at the Israeli SharePoint user group next month (19/9) about features and templates (hebrew web site) so if you are in Israel, come over and say hello.

While I am there, there is a Canberra user group without me, and I am still looking for presenters. I will keep you updated if you want to know who will talk. Last month we had Anthony Woodward from my company talk about sharepoint record managment compliance in general and with Australian-specific notes. It was quite interesting. If you want to present, give me a buzz (my contact form is on this blog - look for it!)

Sunday, August 26, 2007

What's good and what's missing from WSS Visual Studio Extensions 1.1?


The Windows SharePoint Services 3.0 Tools: Visual Studio 2005 Extensions, Version 1.1 CTP were released, with
"Support for 'Web Solution Package' editing, List Instance item template ,List Event Handler item template,Bug fixes", so I decided to take a quick look at what's in the web part project template.


First, I liked the fact that now by default when you create a web part project you don't get the "Render" event that you used to get in the past. This caused many developers to start writing html in the Render event, and then be puzzled when the events on controls didn't work (see Server side controls and data binding in web parts in this blog).



I am still looking into it, but one thing that I would expect from a webpart project template is a wizard when creating the project that asks:


  1. Do you want the web part to support connections? (add stubs for connection interface)

  2. Do you want the web part to connect to a specific list? (add properties and functions to connect to a list)

  3. Do you want a custom toolpane?



What do you think? what else would you want from a template?






Wednesday, August 22, 2007

New SDK, with a tool for BDC

Hey!

A new MOSS SDK is available, and it comes with a free tool to create BDC definitions (is this the end of MetaMan? I wonder...).
The new tool is called "Business Data Catalog Definition Editor"

MSDN already have articles on how to use the tool with web services and other systems.


Installing requires you to install SQL server 2005, so don't install on your production server (duh!), and be prepared for some drastic changes to your system when you install on your dev box.



The good news is that it can be installed on windows XP, proving that Microsoft are listening to us developers, and to our gripes about developing on a server...

I tried installing by running the MSI directly, but that didn't work (didn't install the SQL express, and the installation failed when it couldn't find SQL express).







Monday, August 20, 2007

Slides from Tech.Ed Australia 2007 - Templates and Features

Here are the slides I did in my presentation with Milan Gross (his slides are not included here, as they are his to publish):


I start we had the Tech.Ed image:




Next, our names and titles:





Some links and referances:








The agenda of the session (Milan covered that):





Here I spoke about the difference between templates (save as template for a list or a site) and definitions (file system xml and aspx files that allow developers and administrators have more control over the sites after deployment)




A quick explanation of how a solution package is built. For example I showed the package of the "print list" feature as it was done by Scott Hillier.



Next I discussed what are DDF files and why we need them (we need them to let makecab.exe know how to build our cab file - using folders), as well as giving some alternatives such as cabarc that allows you to build a cab file with folders in it - no need for a definition file. I also mentioned a tool I never used - WSPBuilder




Now to my part of the demo - I showed how to create a feature for a webpart and how to package it. I used my own Enhanced Content Query WebPart solution package to demonstrate this, and you can download that from codeplex!









After that Milan took over and showed a scenario that used the Solution Generator (part of the WSS extensions for Visual Studio) to create a site definition, with lists that are connected to workflows and have BDC fields and all. It was pretty complex, and I felt we needed more time to really explain it all.


And that was my first presentation. I will let you know about the second one soon.