Friday, November 25, 2011

SharePoint Datasheet View - The selected cells are readonly

This happens because:
  1. The field is a read-only field
  2. The view has folder-content types
  3. You installed Office 2007 SP3

For the first issue you can use the tool "SharePoint Manage". Drill down to the field and change the property "ReadOnlyField"


SharePoint Manager - Change Read-Only property

For the second issue modify/create your list view and select in the Folder section "Show all items without folder"

Modify View - Show item without folder
The Solution for the third issue is to change the field type from Enhanced rich text (Rich text with pictures, tables, and hyperlinks) to Rich text (Bold, italics, text alignment).

There seems to be also a fix from Microsoft  http://support.microsoft.com/kb/2598128 

SharePoint Enterprise Content Types and Contenty Type Hubs

With Content Type Hubs in SharePoint 2010 you can now easily distrubute your content types over all webapplications and sitecollections. Now you can define a single sitecollection in which you manage your content types. All other sitecollections can consume from this site.

1. Create Content Type Hub
Before you can use Enterprise Content Types you have to define a Content Type Hub. The Content Type Hub is a single arbitrary sitecollection. The probably best way is to create a new sitecollection with the blank site template for this purpose.

2. Configure Service
After you have created the sitecollection you have to tell the service where to consume the content types. The service which manages the enterprise content types is the "Managed Metadata Service". If you dont't have this service, you have to create it first. If the service already exists follow these steps:
  1. Go to central administration
  2. Application Management
  3. Service Application - Manage service applications
  4. Change Metadata Service properties
  5. Click on the "Managed Metadata Service" (not on the link only so that the line is marked) and than on "Properties in the Ribbon"
  6. In the dialog scroll down to the "Content Type hub" and enter the URL of the sitecollection from which to consume the content types
  7. Activate consume
  8. Close the dialog and now mark the "Managed Metadata Service Application Proxy" and click on "Properties"
  9. Click on checkbox "Consume content types from the content type gallery...". This checkbox is usually greyed out if you don't have defined a content type hub.
3. Create Content Types

Now go to the Content Type Hub Site Collection and create new content types. 


Create new content types
After you have created a new content type go to the content type settings.

List of Content Types

Content Type settings


Click on "Manage publishing for this content type". Usually the "Publish" radiobutton is check when the content type is never published before. When you enter the setting the first time and you want to publish this content type, click on "OK". When the content type is published the both other content types will be activated.


4.Wait or start the timer jobs

The content types are published by SharePoint timer jobs. You can wait until the timer jobs published the content types or you can start the timer job manually in the Central Administration. Therefore open the Central Administration -> Monitoring -> Review job definitions.

Manage Timer Jobs

Search the two jobs:
  • Content Type Hub
  • Content Type Subscriber
The 2 Timer Jobs
Click on the Timer Job and activate "Run Now" on both jobs.

4. Check Publishing

Now go to another site collection to check if the publishing has worked. Go to the sitecollection site settings and click on "Content type publishing" in "Site Collection Administration". If the publishing worked you'll see the published content types listed there. Now you can select anywhere in the site collection the new content types.

Wednesday, October 12, 2011

Limiting the SharePoint People Picker with PowerShell

We had the problem that the searching after people in the Active Directory took very long. If you don't limit the people picker in SharePoint it searches the whole domain and all forests within. This can be very time consuming when the picker tries to resolve the names ( In out company about 1 minute).

Therefore we decided to limit the people picker by domain. The problem is that you can define this limitation only on sitecollection level. This means that you have to do that each time you create a new sitecollection.

I wrote a powerhell script which loops throught alle sitecollections and restricts the one, which do not already have a limitation.

 #Powershell for Changing People Picker Settings
  
 #Author I.Bikmaz
 #Add SharePoint SnapIn
  
 $snapin = Get-PSSnapin | Where-Object {$_.Name -eq 'Microsoft.SharePoint.Powershell'}
  
 if ($snapin -eq $null) {
   Write-Host "Loading SharePoint Powershell Snap-in"
   Add-PSSnapin "Microsoft.SharePoint.Powershell"
 }
  
 Get-SPWebApplication | Where {$_.Url -eq "http://youwebapp/"} | Get-SPSite -Limit ALL | ForEach-Object {
   # Choose the sites with empty setting
   Get-SPSite $_.Url | Where {$_.UserAccountDirectoryPath -eq ""} | ForEach-Object {  
     Write-Host "Creating People Picker Setting for '"$_.Url"'"  
     Set-SPSite $_.Url -UserAccountDirectoryPath "DC=xxx,DC=xxx,DC=com"  
     Write-Host "done..."  
     Write-Host ""  
   }
  
 }
  


Using PowerShell for restricting the people picker


In SharePoint 2007 generally you have used the stsadm command to set the properties for the people picker (Look here for the list of stsadm commands for the people picker).

In SharePoint 2010 you can use PowerShell. But: You have to code multiple lines for what the stsadm command could do in one line. Therefore most people are still using stsadm commands for limiting the people picker.

The example above was a setting for a single site collection. You can also set limitations on webapplication level. To see which settings can be manipulated open your SharePoint Management Shell and type:

C:\>$webapp = Get-SPWebApplication http://yourwebapp
C:\>$webapp.PeoplePickerSettings





An easy way is to restrict your people picker to a single domain. This boost your picker if you have lots of subdomains.

Here you can find all properties and their types. The SearchActiveDirectoryDomains property has the type SPPeoplePickerSearchActiveDirectoryDomain. So you have to create an object of this type to manipulate the object.

For an example look here.



Here I have some useful links:

Friday, October 7, 2011

Drill Down with the SharePoint Chart WebPart

There is a new webpart in SP 2010, the chart webpart, which is very impressing. You can connect it to SharePoint List and WebParts.

Today I wanted to drill down the pages when I click an element of the webpart. It seems that Microsoft bought this webpart from Dundas, so you have to look the syntax there

http://support2.dundas.com/Default.aspx?article=1132
http://support2.dundas.com/Default.aspx?article=1205

Follow the steps:


Step I

Step II
In step 3 click "next" also in step 4.

In step 5 click on the "Hyperlinks and Tooltips" tab. Enter following Url to the fiels:

http://yourdomain.com/yoursite/yourlist/allitems.aspx?Filterfield1=Title&FilterValue1=#LABEL

whereas #LABEL is a keyword mentioned in the support links above

