The problem:
I need to see how well my document library performs when it has more than 5,000 documents. To do that, I need to generate more than 5,000 documents and upload them to the library. It would be nice to do it automatically.
The solution:
I have a simple Aha.docx file. First, I need to copy it 5,000 times. I chose to do it via PowerShell:
$path = "C:\Users\Administrator.TWS\Documents\TestDocs\Aha.docx"
$i = 1
do { $newPath = "C:\Users\Administrator.TWS\Documents\TestDocs\" + $i + ".docx"; Copy-Item $path $newPath; $i++ }
while ($i -le 5000)
Now my TestDocs folder has the Aha.docx file plus 5,000 other files, and I need to upload them to the Documents library. Here is the corresponding PowerShell script:
$spWeb = Get-SPWeb -Identity https://main.my.server.com/sites/Boris/TheSiteIAmTesting
$spFolder = $spWeb.GetFolder("Documents")
$spFileCollection = $spFolder.Files
$path = "C:\Users\Administrator.TWS\Documents\TestDocs\Aha.docx"
$file = Get-ChildItem $path
$spFileCollection.Add("Documents/Aha.docx",$file.OpenRead(),$false)
#Now, let's copy 5000 other files
$i = 1
do { $path = "C:\Users\Administrator.TWS\Documents\TestDocs\" + $i + ".docx"; $file = Get-ChildItem $path; $spFileCollection.Add("Documents/" + $i + ".docx",$file.OpenRead(),$false); $i++; }
while ($i -le 5000)
Now the Documents library contains the Aha.docx document plus 5,000 more documents, from 1.docx to 5000.docx.
Some interesting problems I face day-to-day as a SharePoint 2010 developer.
Monday, July 23, 2012
Wednesday, July 18, 2012
SharePoint 2013 Public Beta - First Impressions
So, today I spent about an hour with the public beta of SharePoint 2013. My first impressions:
1) Blue top bar, having the Static word "SharePoint" (IMHO, it is just taking up valuable space!), followed by (on the right side) Newsfeed, SkyDrive, and Sites.
2) The long-running operations now have the "Sorry to keep you waiting." How cute.
3) However, too many errors result in the "Something went wrong" error messages - I wish they were more specific.
4) Borrowed a lot of ideas from NewsGator, such as Community Portal, Community Site, "following" sites, etc. I created a new Community Portal as well as another site collection (with "Team" being the top-level site and a "Community Site" being the only subsite.)
5) Navigation is not as easy as in SP 2010, the UI is a bit hard to follow, no breadcrumb navigation.
6) Icons where there used to be words. Sometimes it is helpful, but in many cases it is rather confusing.
7) Content is not just list, libraries, and sites, it now also has "apps" (a new buzzword from SharePoint 2013 lingo.) Oh, and lists and libraries are "apps," too.
8) Central Administration has not changed that much. Same UI as it was in SharePoint 2010. But when you create a new site, it has a little "Experience" dropdown box where you can choose whether you want the 2013 experience or the 2010 experience.
9) There are three or so more service applications (Machine Translation, Security Token, and App Management.)
10) Visual Studio 2012 now allows Visual WebParts in Sandbox solutions and provides designers for lists and content types, the Microsoft Fakes framework for testing SharePoint code (always a good idea), profiling tools for SharePoint projects, Intellisense for JavaScript, and a new item template for Project Item.
1) Blue top bar, having the Static word "SharePoint" (IMHO, it is just taking up valuable space!), followed by (on the right side) Newsfeed, SkyDrive, and Sites.
2) The long-running operations now have the "Sorry to keep you waiting." How cute.
3) However, too many errors result in the "Something went wrong" error messages - I wish they were more specific.
4) Borrowed a lot of ideas from NewsGator, such as Community Portal, Community Site, "following" sites, etc. I created a new Community Portal as well as another site collection (with "Team" being the top-level site and a "Community Site" being the only subsite.)
5) Navigation is not as easy as in SP 2010, the UI is a bit hard to follow, no breadcrumb navigation.
6) Icons where there used to be words. Sometimes it is helpful, but in many cases it is rather confusing.
7) Content is not just list, libraries, and sites, it now also has "apps" (a new buzzword from SharePoint 2013 lingo.) Oh, and lists and libraries are "apps," too.
8) Central Administration has not changed that much. Same UI as it was in SharePoint 2010. But when you create a new site, it has a little "Experience" dropdown box where you can choose whether you want the 2013 experience or the 2010 experience.
9) There are three or so more service applications (Machine Translation, Security Token, and App Management.)
10) Visual Studio 2012 now allows Visual WebParts in Sandbox solutions and provides designers for lists and content types, the Microsoft Fakes framework for testing SharePoint code (always a good idea), profiling tools for SharePoint projects, Intellisense for JavaScript, and a new item template for Project Item.
Monday, July 2, 2012
More About Expanding/Collapsing Web Parts
On June 19th, I posted about a suggested method to expand and collapse web parts on a page. In this method, a lonesome square with a plus or a minus is created, displayed in its own web part (CEWP, to be specific), and when you click on this little square, the corresponding web part is expanded or collapsed accordingly.
This is good, but what if you want some other content to be there next to that square with a plus or minus? Will this solution still work?
The answer is: it depends. If there is only static content, then the solution will definitely work, you just add more content to the CEWP. But if there is inline server code (or any server code), then this solution will not work, because CEWPs cannot process server code. In this case, the solution is a bit more complex.
Since you cannot put server code into a CEWP, we'll have to write a custom web part. This custom web part will have at least one personalizable (but not web browsable) property, named WebPartToHideID. The <div> element will have to be added as a panel, and the hyperlinks and images as the corresponding server controls, like this:
Note how the WebPartToHideID is passed as a parameter to the javascript methods.
Next, we need to generate the JavaScript containing the expand/collapse jQuery code. The GetClientScript() helper method generates the corresponding JavaScript, passing WebPartToHideID as a parameter when needed. Finally, both the panel and the script are added to the user control:
That's it. Now we have a web part that acts like the CEWP I posted about earlier, but is also able to contain server code.
This is good, but what if you want some other content to be there next to that square with a plus or minus? Will this solution still work?
The answer is: it depends. If there is only static content, then the solution will definitely work, you just add more content to the CEWP. But if there is inline server code (or any server code), then this solution will not work, because CEWPs cannot process server code. In this case, the solution is a bit more complex.
Since you cannot put server code into a CEWP, we'll have to write a custom web part. This custom web part will have at least one personalizable (but not web browsable) property, named WebPartToHideID. The <div> element will have to be added as a panel, and the hyperlinks and images as the corresponding server controls, like this:
Note how the WebPartToHideID is passed as a parameter to the javascript methods.
Next, we need to generate the JavaScript containing the expand/collapse jQuery code. The GetClientScript() helper method generates the corresponding JavaScript, passing WebPartToHideID as a parameter when needed. Finally, both the panel and the script are added to the user control:
That's it. Now we have a web part that acts like the CEWP I posted about earlier, but is also able to contain server code.
Thursday, June 28, 2012
Custom Pager for SPGridView - Part 3
Of course, our pager should be able to allow us to move to the previous page and to the next page. The following method accomplishes this:
Note that here, the target of the postback is the pager, not the gridview. The argument is "nextpage" for the next page and "previouspage" for the previous page. Here is how you call this method from the overridden Render() method:
Finally, our pager should have the dropdown list with the value of possible page sizes. So, we will have to trigger the postback from the <select> element. This time, it will be trickier because the event argument will not be known until the element is actually selected - so, instead of calling the Page.ClientScript.GetPostBackEventReference() method, we will call __doPostBack() directly. The following chunk of code accomplishes this:
Now, all we have to do is to add our new gridview and pager to the user control of the visual Web Part. Make sure you set the pager's GridViewId property to the Id property of the gridview, and to implement the event handling method for the PageSizeChanged event (and for the PageIndexChanging event, of course.) And, in the OnPreRender() method of your user control, send the initial page size for your gridview (if the request is not a postback one.)
A little gotcha is that if you are putting your web part to a site page, make sure you assign that web part an ID in the <AllUsersWebPart> element. This will ensure your postbacks are always triggered for your gridview, since its client ID will not be changed.
Note that here, the target of the postback is the pager, not the gridview. The argument is "nextpage" for the next page and "previouspage" for the previous page. Here is how you call this method from the overridden Render() method:
Finally, our pager should have the dropdown list with the value of possible page sizes. So, we will have to trigger the postback from the <select> element. This time, it will be trickier because the event argument will not be known until the element is actually selected - so, instead of calling the Page.ClientScript.GetPostBackEventReference() method, we will call __doPostBack() directly. The following chunk of code accomplishes this:
Now, all we have to do is to add our new gridview and pager to the user control of the visual Web Part. Make sure you set the pager's GridViewId property to the Id property of the gridview, and to implement the event handling method for the PageSizeChanged event (and for the PageIndexChanging event, of course.) And, in the OnPreRender() method of your user control, send the initial page size for your gridview (if the request is not a postback one.)
A little gotcha is that if you are putting your web part to a site page, make sure you assign that web part an ID in the <AllUsersWebPart> element. This will ensure your postbacks are always triggered for your gridview, since its client ID will not be changed.
Custom Pager for SPGridView - Part 2
Now that we have an enhanced gridview, we can associate a pager with it. SPGridViewPager comes to mind. The way it is displayed is not exactly what we want, because all it allows you to do is to move to the next page and to the previous page, and it displays the range of rows, not individual page numbers, like this:
There aren't any properties in SPGridViewPager to alter this display style. So, we need to create a new pager class, inheriting from SPGridViewPager. Unfortunately, overriding the CreateChildControls() method in this class does nothing. So, our only hope is to override the Render() method:

