Friday, December 5, 2008

Xsl Transformation Credentials

There is a frustrating deficiency in the XsltLoader class with regards to permissions. This is the class called by the XslCompiledTransform class that loads the xsl source file. The standard call would be as follows:

StringWriter stringWriter = new StringWriter();
HtmlTextWriter writer = new HtmlTextWriter(stringWriter);

XmlUrlResolver resolver = new XmlUrlResolver();
resolver.Credentials = CredentialCache.DefaultCredentials;

XslCompiledTransform transformer = new XslCompiledTransform();
transformer.Load(xslPath, XsltSettings.Default, resolver);
transformer.Transform(sourceXmlDoc, null, writer);

return stringWriter.ToString();

This works fine if you are under standard NT security/permission setups for SharePoint/MOSS 2007. However, if you do something like bring in Forms Based Authentication, you will get a strange xsl compile error.

The problem is that although the XsltLoader.Load method accepts an XmlUrlResolver object as a parameter, it does not actually use it when retrieving the xsl file. So, we cannot call the XslCompiledTransform.Load method by passing the xsl path as a string. Instead, we need to load the xsl into an XmlReader object manually and pass the reader to the Load method instead. This allows us to pass the credentials to the XmlReader and it will do the work of retrieving the xsl file. The finally result would be as follows:

StringWriter stringWriter = new StringWriter();
HtmlTextWriter writer = new HtmlTextWriter(stringWriter);

XmlUrlResolver resolver = new XmlUrlResolver();
resolver.Credentials = CredentialCache.DefaultCredentials;

XmlReaderSettings settings = new XmlReaderSettings();
settings.XmlResolver = resolver;
XmlReader reader = XmlReader.Create(xslPath, settings);

XslCompiledTransform transformer = new XslCompiledTransform();
transformer.Load(reader , XsltSettings.Default, resolver);
transformer.Transform(sourceXmlDoc, null, writer);
return stringWriter.ToString();

Wednesday, October 8, 2008

Filter Dropdown based on Audiences

I found this presentation that gives a nice clean example of how to use the Sharepoint 2007 Audience functionality in code. So here is a quick model for creating a dropdown that is filtered based on the audience(s) set for each list item in an SPList.

private void CreateAudienceFilteredSelect(SPListItemCollection listItems)
{
// define this object at the class level so that it can access on the postback
newDropDown = new DropDownList();
AudienceLoader audienceLoader = AudienceLoader.GetAudienceLoader();

foreach (SPListItem listItem in listItems)
{
if (listItem ["Target Audiences"] != null)
{
string audienceValues = listItem ["Target Audiences"] .ToString();
if (AudienceManager.IsCurrentUserInAudienceOf(audienceLoader, audienceValues, false))
{
newDropDown.Items.Add(new ListItem(listItem["Title"].ToString(), listItem ["ID"].ToString()));
}
}
}
}

Thursday, August 28, 2008

To dispose or not to dispose

Helpful guide for determining when you should dispose your SPWeb and SPSite objects manually.

http://blogs.msdn.com/rogerla/archive/2008/02/12/sharepoint-2007-and-wss-3-0-dispose-patterns-by-example.aspx

Tuesday, August 26, 2008

Where in the world is the Microsoft.SharePoint.ApplicationPages.dll located?

SharePoint 2007 location for the Microsoft.SharePoint.ApplicationPages.dll:

C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\CONFIG\BIN

Thursday, August 21, 2008

Custom Web Part Properties (ToolParts)

SharePoint 2007 Web Parts have a nice built in property management. As long as your properties are straight forward strings, booleans, etc. However, if you need a more complicated property such as a list or a radio group, you will need to create a custom property. These are called Tool Parts.

The ToolPart base class is laid out similarly to the WebPart base class. In essence, you will create your custom controls in the CreateChildControls method.

This example will do a little more than just create the controls though. Here we will style the Tool Part so it looks just like the standard SharePoint properties as well. For the record, I totally stole the formatting stuff from Liam Cleary.
 
First off, create a new class that inherits from Microsoft.SharePoint.WebPartPages.ToolPart.