Hiding the title Field in SharePoint the simple way

I find these two ways as the simplest:

Using the SharePoint Manager

Every SharePoint Admin should have the Sharepoint Manager. Just download it to you SharePoint Server and open it. No installation is needed.

Drill down to the list you want to modify. If you click on the list all fields are show. You will see 4 Title fields. Select the Title field with the Internal Name "Title". You can check the internal name in the properties window on the right if you click on a field.

Now change the values for "Hidden" to true and "Required" to false.




Using Powershell

I don't reinvent the wheel so look at "Phil Childs" site. There is a simple PowerShell script you can use to modify each field and of course also the title field.

Wednesday, September 7, 2011

"Access Denied" when editing a page as site owner

I am in the site owners group and have full permissions. But when I try to edit a page an access denied error occures. I can checkin and checkout but cannot edit the page. If I put me into the site collection admin group, the page can be edited.

This happened to me today and it took me 2 hours to find the problem. I was aware that the problem was not that I have not the correct rights, in fact I assumed that there is something in the page that was not published or something I don't have the rights to edit.

Following could be possible:

  1. I have a WebPart on the page, that tries to read an item or site to which I don't have access.
  2. I have a link on the page to an unpublished item
  3. I have problems with an unpublished page layout
  4. Problems with the master page
  • Point 1 was not the issue, because I didn't has custom or any webparts.
  • Point 2 was also not the problem. I took everything out of the page, but the problem still occured
  • Point 3 was also not the reason for the error. I checked the layouts gallery
  • Point 4 was also not the problem. The masterpage was o.k. But then I checked the library permissions for the masterpage gallery list. The permissions were broken. I adjusted the permissions and la voila the page could be edited.

Sunday, July 24, 2011

MatchPoint - Configuration of Tag-Icons

Each Tag in MatchPoint can have its own icon. The configuration of the icons can be found in the MatchPoint Administration sitecollection, which hosts the MatchPoint instance.

MatchPoint Instance Administration in the Site Settings


The icons for the tags must be located in the images subfolder of 14hive folder in each web frontend server. The easiest way to open the images folder is to paste the following command line into the explorer bar:
"%COMMONPROGRAMFILES%\microsoft shared\web server extensions\14\Template\Images"
The images will not be resized so if you create your own images please ensure that they have the right format,
16x16 I think. Within the images folder you can create your own subfolder structure for your images.

Images folder in 14hive


As an example we have the following Tag structure. Sprache (Language) is a toplevel tag. It has 3 other child tags : German (Deutsch), French (Französisch) and Italian (Italienisch).

Taxonomy

After you've copied all images to the images folder, you need to create a mapping between the tags and the images. Therefore open the "Site Settings" page of the root site in the MatchPoint Administration sitecollection. Under "MatchPoint Instance Administration" open the "MacthPoint Configuration" link. Within the configuration settings click on "Icons" to open the tag icon configuration and create a "Mapping".


In the above configuration we want to have a french flag icon for the french tag. Because "French" is a sub tag of "Language" we have to make a XPath Query like "Language/French". Use the tag names for the selection and not the tag keys.

The filename is the path to the image file in the sharepoint images folder.

After creating the mappings, you have to run the Matchpoint Tagimport timer jobs , so MatchPoint can create the assosiations between the tag and the icon.


You can also use asterisks in your pattern. If you for instance use the following pattern "Links/**", each child tag unter "Links" will have the same icon. In XPath this pattern means "select recursively all child items".


Pattern with asterisk
Result of the selection pattern

When you use only a single asterisk in your pattern, like "FileExtension/*", only the first child tags directly below the tag will inherit the icon.


You can also use "{0}" placeholder in the filename to create dynamic mappings. The "{0}" placeholder will be replaced by the tagname. If you for example have tags with the name "green, red and yellow" to display a status and you saved tag images with the name status_red.gid, status_green.gif and status_yellow.gif in the 14hive images folder, you can use "status_{0}.gif" for the filename .

Tuesday, May 17, 2011

Visual upgrade error in SharePoint 2010

I got following error when I made a visual upgrade:

"One or more field types are not installed properly." (in German: Mindestens ein Feld ist nicht richtig installiert. Wechseln Sie zur Listeneinstellungsseite, um diese Felder zu l√∂schen.)

The error occurs due to a problem in the hidden relationshiplist. The entries are not updated properly. See Problems with visual upgrade.

The solution for this problem is easy: Install the newest Cumulatives Updates.

Most viewed documents in SharePoint 2010

The Task

You want to see the top 10 most viewed documents in a library

The Answer

1. Enable the two reporting features in your site collection features.
Enable the reporting features


2. Go to the list and click on "Library Settings" in the ribbon

Library Settings


3. Click on "Information management policy settings" under "Permission and Management"
Information management policy settings


4. Click on document

5. Activate the checkbox "Enable Auditing" and select "Opening or downloading documents, viewing items in lists, or viewing item properties"
Enable Auditing


Now the auditing is enabled and each time when an item in the doclib is viewed a log entrie is generated. You can limit the size of the lib in the site collection settings under "Site collection audit settings" in the group "Site Collection Administration". Here you can specify that the log should be holded for a period of time.


6. Create a custom webpart or control. Here is the sample code to get the entries you want

 using (SPSite site = new SPSite(mUrl))  
 {  
   using (SPWeb web = site.OpenWeb())  
   {  
     SPList list = web.Lists[mListName];  
     SPAuditQuery spQuery = new SPAuditQuery(site);  
     spQuery.RestrictToList(list);  
     SPAuditEntryCollection auditCol = site.Audit.GetEntries(spQuery);      
     // Getting Audits  
     foreach (SPAuditEntry entry in auditCol)  
     {  
       if (entry.ItemType == SPAuditItemType.Document && entry.Event == SPAuditEventType.View)  
       {                
         // Some Code to cumulate results or linq perhaps  
         // by entry.ItemIds in to an other list or array  
       }  
     }  
     // Show top ten  
     SPListItem item = null;  
     foreach (Guid guid in topTenList) {  
       item = list.GetItemByUniqueId(guid);  
       Console.WriteLine("Item: {0}", item.Name);  
     }  
   }  
 }  

You have to cumulate the results and display the top ten. This is at the same time the disadvantage of this solution. If you have large lists, the accumulation can be time consuming. A solution for that can be a timer job which writes the nr into hidden field in the library or an object cache.

