One of our business users was requesting for a listing of items they've already added and have it available in a format that can be opened in a spreadsheet (Excel). My initial thought was to create a blank aspx page and write all the logic to get the data in the code-behind, run it and save the resulting file to a csv then I'm done.
Compile it and make sure your Command type matches with what you put in the config. Then try it out. Happy coding :)
But then it got me to think, it might be a better idea to have this functionality plugged in to Sitecore and made available for everyone to use.
Sitecore Bucket's Search Options fly-out seemed a good candidate for this feature (See image below). So doing some quick readings I got myself in to the "zone" and started implementing this quick and dirty PoC.
Just to explain what it actually does.
User will basically do a search, (Note that all search options require some filters or search keyword before any of the options can be used) clicks the Export to File, it pops up a dialog to confirm the action and executes, after which a browser-download dialog appears for the user to save the file to his/her local (in this case a CSV file).
User will basically do a search, (Note that all search options require some filters or search keyword before any of the options can be used) clicks the Export to File, it pops up a dialog to confirm the action and executes, after which a browser-download dialog appears for the user to save the file to his/her local (in this case a CSV file).
Getting down to the details, here's a rundown of what I did:
- Go to Settings > Buckets > Settings > Search Operations : you will find here all of the search options available; grouped by their own folders. I created my own called User Defined and added the Export To File item (based from Dropdown List template). Put in the Type (more on this later) e.g. bucket:exporttofile. Just by adding this item, this will automatically get added to the Search Option fly out; it does not do anything yet at this point obviously.
- Now, let's hook it up with a processor. To do that, we would need to modify the following file: App_Config/Include/Sitecore.Buckets.config, find the commands section and add your command entry there then Save e.g.
- Create your custom processor. To do that you would need to create a class that inherits from Sitecore.Shell.Frameworks.Commands.Command class and implement the Execute method. This solution uses SC's SheerResponse and ProgressBox as well as the Filestream object for the file handling. Here's the code:
using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Globalization; using System.IO; using System.Linq; using System.Text; using System.Web; using Sitecore; using Sitecore.Buckets.Util; using Sitecore.ContentSearch; using Sitecore.ContentSearch.SearchTypes; using Sitecore.ContentSearch.Utilities; using Sitecore.Data.Items; using Sitecore.Diagnostics; using Sitecore.Globalization; using Sitecore.Shell.Applications.Dialogs.ProgressBoxes; using Sitecore.Shell.Framework.Commands; using Sitecore.Text; using Sitecore.Web.UI.Sheer; namespace YourNamespace.Commands { [Serializable] public class ExportSearchToFile : Command { private string _linkToFile = string.Empty; // Methods public override void Execute(CommandContext context) { if (context.Items.Length <= 0) return; var item = context.Items[0]; var parameters = new NameValueCollection(); parameters["id"] = item.ID.ToString(); parameters["language"] = item.Language.ToString(); parameters["version"] = item.Version.ToString(); parameters["database"] = item.Database.Name; var strings = context.Parameters.GetValues("url"); if (strings != null) parameters["searchString"] = strings[0].Replace("\"", string.Empty); Context.ClientPage.Start(this, "Run", parameters); } protected void Run(ClientPipelineArgs args) { Assert.ArgumentNotNull(args, "args"); if (args.IsPostBack) { if (args.HasResult) { var database = Sitecore.Configuration.Factory.GetDatabase(args.Parameters["database"]); Assert.IsNotNull(database, "Database \"" + args.Parameters["database"] + "\" not found."); var item = database.Items[args.Parameters["id"], Language.Parse(args.Parameters["language"]), Sitecore.Data.Version.Parse(args.Parameters["version"])]; var list = UIFilterHelpers.ExtractSearchQuery(args.Parameters["searchString"]); Assert.IsNotNull(item, "item"); var jobName = Translate.Text("Export to File"); var title = Translate.Text("Exporting list to file"); const string icon = "~/icon/Software/32x32/text_code.png"; var parameters = new object[] {item, list, args.Result}; ProgressBox.Execute(jobName, title, icon, StartProcess, parameters); SheerResponse.Download(_linkToFile); //Alert(string.Format("Finished exporting all search results. Please click this link to download file: {0}", _linkToFile )); } } else { //create your own XAML -- I reused LayoutDetails for var str4 = new UrlString(UIUtil.GetUri("control:LayoutDetails")); str4.Append("id", args.Parameters["id"]); str4.Append("la", args.Parameters["language"]); str4.Append("vs", args.Parameters["version"]); SheerResponse.ShowModalDialog(str4.ToString(), true); args.WaitForPostBack(); } } private void StartProcess(params object[] parameters) { var str = (string) parameters[2]; var item = (Item) parameters[0]; SitecoreIndexableItem indexable = item; if (indexable == null) { Log.Error("Export to file - Unable to cast current item - " + parameters[0].GetType().FullName, this); } else { var searchStringModel = (List<SearchStringModel>
) parameters[1]; using ( var context = ContentSearchManager.GetIndex(indexable) .CreateSearchContext()) { var count = 0; IQueryable<SitecoreUISearchResultItem> queryable = LinqHelper.CreateQuery(context, searchStringModel, indexable); //var list2 = queryable.Select(item3 => item3.GetItem()).Where(item4 => (item4 != null) && item4.Security.CanWrite(Context.User)).ToList(); var list2 = new List - (); foreach (SitecoreUISearchResultItem item3 in queryable) { var item4 = item3.GetItem(); if (item4.Security.CanWrite(Context.User)) { list2.Add(item4); } } if (list2.Count > 0) { StreamWriter writer = null; try { var filename = DateTime.Now.ToString("g") + "_Export"; var path = "/temp/" + filename + ".csv"; var path2 = path.Replace("/", "\\").Substring(1, path.Length-1); var fs = new FileStream(HttpRuntime.AppDomainAppPath + path2, FileMode.Create, FileAccess.Write); writer = new StreamWriter(fs, Encoding.Default); writer.WriteLine("ID,Name,TemplateName,Parent,Version,Last-Modified"); foreach (var item5 in list2) { count++; if (item5 != null) writer.WriteLine(string.Format("{0},{1},{2},{3},{4},{5}", item5.ID, item5.Name, item5.TemplateName, item5.Parent.Name, item5.Version.Number, item5.Statistics.Updated.ToString( CultureInfo.InvariantCulture))); } writer.Flush(); writer.Close(); Log.Audit(this, "Export to file details: Exported a total of {0} items", new string[] {count.ToString(CultureInfo.InvariantCulture)}); _linkToFile = path; // @""; } catch (Exception ex) { Log.Error("Exception in Export to File - Search: " + ex.Message, this); _linkToFile = @"An error occurred - please contact Support."; if (writer != null) writer.Close(); } } } } } } }
Hi,
ReplyDeleteNeed your help.
I am not able to declare List type as u have done in above code as
var searchStringModel = (List) parameters[1];
I am getting type mismatch error for variable searchStringModel
Can you please suggest solution to it.
Thanks in advance
Best Regards
Anuj Chawla
hi Anuj,
DeleteThe code did not post correctly. I have updated the code. Kindly check it out. It should have had this as it's List type:SearchStringModel
Thanks
Hi, I get
ReplyDeleteSystem.Reflection.TargetInvocationException: Ein Aufrufziel hat einen Ausnahmefehler verursacht. ---> System.NullReferenceException: Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt. bei Sitecore.Web.UI.Sheer.ClientPage..ctor() bei Sitecore.Web.UI.Sheer.SheerResponse.Download(String file) bei Dornbracht.Backend.Commands.ExportSalesPartner.StartProcess(Object[] parameters)
when I reach SheerResponse.Download(_linkToFile); Can you help me?
Could you check your actual link string in debug to see if it's in the proper format? Also, check if you have the /temp folder which I used in the example. Regards
ReplyDeleteThanks for the blog Dean, I think it will help me quite a bit, unfortunately I'm having a bit of trouble at compile.. I'm getting a
ReplyDeleteUsing the generic type 'System.Collections.Generic.List' requires 1 type arguments
at the line of code var list2 = new List();
There appears to be a step or 2 missing. Aside from the error I posted about yesterday, when I do get it somewhat working and I try the button it brings up the layout dialogue.
ReplyDeleteHi Shane,
DeleteI had the same errors as you, but got it working when i did the following:
List() has to be replaced by List()
var filename = DateTime.Now.ToString("g") + "_Export";
has to be replaced by
var filename = DateTime.Now.Ticks.ToString() + "_Export"; (the file name had illegal characters)
if you use sitecore 8 you also need to replace
UIFilterHelpers.ExtractSearchQuery
with Sitecore.ContentSearch.Utilities.SearchStringModel.ExtractSearchQuery
Apparently you can ignore the layout-box (in the code it says that you should put your own xaml here) - just press the OK-button and the file gets generated and prepared for download (at least on my pc)