public class SampleToolPart : ToolPart
{
    // First, override the CreateChildControls method. This is where we create the controls.
    protected override void CreateChildControls()
    {
        // create a panel that will hold all of our controls
        Panel toolPartPanel = new Panel();
        toolPartPanel.CssClass = "ms-ToolPartSpacing";

        // create a table that will put our controls in rows
        Table toolPartTable = new Table();
        toolPartPanel.Controls.Add(toolPartTable);
        toolPartTable.CellPadding = 0;
        toolPartTable.CellSpacing = 0;
        toolPartTable.Style["border-collapse"] = "collapse";
        toolPartTable.Attributes.Add("width", "100%");

        // create the first row that will contain a control
        TableRow firstRow = new TableRow();
        toolPartTable.Rows.Add(firstRow);
        TableCell firstRowCell = new TableCell();
        firstRow.Cells.Add(firstRowCell);

        // add formatting to the cells
        firstRowCell.Controls.Add(new LiteralControl("<div class='UserSectionHead'>Sample Dropdown:</div>"));
        firstRowCell.Controls.Add(new LiteralControl("<div class='UserSectionBody'><div class='UserControlGroup'><nobr>"));

        // create the actual control
        DropDownList sampleDropDown1 = new DropDownList();
        sampleDropDown1.ID = "sampleDropDown1";
        sampleDropDown1.Items.Add("Item 1");
        sampleDropDown1.Items.Add("Item 2");
        sampleDropDown1.Items.Add("Item 3");

        // add the new control to the table
        firstRowCell.Controls.Add(sampleDropDown1);

        // add formatting closing tags to the cell
        firstRowCell.Controls.Add(new LiteralControl("</nobr></div></div>"));

        // create a standard dotted line seperator
        TableRow seperatorRow = new TableRow();
        toolPartTable.Rows.Add(seperatorRow);
        TableCell seperatorCell = new TableCell();
        firstRow.Cells.Add(seperatorCell);
        seperatorCell.Controls.Add(new LiteralControl("<div style='width:100%' class='UserDottedLine' />"));

        // lather, rinse, repeat for all the controls you need

        // finally add the panel to the controls collection of the tool part
        Controls.Add(toolPartPanel);
        base.CreateChildControls();
    }

    // Next, override the ApplyChanges method.
    // This method is where we will persist the values that the user selects.
    public override void ApplyChanges()
    {
        // get the parent webpart
        SampleWebPart parentWebPart = (SampleWebPart)this.ParentToolPane.SelectedWebPart;

        // loop thru this control's controls until we find the ones that we need to persist.
        RetrievePropertyValues(this.Controls, parentWebPart);
    }

    // Recursive function that tries to locate the values set in the toolpart
    private void RetrievePropertyValues(ControlCollection controls, SampleWebPart parentWebPart)
    {
        foreach (Control ctl in controls)
        {
            RetrievePropertyValue(ctl, parentWebPart);

            if (ctl.HasControls())
            {
                RetrievePropertyValues(ctl.Controls, parentWebPart);
            }
        }
    }

    // Method for retrieving the values set by the user.
    private void RetrievePropertyValue(Control ctl, SampleWebPart parentWebPart)
    {
        if (ctl is DropDownList)
        {
            if (ctl.ID.Equals("sampleDropDown1"))
            {
                DropDownList drp = (DropDownList)ctl;
                if (drp.SelectedItem.Value != "")
                {
                    parentWebPart.Property1 = drp.SelectedItem.Value;
                }
            }
        }
    }
}


Now, we need to wire up the tool part to the web part. To do this, we override the GetToolParts() method and place our new tool part in the ToolPart array.
 

public class SampleWebPart : Microsoft.SharePoint.WebPartPages.WebPart
{
    public SampleWebPart()
    {
        this.ExportMode = WebPartExportMode.All;
    }

    // property that will be set thru our custom tool part
    // make sure to set the WebBrowsable attribute to false
    // so it will not show up outside of our custom tool part
    [Personalizable(PersonalizationScope.Shared)]
    [WebBrowsable(false)]
    [System.ComponentModel.Category("My Group")]
    [WebDisplayName("Property1")]
    public string Property1
    {
        get
        {
            return _property1;
        }
        set
        {
            _property1 = value;
        }
    }
    private string _property1 = "Default Value";

    public override ToolPart[] GetToolParts()
    {
        // resize the tool part array
        ToolPart[] toolparts = new ToolPart[3];
        // instantiate the standard SharePopint tool part
        WebPartToolPart wptp = new WebPartToolPart();
        // instantiate the custom property toolpart if needed.
        // this object is what renders our regular properties.
        CustomPropertyToolPart custom = new CustomPropertyToolPart();
        // instantiate and add our tool part to the array.
        // tool parts will render in the order they are added to this array.
        toolparts[0] = new SampleToolPart();
        toolparts[1] = custom;
        toolparts[2] = wptp;
        return toolparts;
    }
}