Additional info

SharePoint offers a webanalytics webpart out of the box. It is within the "Content Rollup" webparts group. To use the webanalytics webparts, the webanalytics service must be started. The webanalytics webparts uses a webanalytics service proxy to get the data. Unfortunately the proxy class is internal. I couldn't found any API to access the webanalytics data, perhaps Microsoft will provide it later. The reporting db offers some stored procedures, but they were too complicated for me.

Tuesday, May 10, 2011

I got the following error as I tried to run a console application to get data from SharePoint 2010:

the web application at ... could not be found. verify that you have typed the url correctly. if the url should be serving existing content, the system administrator may need to add a new request url mapping to the intended application.

The problem is that the default settings for a console project are .Net Framework 4 and Platform target X86. Change these settings to .Net Framework 3.5 and x64 and everthing will work fine.

Tuesday, May 3, 2011

jQuery and SharePoint 2010 conflict

When you try to use jQuery with SharePoint 2010 you could get problems in some browser like IE. This is caused because of conflicts with the Microsoft JavaScript selector "$", which is the same variable as the jQuery selector.

To avoid conflicts you can easily use the noConflict method of jQuery and change the selector for jQuery.

 var $j = jQuery.noConflict();  
 $j(document).ready( function() {    
   $j("#content").addClass("...");  
 });  

For more information see jQuery.noConflict.

Friday, April 29, 2011

SharePoint Migration of Publishing Content Types

When you migrate your Publishing Content Types from SharePoint 2007 to 2010 you have to do some changes to you xml definition. There are two new attributes to consider when creating or migrating Content Types:
  1. Inherits
  2. Overwrite

The Inherits attribute

When you p.e. migrate a custom "publishing content type" that derives from the default page content type you must use the inherits attribute to display your custom fields.

Now take a look what msdn says:

Inherits
Optional Boolean. The value of this attribute determines whether the content type inherits fields from its parent content type when it is created.

If Inherits is TRUE, the child content type inherits all fields that are in the parent, including fields that users have added.

If Inherits is FALSE or absent and the parent content type is a built-in type, the child content type inherits only the fields that were in the parent content type when SharePoint Foundation was installed. The child content type does not have any fields that users have added to the parent content type.

If Inherits is FALSE or absent and the parent content type was provisioned by a sandboxed solution, the child does not inherit any fields from the parent.

The result is:

 <ContentType ID="0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF390027E3522FA4F2451e8AF57D3CC9D40BF1"  
  Name="Project.Contentpage"  
  Group="Project Publishing Contenttypes"  
  Description="Contentpage"  
  Inherits="TRUE"
  Version="0">  
 <FieldRefs>  
  <FieldRef ID="{7B367A27-2052-4b43-A47F-D057FF367A65}" Name="PageTitle" />  
  <FieldRef ID="{7B72B83A-B2EA-479D-A927-50A62DCD1B7D}" Name="HtmlField1" />  
  <FieldRef ID="{2677CEB5-4A8D-4717-B904-BA8962A579EF}" Name="HtmlField2" />  
 </FieldRefs>  
 </ContentType>  

I don't really understand for what the attribute really stands for. I mean we have a complex mechanism of guids to create inheritance for content types and you have to say extra that you want "inherit" fields. Perhaps someone can help me here. I'll update the post when I find more info.

I examinde the contenttype table in the database and found out that when you create a content type with inherit=true, your content type gets unghosted. That means SharePoint write the content type directly into the database. So the information for the definition is picked from the database table and not from the xml definition. I don't no really know if this is good or bad, but when a content type is created by the SharePoint gui or by code, it is also written into the database. The only way to get ghosted content types are by feature installation.

When you omit the inherit attribute your content type is ghosted. The definition for the content type is picked from the xml file. Furthermore when you have a content type and the parent content type is a built-in content type (like publishing contenttype) your content type inherits all fields from the publishing contenttype but not the one you added. He? yeah that is exactly what the definition of inherits says.

When you migrate your Publishing Content Types from SharePoint 2007 to 2010 you have to do some changes to you xml definition. There are two new attributes to consider when creating or migrating Content Types:

The Overwrite attribute


Stefan Stanev has a great post about the new overwrite attribute in his blog.

The overwrite attribute solves some problems with content types which gets updated or modified. Because there are different types of content types, like site content types, list content types, ghosted and unghosted content types, modifiying content types is complicated. Therefore plan content types carefully so that you never have to do that.

Useful links

Wednesday, March 23, 2011

Creating a Custom Validator Control in .Net

The ASP .Net has its own form validation framework. This framework consists of:
  • Base Validation Class, the BaseValidator
  • ASP Validation Controls
  • A Clientside Validation JavaScript Framework
The "Base Validation Class" is the base of all validation controls and is also used to create own custom controls. If you want to create your own validation control, you have to inherit from the base class.

The ASP validation controls are basic validation controls provided by Microsoft. These are:
  • RequiredFieldControl: Checks an input field if its empty
  • CompareValidator: Compares the value of a field with another value. This can be a value in another field or a constant value.
  • RangeValidator: Checks if the value in a form field is within a range
  • RegularExpressionValidator: RegularExpression validators are used to check if the value in a control matches a pattern defined by the regular expression.
  • CustomValidator: If none of the Validators are useful for a purpose, you can create a custom validator. You must handle a customvalidator in the code-behind of your site (aspx page). This means you write backend code for the validator.
All validation controls can validate a field in the frontend or in the backend. Therefore they need also an implementation in JavaScript. Microsoft provides a JavaScript solution for its own validation controls.

Creating a Custom Validation Control

When I talk about custom validation controls, I don't mean the standard "CustomValidator" or an implementation of it. I mean an independent validator like the reqularexpressionvalidator for a specific purpose, which can be used in multiple pages. As an example you want to create a date validator which has attributes to force the user to enter only dates in the past or in the future.

Before you begin to write code you should know that you have to write:
  • Logic for the Backend Validation
  • Logic for the Frontend Validation. This must correspond 1to1 to the backend logic.
  • Logic for the Frontend Validation Framework. The validation framework must know which code to use for validation when a custom validation control is created.


Understanding the client validation

