Friday, November 18, 2011

Replicating the Text Editor Functionality Programmatically

I was developing a custom webpart which has custom properties viz. a people picker, a textbox to type a question and another to type the answer.


As, I was using the EditorPart and the PeoplePicker control, I was unable to group the People Picker property with the other two properties. I would get the People Picker at the top of the webpart property pane, and the other two at the bottom of the properties pane under the custom group "My Properties". Any blogger who knows how to group your custom properties can please post the related url. Thanks in advance.

Thus, I created two more additional textboxes to capture the question and the answer, and was in high spirits of having completed my task.When I demonstrated this "cool" webpart to my product owner, he casually added that the answer textbox needs to have a pop-up Text Editor, as you see in any OOTB textboxes which help you build content. Thats when the real problem started. 

I was expecting Microsoft to give me a "SPLinkBuilderXXX" kind of a control which gives me this functionality straightaway, just as the PeoplePicker class. I was very happy to find on MSDN that there did exist a class called "TextZoomBuilder" class, which to my dismay was marked "Obsolete" :(.

I found that MS uses an input button which calls a javascript function in javascript:MSOPGrid_doBuilder, and passes the arguments - '_layouts/1033/zoombldr.aspx', the id of the control to which the value needs to passed, and the properties of the pop-up window.

When I tried to replicate this, I found that SharePoint (the ASP.NET runtime engine) appends dynamic Guids to all the controls on a page to prevent them from getting duplicated. Thus, we cannot get the ClientId of the control at runtime in the code-behind. It appears this is achieved by SharePoint using the 'ContentToolPart' which is an internal sealed class.
I am very thankful to Tony Bierman who posted Leveraging Custom Property Builders in SharePoint Web Parts, a wonderful article in 2006. The workaround being to place a hidden form field on the page to which you can get the zoombldr.aspx page to post the data back to, and then use a Javascript function which simulates the 'Apply' button click action of the webpart properties pane to post the value back to your textbox.

However, when I tried to implement it in SharePoint 2010, I got lot of Javascript errors. I found that we need to use the name of ApplyButton directly. The code which worked for me was - 

 string key = "applyFunction";
                StringBuilder embeddedScriptFormat = new StringBuilder("<script language=jscript>function ApplyProperties(){\n");
                embeddedScriptFormat.Append("document.forms[MSOWebPartPageFormName].MSOTlPn_Button.value = 'apply';\n");
                embeddedScriptFormat.Append("document.forms[MSOWebPartPageFormName].elements['ctl00$MSOTlPn_EditorZone$MSOTlPn_AppBtn'].click();\n");
                embeddedScriptFormat.Append("}\n");
                embeddedScriptFormat.Append("</script>\n");
                if (!Page.ClientScript.IsClientScriptBlockRegistered(key))
                {
                    Page.ClientScript.RegisterClientScriptBlock(this.GetType(), key, embeddedScriptFormat.ToString());
                }


One more trick up your sleeve as a SharePoint developer.

Friday, November 11, 2011

Custom Pagelayout with custom user controls causes a page crash

So this was an issue that we were facing a couple of days back and really had a tough time solving it, thanks to the wierd behaviour of SharePoint.

The Issue:

Ok, as any other project in SharePoint would do we were using custom pagelayouts for our content pages. However, one of the layouts has a user control which would push a couple of webparts into the different webpart zones of layout. This page layout would crash when it gets applied to any page in the site.
Applying Custom Page layout

Investigations:

As is common in most of the issues in SharePoint, the ULS wouldnt give a clue. We used to get wierd error messages viz. Unknown class for webpartzone 'xyz', div tag is not a valid tag etc.

The interesting part was that trying to debug wouldnt even hit the breakpoint in any of the pages.

Thus, we turned to the only resort for any developer, Google :)

Cause:

We found that SharePoint would introduce two error webparts into the pagelayout while deploying the solution. This could be checked by going to the edit properties of the pagelayout in the master pages and pagelayouts gallery of the site collection. Scrolling down to the end of the page and selecting the option to 'Open the page in Webpart maintenance mode'.
Link to Webpart Page Maintenance Link at the bottom

Now the question is from where have these errorwebparts come from? Obviously, no sane developer would do that.

Root Cause:

It was obvious that SharePoint was pushing these, but why? A lot of googling ultimately pointed the issue. As I understand SharePoint expects the '<ZoneTemplate></ZoneTemplate>' tags to be defined within the Webpartzone tags, failing to which it injects these webparts when your custom code tried to insert webparts into the webpartzone.

Resolution:

Well the first step in trying to resolve this was to remove the error webparts from the custom pagelayout, and checking if the issue is gone. Having confirmed that, we added the '<ZoneTemplate></ZoneTemplate>' tags at all places in the page before the closing tage of the webpartzone.

Well abracadabra............. the problem is gone :)

Hope this helps you in solving your problem too.

