in

SharePoint Blogs

The Best Place for SharePoint-related Blogs

Nick's SharePoint Blog

  • Daylight savings times messes up WSP solutions

    Last week I encountered something strange here on the servers.

    Retracting a solution file (*.wsp) took just a little longer than 1 hour! Which is WAY longer than it should take, and usually takes...

    My theory: daylight savings time messed up something in the configurations ... Here in Belgium, 2 AM became 3 AM (03/30), which apparently led sharepoint to think that when I scheduled a solution to retract immediately, we wanted to wait an hour to do so ...

    Update: apparently there is a hotfix from MS addressing the problem at http://blogs.msdn.com/sharepoint/archive/2007/10/09/important-security-hotfix-ms07-059.aspx - Thank you Tobias!

     

    Posted Apr 07 2008, 12:01 PM by nsevens with 4 comment(s)
    Filed under: , ,
  • Manage audiences with C#

    Past few days I have been looking into the creation and setting up of audiences in MOSS2007. It took me a while though to figure all these things out, but I'm kind of getting the hang of it right now ;) So ... time to share it with the world, right?

    For the development, we'll be using the following Assemblies:

    • System.Web.UI.WebControls.WebParts
    • Microsoft.Office.Server
    • Microsoft.Office.Server.Audience
    • Microsoft.Office.Server.Search.Administration
    • Microsoft.SharePoint
    • Microsoft.SharePoint.WebPartPages

    (For those of you who don't know where to find the Microsoft.Office.Server dll files, it's located in the C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\ISAPI folder.)

     

    1. Creating a new audience

    First thing we'll do is creating a custom audience, giving it two rules, joined with an OR operator.

    public void CreateNewAudience()

    {

        string audienceName = "Test and temp"; //audience name

        string audienceDesc = "Only the test and temp users."; //audience description

     

        using (SPSite site = new SPSite("http://server/site"))

        {

            ServerContext context = ServerContext.GetContext(site);

     

            AudienceManager audManager = new AudienceManager(context);

            AudienceCollection audCollection = audManager.Audiences;

            Audience audience = null;

            AudienceRuleComponent audRule = null;

     

            audience = audCollection.Create(audienceName, audienceDesc);

            audience.AudienceRules = new ArrayList(); //initialize the collection

     

            /* An AudienceRuleComponent exists of:

            * a left part: the property to look at

            * an operator: the operator to perform (=, <>, Contains, ...)

            * a right part: the value to search for */

            audRule = new AudienceRuleComponent("UserName", "Contains", "test");

            audience.AudienceRules.Add(audRule);

            audRule = new AudienceRuleComponent(null, "OR", null);

                //to use an operator (AND, OR, ...) set the left and the right part to NULL !

            audience.AudienceRules.Add(audRule);

            audRule = new AudienceRuleComponent("UserName", "Contains", "temp");

            audience.AudienceRules.Add(audRule);

     

            audience.Commit(); //commit the changes

        }

    }

    Doing this, a new audience named "Test and Temp" will be created, which in the end will contain all users with user names containing "test" or "temp".

    If you would like to have a complete set of the operators, see http://msdn2.microsoft.com/en-us/library/ms954299.aspx

    However, without compiling the new audience, we won't be able to do a lot with it, right?
    Compiling the audience can be done in a couple different ways. We can compile it manually through the Shared Service Provider administration pages or stsadm, schedule the compilation, or do it programmatically. Guess what we are going to do ;)

     

    2. Compiling the new audience

    To compile the new audience, we have to run an AudienceJob, and give it 4 parameters in a string array:

    1. The application id
    2. "1" = start job, "0" = stop job
    3. "1" = full compilation, "0" = incremental compilation (optional, default = 0)
    4. Audience name (optional, if null is supplied, all audiences are run!

    http://msdn2.microsoft.com/en-us/library/microsoft.office.server.audience.audiencejob.runaudiencejob.aspx

    http://jopx.blogspot.com/2007/05/scheduling-audience-compilation-in-moss.html

    public void CompileAudience()

    {

        using (SPSite site = new SPSite("http://server/site"))

        {

            ServerContext context = ServerContext.GetContext(site);

            SearchContext searchContext = SearchContext.GetContext(context);

     

            string[] args = new string[4];

            args[0] = searchContext.Name;

            args[1] = "1";

            args[2] = "1";

            args[3] = "Test and temp";

     

            Int32 runjob = AudienceJob.RunAudienceJob(args);

        }

    }

    So in this case, it means we will perform a START of the operation, doing a FULL compilation, on the audience TEST AND TEMP.

    Oke, so now our audience has been compiled, we can go for the next step: setting up an audience on a webpart we put on a SharePoint page.

     

    3. Audience targetting in a webpart

    It took me quite a long time to figure out how to set up the audiences in a webpart through code ... I found a partial answer on Mart Muller's blog here:

    http://blogs.tamtam.nl/mart/CommentView,guid,a0b20be6-56e4-4571-af0f-3dff43b5f69d.aspx 

    However, the solution given there does not work for me, but I might as well just have missed something there :) So, after some trial and error, I managed to get "my" version to work, by using the ID of the audience in stead of the name.

    So first, we'll have to retrieve this ID from the audience we're looking for:

    public Guid GetAudienceId(string audienceName)

    {

        using (SPSite site = new SPSite("http://server/site"))

        {

            ServerContext context = ServerContext.GetContext(site);

            AudienceManager audManager = new AudienceManager(context);

            AudienceCollection audCollection = audManager.Audiences;

     

            foreach (Audience aud in audCollection)

            {

                if (aud.AudienceName == audienceName)

                    return aud.AudienceID;

            }

        }

    }

    Then, we add this ID to the AuthorizationFilter property of the webpart. This is where the audience parameter is set!

    public void AddAudienceToWebpart()

    {

        using (SPSite site = new SPSite("http://server/site"))

        {

            using (SPWeb web = site.OpenWeb())

            {

                SPLimitedWebPartManager wpManager = web.GetLimitedWebPartManager("http://server/site/page.aspx", PersonalizationScope.Shared);

                SPLimitedWebPartCollection wpCol = wpManager.WebParts;

     

                foreach (System.Web.UI.WebControls.WebParts.WebPart webp in wpCol)

                {

                    //Let's assume we want to put the audience on a webpart with title "AUDIENCE WebPart"

                    if (webp.Title == "AUDIENCE WebPart")

                    {

                        webp.AuthorizationFilter = GetAudienceId("Test and temp").ToString() + ";;;;";

                        wpManager.SaveChanges(webp);

                        break;

                    }

                }

            }

        }

    }

    As you see, the AuthorizationFilter ENDS with 4 semicolons (";;;;"). According to Mart's blog, when using the name of the audiences, this string STARTS with these semicolons, but as said before, I did not get this to work properly ...

    After running this, the webpart "AUDIENCE WebPart" will be targetted to our Test and temp and only the users compiled in this audience will see the webpart on the page :)

     

  • Microsoft released External Collaboration Toolkit for SharePoint

    Recently Microsoft released a final release of the External Collaboration Toolkit for SharePoint.

    This toolkit allows an "easy" implementation of things like form based login, password reset, ...

    Organizations need tools to simplify the process of collaborating with people outside their firewall. Currently, many such organizations collaborate using methods that are not efficient or secure, such as unencrypted e-mail and instant messaging. The External Collaboration Toolkit for SharePoint includes software and guidance to quickly and easily deploy a collaboration environment based on SharePoint Products and Technologies that you can use to collaborate with partners across the Internet.

    This solution accelerator, which is primarily targeted at mid-sized and smaller organizations, lets project teams manage their own collaboration sites and invite external users to share documents that are centrally located on a SharePoint site inside the firewall. Internal users access the collaboration site through an internal URL and are authenticated against the organization’s Active Directory® domain, whereas external user access the site through the Internet, log on using a form, and are authenticated against a separate Active Directory Application Mode (ADAM) directory that contains only external users.

    Solution features include a setup wizard to automate the setup process, Web Parts to automate the process of provisioning new site collections and new users, form-based logon for external users, and automated password reset functionality.

    I haven't had the chance to test it thorougly, but from what I read, it does sound promising...

    To download the toolkit: go HERE 

     

    Edit: In attachment, you can find a WhitePaper about the External Collaboration Toolkit 

    Posted Mar 05 2008, 08:48 AM by nsevens with no comments
    Filed under:
  • Passed the 70-541 exam

    Friday afternoon I went to take the 70-541 - WSS 3.0 Application Development exam.

    It took me about an hour to complete it entirely, and I passed with 918/1000, which I think isn't all that bad :)
    Thanks goes to Ted Pattison and Daniel Larsson for their great book Inside Microsoft Windows SharePoint Services 3.0, which turned out to be a great help for preparation!

    So, gratz to me! And still wondering what my next exam is going to be ... ;)

    Posted Mar 03 2008, 02:25 PM by nsevens with 8 comment(s)
    Filed under: ,
  • Requesting SharePoint User Profiles

    A couple weeks ago I had to call SharePoint user profile properties using code. However, it took me quite some searching to make it work properly (but that might just be me?).

    Anyway, I thought I'd just share the snippet with the rest of the world, so here you go :)

    using System.Web;

    using Microsoft.SharePoint;

    using Microsoft.Office.Server.UserProfiles;

    using Microsoft.Office.Server;

     

    namespace Nick.Blog

    {

        public class RequestUserProfile

        {

            public UserProfile GetUserProfile()

            {

                using (SPSite site = new SPSite("http://myserver.com"))

                {

                    using (SPWeb web = site.OpenWeb())

                    {

                        ServerContext context = ServerContext.GetContext(web.Site);

                        UserProfileManager upMgr = new UserProfileManager(context);

     

                        UserProfile profile = upMgr.GetUserProfile("accoutName");

     

                        return profile;

                    }

                }

            }

        }

    }

    You'll probably need to run this code in Elevated privileges, as not everyone can call another user's profile, but I guess you get to the point with this one ...

    Having this profile, you can request all kinds of information from the user, including custom defined properties of the user profile.

    Hope it helps someone ;)

  • Adding custom webparts in a Sharepoint Site Definition

    For many among us, site definitions are a quite unexplored feature of sharepoint.
    However, it makes it really easy to add content to custom sites!

    Adding a webpart didn't turn out that easy, and especially not when there are custom properties to be set for it ...

    First of all, let's take a look at how the included Sharepoint webparts are added to a page.

    E.g: Titlebar webpart

    <AllUsersWebPart WebPartZoneID="TitleBar" WebPartOrder="1">

      <![CDATA[

              <WebPart xmlns="http://schemas.microsoft.com/WebPart/v2" xmlns:twp="http://schemas.microsoft.com/WebPart/v2/TitleBar">

                    <Assembly>Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly>

                    <TypeName>Microsoft.SharePoint.WebPartPages.TitleBarWebPart</TypeName>

                    <FrameType>None</FrameType>

                    <Title>My Procedure</Title>

                    <twp:HeaderTitle>Param&#232;tres g&#233;n&#233;raux du dossier en cours ET en fin d'enqu&#234;te au SP</twp:HeaderTitle>

                    <twp:HeaderCaption></twp:HeaderCaption>

              </WebPart>

              ]]>

    </AllUsersWebPart>

    So, this definition defines the following:

    • The webpart zone where the webpart will be placed in will be TitleBar (WebPartZoneID property), and the order will be 1 (WebPartOrder)
    • The webpart XMLNS (XML namespace) and a namespace for the twp: tag.
    • The assembly name of the webpart
    • The classname of the webpart (typename)
    • And then some properties like title, frametype, headertitle, ...

    Now, however when we create our own webpart in .Net 2.0, we do not need to use the http://schemas.microsoft.com/WebPart/v2 namespace, but the http://schemas.microsoft.com/WebPart/v3 one !

    Now, actually the easiest way to find out the correct webpart definition, is to go look for it in Sharepoint itself.

    After you installed the webpart in the sharepoint webpart gallery, you can go look for it in the Site Settings.

    Site settings --> Webparts (under the Galleries section).

    Click the Edit button/image next to the webpart you want to add in the site definition, and then click View XML.

    The XML you get, is the definition that has to be included in the CData part of your page!
    So, for example you would get:

    <AllUsersWebPart WebPartZoneID="Right" WebPartOrder="1">

      <![CDATA[

        <webParts>

          <webPart xmlns="http://schemas.microsoft.com/WebPart/v3">

            <metaData>

              <type name="Nick.Sevens.Blog, WebPartExample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=abc1234567890" />

              <importErrorMessage>Error importing the Web Part.</importErrorMessage>

            </metaData>

            <data>

              <properties>

                <Property Name="Description" Value="Custom webpart added in the site definition." />

                <property name="ChromeType" type="chrometype">TitleOnly</property>

                <property name="TextToDisplay" type="string">Testing the custom property</property>

                <property name="Title" type="string">Custom webpart example</property>

              </properties>

            </data>

          </webPart>

        </webParts>

      ]]>

    </AllUsersWebPart>

    When implementing this, a custom webpart (Nick.Sevens.Blog.WebPartExample) will be loaded on the screen.

    Also, I included a custom property TextToDisplay, which is defined in the Webpart code like this:

    [DefaultValue("")]

    [Personalizable(PersonalizationScope.Shared), WebBrowsable, WebDisplayName("Text to display"),

    WebDescription("The text to display in the custom webpart.")]

    public string TextToDisplay

    {

        get { return _textToDisplay; }

        set { _textToDisplay = value; }

    }

    This way, the property TextToDisplay of the Custom Webpart is accessible through the site definition!

     PS: Do note, that when updating the site definition, the already created pages won't be automatically updated with the changes! This is due to the fact that all webpart information, even if created through a site definition, is stored in the database (for personalization reasons).

  • Changing the locale of a SPWeb

    A little while ago, I was asked to change a SPWeb's locale id through code (or however I managed to accomplish it automatically :p)

    So, I created a really small Feature for it, and when the Feature is activated, to following code runs:

    public void EditLocale()

    {

        using (SPSite site = new SPSite(url))

        {

            using (SPWeb web = site.OpenWeb())

            {

                CultureInfo culture = new CultureInfo("nl-BE");

                web.Locale = culture;

     

                web.AllowUnsafeUpdates = true;

                web.Update();

                web.AllowUnsafeUpdates = false;

            }

        }

    }

    Nothing special as you see.
    Just create a new CultureInfo object (in my case a flemish one), and attach it to the SPWeb.Locale.

    (and don't forget to update your SPWeb object). 

  • Edit Sharepoint security in C# -- "The security validation for this page is invalid"

    I recently had my fair share of trouble when trying to edit the security of a web site through code.

    Every time I executed the code, some error came up. Most of the time, it was either "Access Denied" or "The security validation for this page is invalid".
    Pretty annoying stuff I thought, and searching the web didn't really help all that much ...

    I ran the code using RunWithElevatedPrivileges, but that didn't help much. Neither did the SPWeb.AllowUnsafeUpdates property.

    After searching for a REALLY long time, I found the answer in a blog's comment:
    (http://spiderwool.blogspot.com/2006/07/security-validation-for-this-page-is.html)

    SPSite.WebApplication.FormDigestSettings.Enabled = false

    Finally I had found the solution.
    However, after redeploying the code on a new web application, I suddenly got an Access Denied error when trying to set this property.
    After some searching I found out this was due to the fact I set the application pool to run as Network Service in stead of an administrative account.
    This did fix my problem, however I did not really found out the actual source of the issue ...

    So, taking all this into account, here is an example of how to set a web's security through code:

    public void EditSecurity()

    {

        SPSecurity.RunWithElevatedPrivileges(delegate()

        {

            using (SPSite site = new SPSite(url))

            {

                using (SPWeb web = site.OpenWeb())

                {

                    SPWebApplication webApp = web.Site.WebApplication;

                    webApp.FormDigestSettings.Enabled = false;

                    web.AllowUnsafeUpdates = true;

     

                    SPGroup group = web.SiteGroups["groupname"];

                    SPRoleAssignment roleAssignment = new SPRoleAssignment((SPPrincipal)group);

     

                    SPRoleDefinition roleDefinition;

                    roleDefinition = web.RoleDefinitions.GetByType(SPRoleType.Contributor); // Gets a predefined role definition

                    roleDefinition = web.RoleDefinitions["customRole"]; // Gets a custom defined role definition

     

                    roleAssignment.RoleDefinitionBindings.Add(roleDefinition);

     

                    web.RoleAssignments.Add(roleAssignment);

     

                    web.Update();

                    web.AllowUnsafeUpdates = false;

                    webApp.FormDigestSettings.Enabled = true;

                }

            }

        });

    }

    So, to summarize:

    • Run the code with or with Elevated Privileges.
    • Set the web application's FormDigestSettings to disabled for the time you run your code.
    • Set the AllowUnsafeUpdates of the SPWeb object to true for the time you run your code.
    • Update the web object after executing the code.

    PS: If you would get an Access Denied error at the setting of the FormDigestSettings, and e.g. you cannot change the web application's application pool identity, or you just can't seem to fix it, you can run your code without setting the FormDigestSettings in it. Alternatively, you can disable the page validation in the Web Application's Generel Settings in the Central Administration. To do this, go to Central Administration --> Application Management --> Web application general settings --> Security Validation = Off

    UPDATE: Also take a look at http://epham.wordpress.com/2007/01/22/how-to-fix-security-validation-errors-in-sharepoint-aspnet-page which in some cases also might provide a solution for the issue.

  • Activate features through code

    For a recent project I did, I had to activate a feature programatically.

    I needed some research to achieve this, so I thought I'd just put it in a post. It's only a few lines of code, so nothing hard to do here ...

    Basically, what it does is update the FeatureCollection object of a given Site or Site Collection. Adding a feature to this collection automatically activates it.

    ! Do make sure the feature is already installed in the farm/site/web/... when trying to activate it.

    SPWeb web = new SPSite("http://yourserver.com").OpenWeb();

    SPFeatureCollection featureCollect = web.Features;

    featureCollect.Add(new Guid("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"), true); //True to force activation

    That's all it takes :) 
    Hope it helps someone ...

  • Create custom field types for SharePoint

    In this example we are creating a custom field type which concatenates two text fields (first and last name).

    1. Create a new usercontrol file (customfieldcontrol.ascx). In this usercontrol, add a SharePoint:RenderingTemplate control, and insert whatever you want for your control template.

    2. <%@ Control Language="C#" %>

      <%@ Assembly Name="CustomControls, Version=1.0.0.0, Culture=neutral, PublicKeyToken=xxxxxxxxxxxxxxxx" %>

      <%@ Register TagPrefix="SharePoint" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" Namespace="Microsoft.SharePoint.WebControls" %>

       

      <SharePoint:RenderingTemplate Id="CustomFieldRendering" runat="server">

          <Template>

              <asp:TextBox runat="server" ID="txtFirstName" /> <asp:TextBox runat="server" ID="txtLastName" />

          </Template>

      </SharePoint:RenderingTemplate>


    3. Secondly, create a new class file (customfieldcontrol.cs), which will take the role of codebehind file of the usercontrol. In this class, which will inherit from Microsoft.SharePoint.WebControls.BaseFieldControl, we will override some of the properties and methods to implement our own logic.

    4. using System;

      using System.Collections.Generic;

      using System.Text;

      using System.Web.UI.WebControls;

       

      using Microsoft.SharePoint;

      using Microsoft.SharePoint.WebControls;

       

      namespace CustomControl

      {

          public class customfieldcontrol : BaseFieldControl

          {

              protected TextBox txtFirstName;

              protected TextBox txtLastName;

       

              protected override string DefaultTemplateName

              {

                  get { return "CustomFieldRendering"; }

              }

              public override object Value

              {

                  get

                  {

                      EnsureChildControls();

                      return txtFirstName.Text + "%" + txtLastName.Text;

                  }

                  set

                  {

                      try

                      {

                          EnsureChildControls();

                          txtFirstName.Text = value.ToString().Split('%')[0];

                          txtLastName.Text = value.ToString().Split('%')[1];

                      }

                      catch { }

                  }

              }

              public override void Focus()

              {

                  EnsureChildControls();

                  txtFirstName.Focus();

              }

              protected override void CreateChildControls()

              {

                  if (Field == null) return;

                  base.CreateChildControls();

       

                  //Don't render the textbox if we are  just displaying the field

                  if (ControlMode == Microsoft.SharePoint.WebControls.SPControlMode.Display) return;

       

                  txtFirstName = (TextBox)TemplateContainer.FindControl("txtFirstName");

                  txtLastName = (TextBox)TemplateContainer.FindControl("txtLastName");

                  if (txtFirstName == null) throw new NullReferenceException("txtFirstName is null");

                  if (txtLastName == null) throw new NullReferenceException("txtLastName is null");

                  if (ControlMode == Microsoft.SharePoint.WebControls.SPControlMode.New)

                  {

                      txtFirstName.Text = "";

                      txtLastName.Text = "";

                  }

              }

          }

      }


      Actually, what this code does is:

      Define the ID of the renderingtemplate control in our usercontrol.
      Set the return value to the value we want it to return :), in this case this will be something like Firstname%Lastname.
      Make sure that when you edit an item, the proper values are filled in into the textboxes again.

    5. Next thing to do is to create the Field type class (CustomField.cs) itself. In this class, which derives of one of the base control types from SharePoint (SPFieldText, SPFieldChoice, ...), we will define which control has to be used as template, and which value has to be returned when displaying a list item.

      using Microsoft.SharePoint;

      using Microsoft.SharePoint.WebControls;

       

      namespace CustomControl

      {

          public class CustomField : SPFieldText

          {

              public CustomField(SPFieldCollection fields, string fieldName)

                  : base(fields, fieldName)

              { }

       

              public CustomField(SPFieldCollection fields, string typeName, string displayName)

                  : base(fields, typeName, displayName)

              { }

       

              public override BaseFieldControl FieldRenderingControl

              {

                  get

                  {

                      BaseFieldControl fieldControl = new customfieldcontrol();

                      fieldControl.FieldName = this.InternalName;

                      return fieldControl;

                  }

              }

       

              public override string GetValidatedString(object value)

              {

                  return value.ToString().Split('%')[1].ToUpper() + " " + value.ToString().Split('%')[0];

              }

          }

      }


      Main thing of this code is the GetValidatedString() function. This function defines what is to be displayed when you display an item (in a list view for example).
      In our case, which in our case will be something like LASTNAME Firstname.

    6. Last file to create is the XML File (fldtypes_custom.xml), which will add the custom field type to SharePoint.

      <?xml version="1.0" encoding="utf-8"?>

      <FieldTypes>

        <FieldType>

          <Field Name="TypeName">CustomField</Field>

          <Field Name="ParentType">Text</Field>

          <Field Name="TypeDisplayName">Custom Name Field</Field>

          <Field Name="TypeShortDescription">Custom Name Text Field</Field>

          <Field Name="UserCreatable">TRUE</Field>

          <Field Name="ShowOnListCreate">TRUE</Field>

          <Field Name="ShowOnSurveyCreate">TRUE</Field>

          <Field Name="ShowOnDocumentLibrary">TRUE</Field>

          <Field Name="ShowOnColumnTemplateCreate">TRUE</Field>

          <Field Name="Sortable">TRUE</Field>

          <Field Name="Filterable">TRUE</Field>

          <Field Name="FieldTypeClass">CustomControl.CustomField, CustomControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=xxxxxxxxxxxxxxxx</Field>

          <Field Name="SQLType">nvarchar</Field>

        </FieldType>

      </FieldTypes>


    7. So far so good with creating the files :) Now, all we have to do is put the right files in the right places ...
      Compile your project (or at least both .cs classes) and make sure they are strong named.
      Then, install them in the GAC (%windir%\assembly)

      Next, copy the .ascx file to C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\TEMPLATE\CONTROLTEMPLATES

      And the last file to copy: copy the .xml file to C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\TEMPLATE\XML
    8. That's it. Maybe do an IISRESET, and you can use your newly created Custom Field Type!

    Edit: changed the ShowIn* to ShowOn* (thanks Phil for noticing!)
    Posted Aug 31 2007, 12:35 PM by nsevens with 33 comment(s)
    Filed under: ,

Need SharePoint Training? Attend a SharePoint Bootcamp!

Posts (c) their respective authors. Everything else (c) 2007 SharePoint Experts