As an example we have two form fields to validate. The first field is a date field and the second one a text field. The value of the date field must be in a date format whereas the text field should not be empty. Here is the simple code: (The date validation is not correct, it is just an example)

 <asp:Label ID="lblBirth" Text="Birthday:" runat="server" /><br />  
 <asp:TextBox ID="txtDate" Width="80" MaxLength="10" runat="server" />     
 <asp:RegularExpressionValidator runat="server"  
           ControlToValidate="txtDate"  
           ErrorMessage="Please check your date format (19.05.2001)"  
           EnableClientScript="true"  
           ValidationExpression="\d\d\.\d\d\.\d\d\d\d" />  
 <div class="spacer"></div>  
 <asp:Label ID="lblName" Text="Name:" runat="server" /><br />  
 <asp:TextBox ID="txtName" Width="200" runat="server" />  
 <asp:RequiredFieldValidator runat="server"  
           ControlToValidate="txtName"  
           ErrorMessage="Please enter a name"  
           EnableClientScript="true" />  

The clientvalidation is enabled, so the validation is made in the browser. But how is this done?
When you insert a validation control into your aspx page the validator inserts JavaScript Code
to the end of you site. When you look at the page source you'll find something like this:

 var Page_Validators = new Array(document.getElementById("ctl02"), document.getElementById("ctl03"));  

 var Page_Validators = new Array(document.getElementById("ctl02"), document.getElementById("ctl03"));  
 var ctl02 = document.all ? document.all["ctl02"] : document.getElementById("ctl02");  
 ctl02.controltovalidate = "txtDate";  
 ctl02.errormessage = "Please check your date format (19.05.2001)";  
 ctl02.evaluationfunction = "RegularExpressionValidatorEvaluateIsValid";  
 ctl02.validationexpression = "\\d\\d\\.\\d\\d\\.\\d\\d\\d\\d";  


The line

 var Page_Validators = new Array(document.getElementById("ctl02"), document.getElementById("ctl03"));  

creates an array "Page_Validators" that contains some DOM node. These Dom nodes are the rendered html of the validaton messages:

 <span id="ctl02" style="color:Red;visibility:hidden;">Please check your date format (19.05.2001)</span>  
 <span id="ctl03" style="color:Red;visibility:hidden;">Please enter a name</span>  

The following line creates a variable ct102 which contains a reference to the DOM nodes. In JavaScript everything is handled with references. Therefore a change in the variable ct102 changes the DOM node with the id "ct102". The variable ct102 is extended with the properties:
  • .controltovalidate: These property contains the id of the input field
  • .errormessage: This is the error message to display
  • .evaluationfunction: This is the name of the JavaScript function to call when validating. For the standard validators the functions are implemented in the JavaScript Validation Framework.
  • .initialvalue: If a field has a initial value, it is handled like the field does not contain a value.
These 4 properties "controltovalidate, errormessage, evaluationfunction and initialvalue" are used by all validators. But you can also extend these properties for your own custom validator, like the RegularExpressionValidator does with the "validationexpression" property.

When you click the send button to submit your form, a validation function "Page_ClientValidate" is called which iterates through the "Page_Validators" array and calls the function defined in the "evaluationfunction" property such as the built-in evaluation function for the RequiresFieldValidator:

 function RequiredFieldValidatorEvaluateIsValid(val) {  
   return (ValidatorTrim(ValidatorGetValue(val.controltovalidate)) != ValidatorTrim(val.initialvalue))  
 }  

These evaluation functions always return true or false. The returned value is saved in the ".isvalid" property of the validator. Also a global variable "Page_IsValid" is created which returns a boolean value. Is "Page_IsValid" valid is false the submit of the form is cancelled and the errors in the "errormessage" property of the validators are displayed.



Creating the Backend Code

When creating a custom validator you must inherit from the "BaseValidator" class. This class offers multiple properties and functions to override for creating a valid validator. The most important methods are:
  • bool ControlPropertiesValid(): You can use this function to check if the properties of your validator are valid.
  • bool EvaluateIsValid(): The real validation happens in this method.
and the most important properties:
  • ControlToValidate : ID of the control to validate
  • ErrorMessage: The error message string
  • EnableClientScript: If client validation is enabled or not

There are lot of more but these are enough for our example. As an example we create now a date validator:

1:  namespace ValidatorDemo.Validator  
2:  {  
3:  public class DateValidator : BaseValidator  
4:  {  
5:      #region Private Fields  
6:      private String mValueToValidate;  
7:      private Control mControlToValidate;  
8:      #endregion  
9:      #region Lifecycle Overrides  
10:      /// <summary>  
11:      /// Initialize the client script properties  
12:      /// </summary>  
13:      /// <param name="pE">Not used</param>  
14:      protected override void OnInit(EventArgs pE)  
15:      {  
16:          if (this.EnableClientScript)  
17:              RegisterClientValidationScript();  
18:      }  
19:      /// <summary>  
20:      /// Checks if the properties are correct  
21:      /// </summary>  
22:      /// <returns>True if properties are o.k.</returns>  
23:      protected override bool ControlPropertiesValid()  
24:      {  
25:          Control findControl = this.FindControl(this.ControlToValidate);  
26:          if (findControl is TextBox)  
27:          {  
28:              TextBox txtBox = ((TextBox)findControl);  
29:              mControlToValidate = txtBox;  
30:              mValueToValidate = txtBox.Text;         
31:              return true;  
32:          }  
33:          else  
34:          {  
35:              return false;  
36:          }  
37:      }  
38:      /// <summary>  
39:      /// Validates the date  
40:      /// </summary>  
41:      /// <returns>True if evaluation is valid, else false</returns>  
42:      protected override bool EvaluateIsValid()  
43:      {  
44:          try  
45:          {  
46:              string sDate = mValueToValidate;  
47:              if (sDate == string.Empty) return true;  
48:              CultureInfo ci = new CultureInfo("de-DE");  
49:              DateTime date = DateTime.Parse(sDate, ci);  
50:          }  
51:          catch  
52:          {  
53:              return false;  
54:          }  
55:          return true;  
56:      }  
57:      #endregion  
58:      #region Helper  
59:      /// <summary>  
60:      /// The Method provides the clientside validation script.  
61:      /// </summary>  
62:      private void RegisterClientValidationScript()  
63:      {  
64:          //Registering validator clientside javascript function  
65:          Page.ClientScript.RegisterExpandoAttribute(ClientID, "evaluationfunction", "ValidateDateBox", false);  
66:      }  
67:      #endregion  
68:  }  
69:  }  

