Empty Admin Recycle Bin items

What is it?

Usually the size of the recycle bin is not relevant. But on development machines, you don’t want lots of files in there, which make your databases grow without actually used data.

What do you do? Go to the recycle bin, click on “Site Collection Recycle Bin”. The two stages of the recycle bin can be managed independently.

image

The two views on the left let you switch between the first- and second stage. All items from the first stage can be moved to the second stage with one click (+ 1 confirmation). But if the items are in the second stage, you can only delete 200 items at a time by selecting all and delete them in batches. An option to delete all items is missing (in the GUI).

image

My solutions adds this option for the second stage recycle bin. A link for deleting all items at once is displayed in the toolbar. If you click it, a modal dialog – I love SharePoint 2010 🙂 – is displayed, and you can delete all items at once.

image

 

How does it work?

A feature (farm-scoped) activates a delegate control. It references jQuery/JavaScript code to add the button “Empty Recycle Bin”, as shown in the second picture, and to open a modal dialog.

This modal dialog is a custom application page, which will query the recycle bin for statistics (as shown in the second picture) and delete items in batches via SPRecycleBinQuery which is similar to the SPQuery.

jQuery was my choice to add an additional button the the toolbar of the second stage recycle bin. The developer tools from the Internet Explorer helped me to find the right table by its ID. The content of it is modified, so the button is injected via JavaScript. That way there is not server code required to add the button directly to the page.

jQuery(document).ready(function () {
   // the Toolbar2 is used for the second stage recycle bin
   var row = jQuery("#ctl00_PlaceHolderMain_Toolbar2 > tbody > tr");
   
   // find last cell
   var lastTD = $(row).find('td:last');

   // add seperator
   lastTD.before('<TD class=ms-separator>|</TD>');

   // change content of the last cell
   var content =
   '<TABLE border=0 cellSpacing=0 cellPadding=1>' +
      '<TBODY>' +
         '<TR>' +
            '<TD class=ms-toolbar noWrap><A class=ms-toolbar title=' + emptyRecycleBin + ' href="javascript:openEmptyAdminRecycleBinDialog()">...</A></TD>' +
            '<TD class=ms-toolbar noWrap><A class=ms-toolbar title=' + emptyRecycleBin + ' href="javascript:openEmptyAdminRecycleBinDialog()">' + emptyRecycleBin + '</A>...</TD>' +
         '</TR>' +
      '</TBODY>' +
   '</TABLE>';
   lastTD.html(content);
});

The modal dialog is opened with JavaScript:

function openEmptyAdminRecycleBinDialog() {
   var options = {
      url: "../../_layouts/RH.EmptyAdminRecycleBin/EmptyAdminRecycleBin.aspx",
      width: 400,
      height: 130,
      title: "Empty Admin RecycleBin",
      dialogReturnValueCallback: onDialogClose
   };
   SP.UI.ModalDialog.showModalDialog(options);
}

function onDialogClose(dialogResult, returnValue) {
   if (dialogResult == SP.UI.DialogResult.OK) {
      // refresh the page to reflect changes
      SP.UI.ModalDialog.RefreshPage(SP.UI.DialogResult.OK)
   }
   if (dialogResult == SP.UI.DialogResult.cancel && returnValue != null) {
      alert(returnValue);
   }
}

With the callback function, a page refresh is triggered. Otherwise the deleted items would still be visible on the recycle bin page.

Recycle Bin

Querying recycle bin items is achieved with a dedicated object. The SPRecycleBinQuery. It takes parameters like the RowLimit. If you don’t specify the RowLimit, only 50 items will be returned. The ItemState defines if items from the first, or second stage will be returned. The query is executed multiple times, to get all items.

private void GetRecycleBinStorageInfo(out int itemCount, out long overalSize)
{
   itemCount = ;
   overalSize = ;
   SPRecycleBinItemCollectionPosition itemcollectionPosition = null;
   do
   {
      SPRecycleBinQuery query = CreateQuery(itemcollectionPosition);
      SPRecycleBinItemCollection recycleBinItems = Site.GetRecycleBinItems(query);
      itemcollectionPosition = recycleBinItems.ItemCollectionPosition;
      // get itemCount
      itemCount += recycleBinItems.Count;
      // get overalSize
      overalSize += recycleBinItems.Cast<SPRecycleBinItem>().Sum(item2 => item2.Size);
   } while (itemcollectionPosition != null);
}

private static SPRecycleBinQuery CreateQuery(SPRecycleBinItemCollectionPosition page)
{
   var query = new SPRecycleBinQuery
                  {
                     RowLimit = 200,
                     ItemState = SPRecycleBinItemState.SecondStageRecycleBin,
                     OrderBy = SPRecycleBinOrderBy.Default,
                     ItemCollectionPosition = page ?? SPRecycleBinItemCollectionPosition.FirstPage
                  };
   return query;
}

The method returns the itemcount and size of all items in the second stage recycle bin.

To delete all items, I’ve used this code:

using (var operation = new SPLongOperation(this))
{
   operation.Begin();

   try
   {
      // delete all items in pages of 200 items
      SPRecycleBinItemCollectionPosition itemcollectionPosition = null;
      do
      {
         SPRecycleBinQuery query = CreateQuery(itemcollectionPosition);
         SPRecycleBinItemCollection recycleBinItems = Site.GetRecycleBinItems(query);
         itemcollectionPosition = recycleBinItems.ItemCollectionPosition;
         for (int i = ; i < recycleBinItems.Count; i++)
         {
            recycleBinItems[i].Delete();
         }
      } while (itemcollectionPosition != null);

      operation.End("/_layouts/RH.EmptyAdminRecycleBin/CloseModalDialog.html", SPRedirectFlags.Default, Context, null);
   }
   catch (Exception ex)
   {
      UlsLogging.Write(TraceSeverity.Unexpected, ex.ToString());
      SPUtility.TransferToErrorPage(ex.Message);
      return false;
   }
   return true;
}

The SPLongOperation shows the nice animation during processing the code. Just make sure you end the operation to hide the animation.

Exception Handling

Usually my classes have a method HandleException, as the WSPBuilder does :-). This class will write the exception to the SharePoint ULS log.

private void HandleException(Exception ex)
{
   try
   {
      UlsLogging.Write(TraceSeverity.Unexpected, ex.ToString());
      Controls.AddAt(Controls.Count, new Label {CssClass = "ms-error", Text = ex.Message});
   }
   catch (Exception e)
   {
      Trace.Write(e.ToString());
   }
}

internal class UlsLogging
{
   internal static void Write(TraceSeverity traceSeverity, string message)
   {
      var uls = SPDiagnosticsService.Local;
      if (uls != null)
      {
         SPDiagnosticsCategory cat = uls.Areas["SharePoint Foundation"].Categories["Web Controls"];
         uls.WriteTrace(1, cat, traceSeverity,message, uls.TypeName);
      }
   }
}

If you want to use an area or category which is not a default one, you’ll need administration permissions to create it. The use of RunWithElevatedPrivilegues is not enough!

You can download the sourcecode here: RH.EmptyAdminRecycleBin(Sourcecode).zip

The compiled solution as WSP file can be downloaded here: RH.EmptyAdminRecycleBin.wsp

And before you ask: The solution is for SP 2010! SharePoint Services 3 lacks the modal dialog. But you can take the source code, and modify it to fulfill your needs.