Monday, July 21, 2008

Setting Master Page For An Individual Page

Settings the master page for a SharePoint 2007 publishing website can be done out of the box using the UI or programmatically. For guidance on how to set the MasterPage programmatically, Mike Hodnick has a good post.

However, if you want to set the Master Page for an individual page or pages within a website, things get tricky.

The @Page directive exposes the MasterPageFile property, but the value is immediately overwritten in the Pre-Init event of the publishing pages base class (PublishingLayoutPage).

To resolve this, we will have to set the MasterPageFile through code. Andrew Connell provides a nice tutorial on how to create a code-behind class for a layout page.

Once you have created the code-behind class, override the OnPreInit event method and set the MasterPageFile property immediately after the base.OnPreInit(e) call.

protected override void OnPreInit(EventArgs e)
{
     base.OnPreInit(e);
     // override the MasterPageFile which is set in the Pre-Init with our own.
     MasterPageFile = "mymaster.master";
}

Friday, July 18, 2008

Content Type is still in use error

I spent an hour trying to delete a content type through the SharePoint 2007 UI with no success. I kept getting an "Content Type is still in use" exception even though I had deleted all layout pages that used that content type. I even went as far as de-activating the feature that installs the layout pages in the first place.

Finally I figured out that I have to remove the content type from any document library that contained any of the layout pages attached to the offending content type.

In my case, I had a publishing web site. So I clicked on "View All Site Content" and selected the Pages document library. From there, I went to the Settings page and, under the Content Types section, clicked on the content type I wanted to remove and deleted it.

Wednesday, July 16, 2008

Programmatically Creating a List from a ListTemplate

Adding a list to a website in SharePoint 2007 from a ListTemplate is fairly straightforward. But there is one quirk that you need to be aware of.

The templates are located in SPWeb.ListTemplates. The indexer for the ListTemplateCollection has two overloads. The first one uses the index value of the ListTemplate. Since you will probably not know that value, the second overload comes into play. This one uses the "Name" of the ListTemplate.

Here's the catch. What Microsoft really means by "Name" is "Display Name". This seems like more than just bad naming conventions, but bad practice as well. As the "Name" of the ListTemplate should be the key.

Monday, July 7, 2008

Namespaces matter

Thanks to Paul's post, I realized that SharePoint 2007 pays closer attention to namespaces than I realized. Your feature and it's feature receiver must have the same root namespace or the solution will not deploy.

I am still not sure what the difference is, because I could deploy to my local environment using WSPBuilder without issue. But when I tried to deploy to our test environment using the standard stsadm commands it failed with the following exception:

"Failed to create feature receiver object from assembly 'MyAssemblyInfo'... : System.ArgumentNullException: Value cannot be null."

Thursday, July 3, 2008

Create an Anonymous Access Survey Programmatically

Their are several good examples on how to use the UI to make a SharePoint 2007 survey work for anonymous users.  So I took what I learned from them and came up with this method for doing the same thing programmatically.

The main catch was the AnonymousPermMask64 flags.  The only ones that you see in the UI are the AddListItems and ViewListItems.  But there are several others that are set behind the scenes.

 

 
private void AddNewSurvey(SPWeb web, SPListTemplate surveyTemplate)
{
    web.Lists.Add("Survey Title", "Survey Description", surveyTemplate);
    
    // once we have created the survey, find it again so we can set the permissions
    SPList surveyList = web.Lists["Survey Title"];
    web.AllowUnsafeUpdates = true;
    // break the role inheritance so we can set our own permissons
    if (!surveyList.HasUniqueRoleAssignments)
        surveyList.BreakRoleInheritance(true);
    surveyList.ReadSecurity = 2; // can read their own
    surveyList.WriteSecurity = 2; // can edit their own
    // set the permission flags for Add and View plus some internal flags
    surveyList.AnonymousPermMask64 =
        (SPBasePermissions.Open |
        SPBasePermissions.OpenItems |
        SPBasePermissions.ViewFormPages |
        SPBasePermissions.UseClientIntegration |
        //SPBasePermissions.UseRemoteAPIs |
        SPBasePermissions.ViewListItems |
        SPBasePermissions.AddListItems);
    surveyList.Update();
}