In the method "OnInit" we check if the control property to activate the client script is set to true. If so the "RegisterClientValidationScript" method is called. It expands the DOM object with the property "evaluationfunction" and sets it to a custom JavaScript function "ValidateDateBox". The function "ValidateDateBox" must be implemented in the client code. Now following client code is generated when we insert this control:

 var ctl02 = document.all ? document.all["ctl02"] : document.getElementById("ctl02");  
 ctl02.evaluationfunction = "ValidateDateBox";  
 ctl02.controltovalidate = "txtDate";  
 ctl02.errormessage = "Enter a valid date (e.g. 05.06.1981)";  

In the "ControlPropertiesValid" method we check it the control we have to validate is a textbox control. All other controls are not accepted. If the control is a textbox we initialize to class variables to hold the controls and its value.

At least the "EvaluateIsValid" method is called in the lifecycle. It checks if the value is a correct date and returns a boolean value to indicate this.

As you can see it is very easy to create a validator control.

Now we create the client code for the validator. The function must have the name "ValidateDateBox" like how we defined it in the backend code. The evaluation function has one argument, the validator, so it can get the value to validate.

 function ValidateDateBox(val) {  
     var oDate = document.getElementById(val.controltovalidate);  
     var sDate = oDate.value;  
     // Not required field  
     if (sDate == "") {  
         return true;  
     }  
     // Check Date       
     return ParseDate(sDate);  
 }  
 function ParseDate(sDate) {  
     var iDay, iMonth, iYear;  
     var arrValues;  
     var today = new Date();      
     var fPoint = /^\d{1,2}\.\d{1,2}\.\d{4}$/;  
     if (fPoint.test(sDate)) {  
         arrValues = sDate.split(".");  
     } else {  
         return false;  
     }  
     iDay = arrValues[0];  
     iMonth = arrValues[1];  
     iYear = arrValues[2];  
     if ((iMonth == null) || (iYear == null)) {  
         return false;  
     }  
     if ((iDay > 31) || (iMonth > 12) || (iYear < 1900)) {  
         return false;  
     }  
     var dummyDate = new Date(iYear, iMonth - 1, iDay, 0, 0, 0);  
     if ((dummyDate.getDate() == iDay) && (dummyDate.getMonth() == iMonth - 1) && (dummyDate.getFullYear() == iYear)) {  
         return dummyDate;  
     }  
     return false;  
 }  

You can check the validation by entering a date like "40.11.2002" or "31.02.2001". Both should be invalid.


Extending the validator with custom properties

Now we want a property "OnlyFutureDate" that force users to enter a date in the future. This can be useful for traveling agencies or insurances. The validator now looks like that:

 <asp:Label ID="Label1" Text="Birthday:" AssociatedControlID="txtDate" runat="server" /><br />  
 <asp:TextBox ID="txtDate" Width="80" MaxLength="10" runat="server" />     
 <demo:DateValidator runat="server"  
     ControlToValidate="txtDate"  
     ErrorMessage="Enter a valid date (e.g. 05.06.1981)"  
     OnlyFutureDate="true"  
     EnableClientScript="true" />  

In the backend code we create a private property "mOnlyFuture" and a public property "OnlyFuture". The public property is the property used in the aspx page as control property.

 private Boolean mOnlyFutureDate = false;  
 /// <summary>  
 /// Allows only dates that lie in the future  
 /// </summary>  
 public Boolean OnlyFutureDate  
 {  
     set { mOnlyFutureDate = value; }  
 }  

We extend the "EvaluateIsValid" method to check if the property is set and the date is a date in the future:

 protected override bool EvaluateIsValid()  
 {  
     try  
     {  
         string sDate = mValueToValidate;  
         if (sDate == string.Empty) return true;  
         CultureInfo ci = new CultureInfo("de-DE");  
         DateTime date = DateTime.Parse(sDate, ci);  
         DateTime now = DateTime.Now;  
         DateTime today = new DateTime(now.Year, now.Month, now.Day, 0, 0, 0);  
         // if only future is allowed  
         if (mOnlyFutureDate)  
             return (date >= today);  
     }  
     catch  
     {  
         return false;  
     }  
     return true;  
 }  

The clientcode must also be informed if the property is set or not. So we extend the DOM object with an additional property "onlyfuturedate".

 private void RegisterClientValidationScript()  
 {  
     //Registering validator clientside javascript function  
     Page.ClientScript.RegisterExpandoAttribute(ClientID, "evaluationfunction", "ValidateDateBox", false);  
     Page.ClientScript.RegisterExpandoAttribute(ClientID, "onlyfuturedate", mOnlyFutureDate.ToString().ToLower(), false);  
 }  

Following client code is generated:

 var ctl02 = document.all ? document.all["ctl02"] : document.getElementById("ctl02");  
 ctl02.evaluationfunction = "ValidateDateBox";  
 ctl02.onlyfuturedate = "true";  
 ctl02.controltovalidate = "txtDate";  
 ctl02.errormessage = "Enter a date in the future";  


At least we must change the client code, so the JavaScript can also accept the new property.

 function ValidateDateBox(val) {  
     var oDate = document.getElementById(val.controltovalidate);  
     var isOnlyFuture = val.onlyfuturedate;       
     var sDate = oDate.value;  
     // Not required field  
     if (sDate == "") {  
         return true;  
     }  
    var dummyDate = ParseDate(sDate);  
     var now = new Date();  
     var today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); // 23.09.2011 00:00:00  
     if (dummyDate != false) {  
         // Check if it's a future date  
         if (isOnlyFuture == "true") {  
             return (dummyDate >= today);  
         }  
     }  
     return (dummyDate != false);  
 }  

Because we extended the DOM object in the backend code with the property "onlyfuturedate" we can now use it to check if the property is set.

Updatepanels and Custom Validation Controls

All other validation controls are made in the form explained in this post. One thing you have to pay attention is when you use "UpdatePanels". When a custom validator sits in an updatepanel the expando attributes get lost when the panel is updated. Therefore you have to register the expando attribute in the ScriptMaganer Control. The propably easiest way to do this is to create a custom helper method, which check if a scriptmanager exists and registers than like that:


 public static void RegisterExpandoAttribute(this Control control, string controlId, string attributeName, string attributeValue, bool encode)  
 {  
     if (control.IsInsideUpdatePanel() && control.HasPageScriptManager())  
     {  
         ScriptManager.RegisterExpandoAttribute(control, controlId, attributeName, attributeValue, encode);  
     }  
     else  
     {  
         control.Page.ClientScript.RegisterExpandoAttribute(controlId, attributeName, attributeValue, encode);  
     }  
 }  


