Have you ever thought “wouldn’t it be handy to have the ability to delete an item across multiple databases in Sitecore?” In other words, wouldn’t it be nice to not have to publish the parent of an item — with sub-items — after deleting it, just to remove it from a target database?
This particular thought has crossed my mind more than once, and I decided to do something about it. This post showcases what I’ve done.
I spent some time surfing through Sitecore.Kernel.dll and Sitecore.Client.dll in search of a dialog that allows users to select multiple options simultaneously but came up shorthanded — if you are aware of one, please leave a comment — so I had to roll my own:
<?xml version="1.0" encoding="utf-8" ?> <control xmlns:def="Definition" xmlns="http://schemas.sitecore.net/Visual-Studio-Intellisense"> <DeleteInDatabases> <FormDialog ID="DeleteInDatabasesDialog" Icon="Business/32x32/data_delete.png" Header="Delete Item In Databases" Text="Select the databases where you want to delete the item." OKButton="Delete"> <CodeBeside Type="Sitecore.Sandbox.Shell.Applications.Dialogs.DeleteInDatabasesForm,Sitecore.Sandbox"/> <GridPanel Width="100%" Height="100%" Style="table-layout:fixed"> <Border Padding="4" ID="Databases"/> </GridPanel> </FormDialog> </DeleteInDatabases> </control>
using System; using System.Collections.Generic; using System.Linq; using System.Web.UI; using System.Web.UI.HtmlControls; using Sitecore.Configuration; using Sitecore.Data; using Sitecore.Data.Items; using Sitecore.Diagnostics; using Sitecore.Web; using Sitecore.Web.UI.HtmlControls; using Sitecore.Web.UI.Pages; using Sitecore.Web.UI.Sheer; namespace Sitecore.Sandbox.Shell.Applications.Dialogs { public class DeleteInDatabasesForm : DialogForm { private const string DatabaseCheckboxIDPrefix = "db_"; protected Border Databases; private string _ItemId; protected string ItemId { get { if (string.IsNullOrWhiteSpace(_ItemId)) { _ItemId = WebUtil.GetQueryString("id"); } return _ItemId; } } protected override void OnLoad(EventArgs e) { AddDatabaseCheckboxes(); base.OnLoad(e); } private void AddDatabaseCheckboxes() { Databases.Controls.Clear(); foreach (string database in GetDatabasesForSelection()) { HtmlGenericControl checkbox = new HtmlGenericControl("input"); Databases.Controls.Add(checkbox); checkbox.Attributes["type"] = "checkbox"; checkbox.Attributes["value"] = database; string checkboxId = string.Concat(DatabaseCheckboxIDPrefix, database); checkbox.ID = checkboxId; HtmlGenericControl label = new HtmlGenericControl("label"); Databases.Controls.Add(label); label.Attributes["for"] = checkboxId; label.InnerText = database; Databases.Controls.Add(new LiteralControl("<br>")); } } private static IEnumerable<string> GetDatabasesForSelection() { return WebUtil.GetQueryString("db").Split("|".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); } protected override void OnOK(object sender, EventArgs args) { IEnumerable<string> selectedDatabases = GetSelectedDabases(); if (!selectedDatabases.Any()) { SheerResponse.Alert("Please select at least one database!"); return; } DeleteItemInDatabases(selectedDatabases, ItemId); SheerResponse.Alert("The item has been deleted in all selected databases!"); base.OnOK(sender, args); } private static IEnumerable<string> GetSelectedDabases() { IList<string> databases = new List<string>(); foreach (string id in Context.ClientPage.ClientRequest.Form.Keys) { if (!string.IsNullOrWhiteSpace(id) && id.StartsWith(DatabaseCheckboxIDPrefix)) { databases.Add(id.Substring(3)); } } return databases; } private static void DeleteItemInDatabases(IEnumerable<string> databases, string itemId) { foreach(string database in databases) { DeleteItemInDatabase(database, itemId); } } private static void DeleteItemInDatabase(string databaseName, string itemId) { Assert.ArgumentNotNullOrEmpty(databaseName, "databaseName"); Assert.ArgumentNotNullOrEmpty(itemId, "itemId"); Database database = Factory.GetDatabase(databaseName); Assert.IsNotNull(database, "Invalid database!"); DeleteItem(database.GetItem(itemId)); } private static void DeleteItem(Item item) { Assert.ArgumentNotNull(item, "item"); if (Settings.RecycleBinActive) { item.Recycle(); } else { item.Delete(); } } } }
The dialog above takes in an item’s ID — this is the ID of the item the user has chosen to delete across multiple databases — and a list of databases a user can choose from as checkboxes.
Ideally the item should exist in each database, albeit the code will throw an exception via an assertion in the case when client code supplies a database, the user selects it, and the item does not live in it.
If the user does not check off one checkbox, and clicks the ‘Delete’ button, an ‘Alert’ box will let the user know s/he must select at least one database.
When databases are selected, and the ‘Delete’ button is clicked, the item will be deleted — or put into the Recycle Bin — in all selected databases.
Now we need a way to launch this dialog. I figured it would make sense to have it be available from the item context menu — just as the ‘Delete’ menu option is available there “out of the box” — and built the following command for it:
using System; using System.Collections.Generic; using System.Linq; using Sitecore.Configuration; using Sitecore.Data.Items; using Sitecore.Diagnostics; using Sitecore.Shell.Framework.Commands; using Sitecore.Text; using Sitecore.Web.UI.Sheer; namespace Sitecore.Sandbox.Commands { public class DeleteInDatabases : Command { public override void Execute(CommandContext commandContext) { Context.ClientPage.Start(this, "ShowDialog", CreateNewClientPipelineArgs(GetItem(commandContext))); } private static ClientPipelineArgs CreateNewClientPipelineArgs(Item item) { Assert.ArgumentNotNull(item, "item"); ClientPipelineArgs args = new ClientPipelineArgs(); args.Parameters["ItemId"] = item.ID.ToString(); args.Parameters["ParentId"] = item.ParentID.ToString(); return args; } private void ShowDialog(ClientPipelineArgs args) { if (!args.IsPostBack) { SheerResponse.ShowModalDialog ( GetDialogUrl ( GetDatabasesForItem(args.Parameters["ItemId"]), args.Parameters["ItemId"] ), "300px", "500px", string.Empty, true ); args.WaitForPostBack(); } else { RefreshChildren(args.Parameters["ParentId"]); } } private void RefreshChildren(string parentId) { Assert.ArgumentNotNullOrEmpty(parentId, "parentId"); Context.ClientPage.SendMessage(this, string.Format("item:refreshchildren(id={0})", parentId)); } public override CommandState QueryState(CommandContext commandContext) { bool shouldEnable = Context.User.IsAdministrator && IsInDatabasesOtherThanCurrentContent(GetItem(commandContext)); if (shouldEnable) { return CommandState.Enabled; } return CommandState.Hidden; } private static bool IsInDatabasesOtherThanCurrentContent(Item item) { Assert.ArgumentNotNull(item, "item"); return GetDatabasesForItem(item.ID.ToString()).Count() > 1; } private static Item GetItem(CommandContext commandContext) { Assert.ArgumentNotNull(commandContext, "commandContext"); Assert.ArgumentNotNull(commandContext.Items, "commandContext.Items"); return commandContext.Items.FirstOrDefault(); } private static IEnumerable<string> GetDatabasesForItemExcludingContentDB(string id) { return GetDatabasesForItem(id).Where(db => string.Equals(db, Context.ContentDatabase.Name, StringComparison.CurrentCultureIgnoreCase)); } private static IEnumerable<string> GetDatabasesForItem(string id) { Assert.ArgumentNotNullOrEmpty(id, "id"); return (from database in Factory.GetDatabases() let itemInDatabase = database.GetItem(id) where itemInDatabase != null select database.Name).ToList(); } private static string GetDialogUrl(IEnumerable<string> databases, string id) { Assert.ArgumentNotNullOrEmpty(id, "id"); Assert.ArgumentNotNull(databases, "databases"); Assert.ArgumentCondition(databases.Any(), "databases", "At least one database should be supplied!"); UrlString urlString = new UrlString(UIUtil.GetUri("control:DeleteInDatabases")); urlString.Append("id", id); urlString.Append("db", string.Join("|", databases)); return urlString.ToString(); } } }
The command is only visible when the item is in another database other than the context content database and the user is an admin.
When the item context menu option is clicked, the command passes a pipe delimited list of database names — only databases that contain the item — and the item’s ID to the dialog through its query string.
Once the item is deleted via the dialog, control is returned back to the command, and it then refreshes all siblings of the deleted item — this is done so the deleted item is removed from the content tree if the context content database was chosen in the dialog.
I then made this command available in Sitecore using a configuration include file:
<?xml version="1.0"?> <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <commands> <command name="item:DeleteInDatabases" type="Sitecore.Sandbox.Commands.DeleteInDatabases,Sitecore.Sandbox"/> </commands> </sitecore> </configuration>
I’ve omitted the step on how I’ve wired this up to the item context menu in the core database. For more information on adding to the item context menu, please see part one and part two of my post showing how to do this.
Let’s see this in action.
I navigated to a test item that lives in the master and web databases, and launched its item context menu:
I clicked the ‘Delete in Databases’ menu option, and was presented with this dialog:
I got excited and forgot to select a database before clicking the ‘Delete’ button:
I then selected all databases, and clicked ‘Delete’:
When the dialog closed, we can see that our test item is gone:
Rest assured, it’s in the Recycle Bin:
It was also deleted in the web database as well — I’ve omitted screenshots of this since they would be identical to the last two screenshots above.
If you have any thoughts on this, or recommendations on making it better, please share in a comment.
Until next time, have a Sitecoretastic day!
Hi.This site is really awesome with beautiful snippets but i feel like some documentation is missing.I mean a lack of information regarding the implementation details.
Please tell how to create the control step by step.This could be useful for beginners
Thank you for this suggestion!
Do you know of blog post I could take a look at for pointers on how to add more information for beginners?
Hi Mike, Would like to know what it does in case of linked items, if the item is referred to 100 different locations in Master and Web?