Tuesday, September 13, 2011

SP 2010 Best Practices


1. Ensure Object Disposal by using Dispose method, using clause or try, catch, and finally blocks

2. Create lists and libraries using appropriate list definition templates
3. Refactor your project solution into appropriate projects to have clear demarcation of various object. Use a separte project for data access, a separate project for list instantiation, and a separate project for logging, and another project for SharePoint artefacts like webparts, custom pages etc
4. Always use the developer dashboard on all the custom pages that are developed. This gives an overview of the performance of the page
5. The SPSiteCollection.Add method creates and returns a new SPSite object. You should dispose of any SPSite  object returned from the SPSiteCollection.Add method.
6. The SPSiteCollection [] index operator returns a new SPSite  object for each access. An SPSite instance is created even if that object was already accessed.
7.The SPSite.AllWebs.Add method creates and returns an SPWeb  object. You should dispose of any SPWeb object returned from SPSite.AllWebs.Add.
8.If the object is obtained from the SharePoint context objects (GetContextSite  method and GetContextWeb method), the calling application should not call the Dispose  method on the object. Doing so may cause the SharePoint object model to behave unpredictably or fail. This is due to an internal list that is kept in the SPSite and SPWeb objects derived in this way. Internally, the object model enumerates over this list after page completion to dispose of the objects properly.
9. Always use SPQuery object and CAML Query to filter data from lists. Iterate through collections only after filtering the collection as required
10. While using images always set the ALT property to the IMG tag.
11. Create lists and columns with a name WITH NO SPACES
12. WebParts: Always initialize your controls in the CreateChildControls method.
Call the EnsureChildControls method in your code
Do not use the Render() method while developing a webpart in SharePoint
13. Frequently check the entries in the log for - The total number of SPRequest objects, An SPRequest object continues, An SPRequest object was garbage collected
14.Make sure that the containers like lists and libraries follow the recommended guidelines on number of items in it viz 2000 items in SharePoint 2007 and 5000 items in SharePoint 2010
   a. Implement a timer job that deletes items from list regularly if they are not needed
   b. Store items in folders rather than storing them directly in list
15.Use the SPQuery.ViewFields property to get only the required number of fields in the dataset
16. SPDisposeCheck tool on all code before deploying outside of you development environment
17.Wrap all your custom code that runs with full trust in SharePoint using the SPMonitoredScope class.

using (new SPMonitoredScope("My Code"))
{
    //Your code
}


using (new SPMonitoredScope("My Scope Name",1000,new SPRequestUsageCounter(3),new SPSqlQueryCounter()))
{
    //Your code
}

You can use any of the following classes to monitor specific code sections depending on the scenario SPCriticalTraceCounter (critical events and asserts), SPExecutionTimeCounter (track execution time for your scope), SPRequestUsageCounter (rack the number of SPRequest objects your code uses), SPSqlQueryCounter (tracks the number of SQL queries for your scope).
18. Database related -
       a. SharePoint runs a nightly timer job to rebuild indexes and update statistics, so be sure that this job is created and running.
       b. Always pre - grow all your databases, as it is an expensive operation for SQL Server to autogrow databases
19. Do not use CSS inline styles but use CSS files and appropriately markup the CSS styles using theme attributes
20. At all possible times develop sand-boxed solutions, and use full-trust proxies to do the necessary code plumbing
21.Allocate a pre-defined quota to all your sandboxed solutions, and regularly monitor their usage and fine tune them
22. Validate all the sandboxed solutions using the SPSolutionValidator class
23. The 'site.GetFile' method returns an SPFile object even when you pass the path to a site page that does not exist.You should inspect the Exists property before attempting to access any of its other methods.
24. Recompile all your sandbox solutions by referencing it to the Sandboxed Microsoft.SharePoint.dll present in the UserCode directory in 14 hive to make sure that none of the API are calling methods which are restricted in SSs.
25. Indexing should be done on the columns that are used for filtering in the LINQ/CAML queries
26. use Power Shell scripts for deployment 
27. Do not use the Workflow History list heavily. Implement some other logging mechanism if needed to log heavily
28. Make sure to create a root site in a site collection for a given web application, else it might give issues when accessing web services via that web application
29. Use SPWeb.ProcessBatchData method for bulk deletion of items in a list
30. Do not use delay activities in custom workflows as they do not fire always. It is a known issue. Try to implement Timer Jobs instead
31. In scenarios where a field’s display name could change, it is a safer bet to access the field within a Fields collection using the GetFieldByInternalName method
32. Create Site columns and custom content types in the site columns gallery or content types gallery of top-level sites so that they are made available on a site collection–wide basis
33. Always use the SPList.EventReceivers.Add(GUID) overload while registering event handlers programmatically. This helps to check if the event handler is already existing or not, and its easier to delete it in Feature Deactivation
34. It is generally recommended that you avoid custom site definitions when designing and developing SharePoint solutions