This allows us to generate HTML on the fly, overriding the default rendering.
The problem we are having now is that we will have to trigger a postback from HTML elements. This is not so hard - that's what the Page.ClientScript.GetPostBackEventReference method is for. Here is how we can display individual page numbers (with the number in bold if the page is current, and a link to the correct page for non-current page numbers.)
In this method, a postback is triggered, with the gridview being the event target and "Page$" followed by a number being the event argument. The OOTB SPGridView knows how to raise a postback event for such an event argument.
Here I would like to remind everyone that the Page.ClientScript.GetPostBackEventReference(target,argument) method call is displayed in the HTML source as "javascript:__doPostBack(target, argument)."
To be continued...
There aren't any properties in SPGridViewPager to alter this display style. So, we need to create a new pager class, inheriting from SPGridViewPager. Unfortunately, overriding the CreateChildControls() method in this class does nothing. So, our only hope is to override the Render() method:

This allows us to generate HTML on the fly, overriding the default rendering.
The problem we are having now is that we will have to trigger a postback from HTML elements. This is not so hard - that's what the Page.ClientScript.GetPostBackEventReference method is for. Here is how we can display individual page numbers (with the number in bold if the page is current, and a link to the correct page for non-current page numbers.)
In this method, a postback is triggered, with the gridview being the event target and "Page$" followed by a number being the event argument. The OOTB SPGridView knows how to raise a postback event for such an event argument.
Here I would like to remind everyone that the Page.ClientScript.GetPostBackEventReference(target,argument) method call is displayed in the HTML source as "javascript:__doPostBack(target, argument)."
To be continued...
Wednesday, June 27, 2012
Custom Pager for SPGridView - Part 1
The problem:
My customer requirement is that the web part has to have a gridview whose pager allows access to any given page, go to the next page, go to the previous page, and select how many rows are displayed per page.
The solution:
This seemingly simple problem is actually a little more complex than it seems. The issue is that SPGridView does not have an event that triggers when the page size changes. So, the first step will be to create a new class that inherits from SPGridView and contains the following three things:
1) The delegate PageSizeChangeHandler;
2) The public event PageSizeChanged;
3) the overridden RaisePostBackEvent method which triggers the PageSizeChanged event if the event argument is "Rows$" followed by the number of rows (e.g., "Rows$5" for 5 rows.) The dollar sign is used as a separator to follow the guidelines for event arguments for gridviews:
Of course, there should also be a new class PageSizeChangeEventArgs (inheriting from System.EventArgs) with a public property NewPageSize and a public constructor that sets this property based on the argument constructor:
Now we have a gridview capable of raising a postback event when the number of rows to be displayed changes.
To be continued...
My customer requirement is that the web part has to have a gridview whose pager allows access to any given page, go to the next page, go to the previous page, and select how many rows are displayed per page.
The solution:
This seemingly simple problem is actually a little more complex than it seems. The issue is that SPGridView does not have an event that triggers when the page size changes. So, the first step will be to create a new class that inherits from SPGridView and contains the following three things:
1) The delegate PageSizeChangeHandler;
2) The public event PageSizeChanged;
3) the overridden RaisePostBackEvent method which triggers the PageSizeChanged event if the event argument is "Rows$" followed by the number of rows (e.g., "Rows$5" for 5 rows.) The dollar sign is used as a separator to follow the guidelines for event arguments for gridviews:
Of course, there should also be a new class PageSizeChangeEventArgs (inheriting from System.EventArgs) with a public property NewPageSize and a public constructor that sets this property based on the argument constructor:
To be continued...
Wednesday, June 20, 2012
Capturing Web Analytics Data in a Custom Web Part
The problem:
I need to display the number of daily unique visitors to a site collection on a custom web part. Using the following code results in an array with the only element being "The specified user or domain was not found." The Web application uses claims authentication.

The solution:
The problem is that the GetWebAnalyticsReportData() function takes the user login name from Thread.CurrentPrincipal.Identity.Name and passes it to SPWeb.DoesUserHavePermissions() - but SPSecurity.RunWithElevatedPrivileges does not change Thread.CurrentPrincipal.Identity.Name to the application pool account name. It does, however, change WindowsIdentity.GetCurrent() to the application pool account.
So, it is necessary to explicitly set Thread.CurrentPrincipal to the correct identity. Since my Web application uses claims authentication, here is a correct way to do this:
System.Threading.Thread.CurrentPrincipal =
ClaimsPrincipal.CreateFromIdentity(WindowsIdentity.GetCurrent());
Works like a charm!
Subscribe to:
Posts (Atom)