See following post for more info: http://thedalehawthorneeffect.blogspot.com/2009/05/another-way-to-handle-client-script.html

Saturday, March 19, 2011

SharePoint 2010 MySite in detail

In general
The SharePoint 2010 MySite has changed in major points to the one in SharePoint 2007. The most striking renewals are probably the tagging und activity features. The user can now tag, comment and rate his sites and documents.

Since SharePoint 2010 has no longer a Shared Service Provider the MySite is now bound to the Profile Service. This means that the profile service must be running and configured before you can create a MySite.

The MySite uses a special master page, the "mysite.master", which is activated by the feature MySiteLayouts and uploaded to the master page gallery of the MySite sitecollection. It's relatively easy to change and switch the masterpage either by hand or by feature. The sitecollection administrator can upload a masterpage to the masterpage gallery and easily switch the master.

The good news for the frontend engineer is that the new mysite.master consists mostly of div layouts, so it
is easier to style by css. But in generally it is more difficult to change the layouts than the masterpage. This is because the mysite is a team site and has no publishing features. But you can easily change that by activating
the publishing features by hand.

The easiest way to style the mysite is propably by using themes. The sitecollection admin can create his own themes or use the existing ones.

Structure
The MySite has public and private spheres. The public pages use a different site template as the personal pages

  • Public : SPSMSITEHOST (My Site Host)
  • Private : SPSPERS (SharePoint Portal Server Personal Space)

Once you click on "My Site" within the Site Actions menu, it leads to the public area, to the default.aspx MySite site collection.



Public areas have the same view for each user and can not be personalized. All users from the ProfilDB have access to this area and only the site collection administrators can customize these pages and web parts.

The public area includes the following page:

  • default.aspx
  • discusssion.aspx
  • person.aspx
  • OrganizationView.aspx
  • personcontent.aspx
  • tagprofile.aspx

The HTML structure of these pages cannot be modified by deployment. The layout of these pages can be adjusted only with CSS, Themes or SharePoint Designer.

Clicking on "My Profile" (Site Actions Menu) leads to the public profile page. This page displays the profile information of the selected users. If the user wants to see his own profile, he has the rights to change the stored profile information. The changes are written back to the Profile-DB. If a synchronization is configured with an AD the information could also be written back to the AD.

By clicking on "My Content" in the MySite global navigation the user enters his private area. His personal site is a site collection within the MySite. If it does not exist it is created the first time. A user can create lists, modifiy content and add webparts to his personal page. It is used as a private storage of documents and information.

A private site has the url http://mysite.com/personal/mkaplan. "Personal" ist the managed path of the sitecollection and "mkaplan" the accountname and the sitecollection.

Looking at the global navigation, you'll realize that the MySite is logically divided into 3 parts. These are:

  1. My NewsFeed: Area for actual news from colleagues
  2. My Content: Area for personal data
  3. My Profile: Area with profile information of the MySite users

The following sections will describe these parts.

Lists on MySiteHost
The root site has following lists predefined:

List NameDescription
Customized ReportsFor Web Analytics
Form TemplatesContains the form templates
Style LibraryFor custom stylessheets
Organization PhotosOrganization logos
User PhotosProfile pictures

Personalization Sites
You can also use personalized sites witin the public areas of the MySite. This requires to create a new site using the "Personalization Site" template.

Personalization site template

"Personalization Sites" use the "Current User Filter" webpart, which can be connected with other webparts to display personalized data.

When you create a "Personalization Site" in the MySite the site will appear within the global navigation of the MySite as an extra entry, which is available for all users. A "Personalzation Site" contains 4 webpart zones: 1 on the top, 1 at the bottom and 2 in the middle.

A personalization site

Personalization Links in the global navigation
"Personalization Links" can be created to show entries in the global navigation for a specific audience. These types of links must be created in the "Profile Service Properties" und "Configure Personalization Site".




Site Definitions within the MySite


1. SPSMYSITEHOST

The SPSMYSITEHOST Teamsite Sitedefinition is used for all public sites within the MySite. It activates following features:

Site Features:
  • "My Site Host" in FEATURES\MySiteHost\Feature.xml.
  • "My Site Layouts" in FEATURES\MySiteLayouts\feature.xml.
Web Features:
  • "My Site Navigation" in FEATURES\MySiteNavigation\Feature.xml.
  • "Shared Picture Library for Organizations logos" in FEATURES\MySiteHostPictureLibrary\Feature.xml.
  • "Team Collaboration Lists" in \FEATURES\TeamCollab\feature.xml.
Modules: (deployed files)
  • blog.xsl : Is used by the blog webpart
  • tagprofile.aspx
  • person.aspx
  • default.aspx
  • discussion.aspx
  • OrganizationView.aspx
  • personcontent.aspx

2. SPSPERS
This Sitedefinition is used by the private sites on the MySite and activates the following features:

Adds following items to the Quicklaunch menu:
  • Documents
  • Pictures
  • Libraries
Site Features:
  • My Site Layouts: Deploys mysite master and pages
  • Base WebPart Feature
Web Features:
  • ‘Personalization Site’ in FEATURES\PersonalizationSite\feature.xml.
  • ‘My Site Navigation’ in FEATURES\MySiteNavigation\Feature.xml.
  • ‘Team Collaboration Lists’ in FEATURES\TeamCollab\feature.xml.
Modules:
  • public.aspx: Contains a redirect webpart
  • default.aspx
WebParts :
  • none
Lists:
  • Personal Documents
  • My Documents
  • My Pictures



Globally used elements


MySite Header

The MySite Header consists of different items: (Color corresponds to the item in image)

  • Page Title / Icon : A simple HTML element
  • Global Navigation: The navigation of the MySite is editable by the site owner in the "Site Settings", "Top Link Bar".
  • Searchbox: The Searchbox is a SharePoint PeopleSearchEx Control and has properties to change the search icon, scope dropdown, frametyp or the search options.
  • HelpButton: A simple HTML element
  • MySite Menu: Can optically only be changed by styles. Menu items can be changed by feature.


MySite Header

