Enterprise software development and consulting for various organizations, industries and domains.

Saturday, August 28, 2010

Sharepoint: Create Visitors group programmatically

I noticed interesting thing, when you use unique permissions for a site and want to set up permission groups, SharePoint offers to create Owners and Members group, but Visitors group is inherited from a parent site by default.


And even when I tried to do it programmatically using SPWeb.CreateDefaultAssociatedGroups, it still creates only two groups.

So I ended up with the following code snippet:

var primaryAdmin = web.SiteAdministrators[0];
var groupName = SPResource.GetString("DefaultVisitorGroupName", new object[] { SPHttpUtility.HtmlEncode(web.Title) });
var groupDescription = SPResource.GetString("DefaultVisitorGroupDescriptionRichText", new object[] { SPHttpUtility.HtmlEncode(web.Title), SPHttpUtility.HtmlUrlAttributeEncode(SPHttpUtility.UrlPathEncode(web.ServerRelativeUrl, false)) });
web.SiteGroups.Add(groupName, primaryAdmin, null, groupDescription);
web.AssociatedVisitorGroup = web.SiteGroups[groupName];

var roleAssignment = new SPRoleAssignment(web.AssociatedVisitorGroup);
web.RoleAssignments.Add(roleAssignment);
roleAssignment.RoleDefinitionBindings.Add(web.RoleDefinitions["Read"]);
web.AllowUnsafeUpdates = true;
roleAssignment.Update();

It creates Visitors group and gives it Read permissions.

Saturday, August 21, 2010

How to set managed metadata field value through code

Recently one of our clients reported an issue that setting taxonomy field value through code doesn't update the field value.

We noticed interesting fact, that even though value is stored in a field, UI control still shows an old value. After spending some time in a reflector digging in Microsoft.SharePoint.Taxonomy.dll we found out a way how SharePoint stores managed metadata and enterprise keywords values.

Guys from Microsoft use pretty simple approach, they store technical information in a hidden field created with the following algorithm.

string str;
string str2;
int num = 0;
while (fields.ContainsField(base.Title + "_" + num.ToString(CultureInfo.InvariantCulture)))
{
    num++;
}
if (this.IsKeyword)
{
    str = "TaxKeywordTaxHTField";
    str2 = "TaxKeywordTaxHTField";
}
else
{
    str = base.Title + "_" + num.ToString(CultureInfo.InvariantCulture);
    str2 = base.InternalName + "TaxHTField" + num.ToString(CultureInfo.InvariantCulture);
}

Where str and str2 are DisplayName and InternalName of a hidden field respectively.

So, in order to set taxonomy field value, either Managed Meatadata or Enterprise Keywords field, we used this piece of code:

var currentItem = SPContext.Current.Item;

// set field value
item[fieldName] = currentItem[fieldName];

// find hidden field and set it's value
SPField htField = GetHTField(item, fieldName);
item[htField.StaticName] = currentItem[htField.StaticName];

GetHTField method is shown below:

private SPField GetHTField(SPItem item, string fieldName)
{
    foreach (SPField field in item.Fields)
    {
        if (field.StaticName.StartsWith(fieldName) && field.StaticName.Contains("TaxHTField"))
        {
            return field;
        }
    }
    return null;
}

This code was used in a code behind of a layout page with FormComponent control thus we were able to get current item from SPContext.

In the next post, for those who are not able to use values from SPContext's current item, I will tell about format of a taxonomy hidden field value and show how to calculate it manually.

Tuesday, August 17, 2010

Using Form Web Part and Data View Web Part to search and filter data

Present data in a grid view with an ability to filter records by a set of fields is a common requirement in an enterprise world. Built-in SharePoint list view filters and search cover basic scenarios, but filtering capabilities are often insufficient to effectively search over particular metadata. For instance,

  • a user might need to search only by particular columns;
  • search results have to be limited by the certain lists;
  • portal is internet facing and configured in a lockdown mode;
  • search has to be performed over external data source;

This is where Data View web part may help, it provides a lot of options for customization since it uses XSLT templates. Very good article on how to use Data View web part for filtering was posted by Phillip Wicklund.

So, author used two connected web parts to feed Data View web part with a values from the Form web part, though every consecutive search filter previously filtered data. It happens because a Data View web part keeps parameter values and variables between postbacks.

A Data View web part thinks that no parameter is provided when value is equal to an empty string. To walk around this issue we used a little script. What we have to do is to let Data View web part think, that value has been changed. Let's replace empty string with some token before submit a form and then substitute it back to an empty string before performing the actual search.

Assume we would like to filter data by "County" and "Zip Code" fields, then we define Data Form web part containing two text inputs accordingly:

<table>
  <tr>
    <td>Zip Code</td>
    <td><input type="text" id="qZipCode" name="qZipCode"></td>
  </tr>
  <tr>
    <td>County</td>
    <td><input type="text" id="qCounty" name="qCounty"></td>
  </tr>
  <tr>
<table>

Place Submit and Reset buttons below the input fields:

<input type="submit" name="search" value="Search" onclick="javascript:beforeSubmit();_SFSUBMIT_"/>&nbsp;&nbsp;
<input type="reset" value="Reset" name="reset" onclick="doReset()"/>

The trick goes with the onclick handler of the Submit button. Add the following script block to the Data Form web part. It replaces empty string with the space.

<script>
 var ctls = ['qZipCode', 'qCounty'];
 
 function trimControls() {
  for(var i=0;i<ctls.length;i++) {           
   var ctl = document.getElementById(ctls[i]);
   if (ctl.value == ' ') {
    ctl.value = '';
   }
  }
 }
 trimControls();
 function beforeSubmit() {
  for(var i=0;i<ctls.length;i++) {           
   var ctl = document.getElementById(ctls[i]);
     if (ctl.value == '') {
      ctl.value = ' ';
     }
    }
 }
 function doReset() {
  window.location.href = window.location.href;
 }         
</script>

Then we have to remove spaces in the Data View web part. We use normalize-space function.

<xsl:variable name="qZipCodeU" select="normalize-space($qZipCode)" />
<xsl:variable name="qCountyU" select="translate(normalize-space($qCounty), $smallcase, $uppercase)" />
<xsl:variable name="Rows" select="/dsQueryResponse/NewDataSet/Row[$search='off'][contains(@ZipCode,$qZipCodeU)][contains(translate(@County,$smallcase,$uppercase),$qCountyU)]" />

Notice $search='off' condition, it allows to perform search only if a user clicks the Search button.

Another nice option of Data View web part is an ability to use various ASP.NET DataSource controls (generally, any control which implements IDataSource), for example:
  • SPDataSource for searching over SharePoint lists;
  • SPSqlDataSource for searching over external database;
  • BdcDataSource for searching within BDC applications;
  • SoapDataSource to filter data from web services.