The MySite-Header is found on every site within the MySite, in public as well as in private sites. It is bound as a Delegate Control into the mysite.master und can be found as a ascx Control in the 14\ControlTemplates folder. The name of the file is "MySiteTopNavigation". Delegate Controls in SharePoint have the ability to be easily overriden by custom controls. This means that the header of a mysite could be customized with a custom ascx control and a feature.

Business Card
Some of the public pages contain a larger area with profile informations about the user, that is called the "Business Card“. Users can see profile informations about other users and can also enter or change personal status information, like in Twitter or Facebook. There are also standard texts for the status information, which can be choosen by a dropdown. If you examine your own "Business Card" you'll find also a link under the profile picture to edit the own profile informations.

MySite Business Card

The "Business Card" is not a Control itself but a combination of HTML tables and profile informations. The profile informations are read by special profile property controls, like the departement field:

<td><SPSWC:ProfilePropertyValue PropertyName="Department" runat="server"/></td>

This means that each page containing the "Business Card" must have the same HTML code. If someone changes the layout of the Business Card with Sharepoint Designer, he must change it on every site that contains the "Business Card" to have a consistent layout.

Quicklaunch
You'll find the "Quicklaunch Navigation" directly under the "Business Card". It is embedded to the page (not master) as an ASP:Menu Control and renders an unordered list as HTML Code and styled by css to look like that:

MySite Quicklaunch

The menu navigation points can be changed within the "Site Properties" by clicking the "Quicklaunch" link under "Look and Feel". Each of the default navigation items is linked to a different aspx page:
  1. "Overview" is linked with person.aspx.
  2. "Organization" is linked with organizationview.aspx.
  3. "Content" is linked with auf personcontent.aspx.
  4. "Tags and Notes" is linked with _layouts/thoughts.aspx.
  5. "Colleagues" is linked with _layouts/MyContactLinks.aspx.
  6. "Memberships" is linked with _layouts/MyMemberships.aspx.
The first 3 pages are delivered with the MySite Feature whereas the last 3 are pages in the layouts folder. All these page are not really personalized. Therefore there need a parameter in the url to display custom content.



Detailed inspection of MySite pages

My Newsfeed - Default.aspx


The newsfeed (or activity feed) is a new feature in SharePoint 2010. This feature corresponds to the concept of news streams in facebook. It shows the latest activities of colleagues such as new tag, changed profile information or new blog posts. The page uses the "ConsolidatedNewsFeedWebPart" to show the information.
The activity feed

Tagprofile.aspx


The tagprofile.aspx page shows information about a tag. The page is displayed when you open the "Tags & Notes" site in the quicklaunch menu and then click a tag in this site. The page contains following webparts:
  • "TagInformationWebPart" - Shows information about a tag.
  • "TaggedUrlListWebPart" - Shows tagged items.
  • "TaggedPeopleListWebPart" - Shows people that follow a tag
  • "SocialCommentWebPart" - Provides a note board
Tag information site

My Profile - Person.aspx

You get to the person.aspx when you click "My Profile" in the quicklaunch menu. This page provides an overview of general information and activities of a user.

The upper area of the site contains the "Business Card" and the "Quicklaunch Navigation". They are followed by the page content. The page layout has 4 webpart zones, like in the personal sites. The page has 1 zone in the upper,  2 in the middle and 1 at the bottom area to guarantee a flexible layout. To be flexible with webpart content, the webpart zones are located within a table layout. It is difficult to make cross browser flexible layouts only with divs.

The page contains 5 predefined webparts:

  • Ask Me About : Reads the "Ask Me About" field from the profile und displays it as a list. Informs other user about the skills of the selected user.
  • Recent Activities : Shows the recent activities of the current users, like the colleagues he added or a post he published.
  • Note Board : A textbox to post a comment.
  • Organigramm : Show the organizational structure and your position therein.
  • In Common With You : Shows common memberships and colleagues with other users.

All
-----
IN PROGRESS

Wednesday, March 2, 2011

How to style the SharePoint "Site Actions Menu" with Firebug

The site actions menu in SharePoint is generated automatically by JavaScript so you cannot look at the source code which classes are used to style the menu. I often use Firebug to check the classes and manipulate on the fly the css. But this is hard to do with the site actions menu because it disapears after a while.

A little trick helps here (works in SP2010 and SP2007)

Open the site in Firefox and open Firebug. Click the selection arrow and select the "Site Action" menu.

Firebug Objekt Picker


You will see a html code like show above. An <a> Tag surrounded by a <span> or <div>.


The trick is to copy the onclick code of the <span> or <div> tag. The javascript code can vary in each enviroment. The code can be easily copied from firebug or from the html code. In my case the code is:


  • In SP 2010
    CoreInvoke('MMU_Open',byid('zz8_FeatureMenuTemplate1'), MMU_GetMenuFromClientId('zz16_SiteActionsMenu'),event,false, null, 0); return false;
  • In SP 2007
    MMU_Open(byid('zz7_SiteActionsMenuMain'), MMU_GetMenuFromClientId('zz8_SiteActionsMenu'),event,false, null, 0);


Now change the "event" parameter to null:

CoreInvoke('MMU_Open',byid('zz8_FeatureMenuTemplate1'), MMU_GetMenuFromClientId('zz16_SiteActionsMenu'),null,false, null, 0); 

Open Firebug again, paste the code into the script textbox and press enter:

Open the site actions menu with JavaScript


The menu will now popup. Don't hover over the menu because you trigger the onmouseout event and the menu will disappear after a while.

In Firebug open the HTML tab and search after a text in the menu. (You have to click search a few times to jump to the right code area). The code will jump to the html of generated menu and you can use Firebug to change styles easily.

Searching the right code



And if the site action menu disappears while modifiying it, you can call the script to open it again !

Ugly menu example

Wednesday, February 9, 2011

No Scrollbar in SharePoint Dialog

Today I had a strange CSS issue. The SharePoint 2010 dialog had no scrollbars anymore. I found out that this was cought by the styles of the body element.

body {width:100%;height:100%;overflow:hidden}

Yes the overflow:hidden caused that the wrapper div could not determine the height. So I checked the html of the site within the dialog and found out that there is an attribute "tabindex" in the body tag.

HTML of Dialog

You can use this attribute to override the overflow style and the scrolbars are back.

body[tabindex] {overflow:auto }

Friday, January 28, 2011

Documentation of C# Code with SandCastle and Visual Studio 2010

In this post I'll show you, how you can create a documention of your code. In the past ndoc was the tool to create code docus but the project seems to be dead. Therefore we use SandCastle, a freeware tool, that is also used by Microsoft to create Code-Documentations.

Here is the reference of the comments within visual studio:



First of all you need to install these things in the given order.

Before you can create the code documentation, you have to create the documentation XML in Visual Studio. Therefore open your solution. Open the properties of a project within the solution.

Open Properties

Now in the build tab check the "XML Documentation File". The xml is now generated in the \bin\Debug folder. Of couse you can enter your own path if necessary.



Now rebuild your solution and check if a xml file exists beside the newly created dll.



The next steps are to open the GUI (SandCastel Help File Builder) and create a new project. You should find the gui in your start menu > programs.


I don't want to reinvent the whell therefore, I forward you to some other sites. These sites can explain the next steps certainly better than I can. If you choose the Step by Step Tutorial, you can now start by step 4.

Thursday, January 27, 2011

HttpModule to check the performance of a (sharepoint) site

I want to share the code of a http module I made for checking the performance of the backend site creation process. The result indicates if the site will break down when its launched and used by tausends of people. You can leave this module in your solution and activate it everytime you have to check the performance of a webpart or control. The module is simply activated by inserting an entry in the web.config.

When you type perf=1 in the querystring (ex. http://mydomain.com/pages/default.aspx?perf=1) than a little yellow div will appear on the upper left corner, that displays the information.

Performance of the backend process


First we create a new class and implent the IHttpModule interface. We implement the init method, which is the starting point.There we have to create two delegates, that process the request.

In an other class we implement the PerformanceFilter to manipulate the outputstream of the request. Here we read the stream an place a div with the information when we find the </body> tag


1:  using System;  
2:  using System.Collections.Generic;  
3:  using System.Linq;  
4:  using System.Text;  
5:  using System.Web;  
6:  using System.Diagnostics;  
7:  using System.IO;  
8:  namespace MyProject.Intranet.SharePoint.CommonLib.HttpModules  
9:  {  
10:    /// <summary>  
11:    /// A http module to check the performance of a sharepoint site  
12:    /// The module is activated when you put perf=1 as parameter in the querystring  
13:    /// Autor: I.Bikmaz  
14:    /// </summary>  
15:    public class PerformanceMonitor : IHttpModule  
16:    {  
17:      /// <summary>  
18:      /// In this method the request is intercepted and the timer begins  
19:      /// </summary>  
20:      public void Init(HttpApplication context)  
21:      {  
22:        context.PreRequestHandlerExecute += delegate(object sender, EventArgs e)  
23:        {  
24:          //Set Page Timer Star  
25:          HttpContext requestContext = ((HttpApplication)sender).Context;  
26:          Stopwatch timer = new Stopwatch();  
27:          requestContext.Items["Timer"] = timer;  
28:          timer.Start();  
29:        };  
30:        context.PostRequestHandlerExecute += delegate(object sender, EventArgs e)  
31:        {  
32:          HttpContext httpContext = ((HttpApplication)sender).Context;  
33:          HttpResponse response = httpContext.Response;  
34:          Stopwatch timer = (Stopwatch)httpContext.Items["Timer"];  
35:          timer.Stop();          
36:          // Don't interfere with non-HTML responses  
37:          if (response.ContentType == "text/html")  
38:          {            
39:            bool addPerformanceHtml = httpContext.Request.Url.ToString().Contains("perf=1");  
40:            if (addPerformanceHtml)  
41:            {  
42:              double seconds = (double)timer.ElapsedTicks / Stopwatch.Frequency;  
43:              string result_time = string.Format("{0:F4} sec ", seconds);  
44:              response.Filter = new PerformanceFilter(response.Filter, result_time);              
45:            }  
46:          }  
47:        };  
48:      }  
49:      /// <summary>  
50:      /// Disposal  
51:      /// </summary>  
52:      public void Dispose()  
53:      {  
54:      }  
55:    }  
56:    /// <summary>  
57:    /// Filter to manipulate the output stream  
58:    /// </summary>  
59:    public class PerformanceFilter : MemoryStream  
60:    {  
61:      private Stream mStream;  
62:      private StreamWriter mStreamWriter;  
63:      private String mTime;  
64:      /// <summary>  
65:      /// Constructor  
66:      /// </summary>  
67:      /// <param name="stm">The stream to filter</param>  
68:      /// <param name="time">The time to display</param>  
69:      public PerformanceFilter(Stream stm, String time)  
70:      {  
71:        mStream = stm;  
72:        mStreamWriter = new StreamWriter(mStream, System.Text.Encoding.UTF8);  
73:        mTime = time;  
74:      }  
75:      /// <summary>  
76:      /// Writes the new stream  
77:      /// </summary>  
78:      /// <param name="buffer"></param>  
79:      /// <param name="offset"></param>  
80:      /// <param name="count"></param>  
81:      public override void Write(byte[] buffer, int offset, int count)  
82:      {  
83:        MemoryStream ms = new MemoryStream(buffer, offset, count, false);  
84:        StreamReader sr = new StreamReader(ms, System.Text.Encoding.UTF8);  
85:        String s;  
86:        while ((s = sr.ReadLine()) != null)  
87:        {  
88:          s = s.Trim();  
89:          if (s == "</body>")  
90:          {            
91:            mStreamWriter.Write(String.Format("<div style=\"padding:5px; background:yellow; width:110px; height:25px; position:absolute; left:0;top:0\">Page generated in {0}</div>", mTime));            
92:          }  
93:          mStreamWriter.WriteLine(s);  
94:        }  
95:        mStreamWriter.Flush();  
96:      }  
97:    }  
98:  }  

After adding code to your solution you have to activate the http module by adding a web.confog entry. In SharePoint 2010 you should add the entry at the bottom  of the modules tag:


 <modules runAllManagedModulesForAllRequests="true">  
  <remove name="AnonymousIdentification" />  
  <remove name="FileAuthorization" />  
  ....  
  <add name="PublishingHttpModule" ...  
  <add name="PerformanceHttpModule"   
   type="MyProject.Intranet.SharePoint.CommonLib.HttpModules.PerformanceMonitor, MyProject.Intranet.SharePoint.CommonLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=xxxxxx" />  
 </modules>