Sitecore Delete Published Items

Within Sitecore, the Delete button is the bigger mystery for non-Sitecore experts….

Everybody assumes that an author going in the Authoring console and Deleting an item, the item got deleted and Unpublished… but unfortunately, this is not the case….

In the case you want the item to be deleted and unpublished, you have to delete the item and have to publish the parent of the item — with sub-items —  just to remove it from a target database…

IMO Sitecore standard behavior should be to delete and unpublish, automatically but unfortunately this is not the case…

There are several possible solutions to this issue and this blog explain the possible customisation that you could do to mitigate the issue…

Introducing a new button:

https://sitecorejunkie.com/2013/08/04/delete-an-item-across-multiple-databases-in-sitecore/ 

Configuring Workflows: http://www.nonlinearcreations.com/Digital/how-we-think/articles/2016/02/Item-deletion-and-immediate-publication-in-Sitecore-8-and-8-1.aspx

My favorite option is to extend the Item Delete event to remove the item from the publishing targets as well…

This code explains how to do it…

 


 public void OnItemDeleted(object sender, EventArgs args)
 {
            Item deltedItem = Event.ExtractParameter(args, 0) as Item;
           CleanUp(deltedItem.Id.ToString());
 }
public CleanUp(string itemId)
{

  // Get all publishing targets
  var publishingTargets = Sitecore.Publishing.PublishManager.GetPublishingTargets(item.Database);

  // Loop through each target, determine the database, and publish
  foreach(var publishingTarget in publishingTargets)
  {
    // Find the target database name, move to the next publishing target if it is empty.
    var targetDatabaseName = publishingTarget["Target database"];
    if (string.IsNullOrEmpty(targetDatabaseName))
        continue;

    // Get the target database, if missing skip
    var targetDatabase = Sitecore.Configuration.Factory.GetDatabase(targetDatabaseName);
    if (targetDatabase == null)
        continue;

      DeleteItemInDatabase(targetDatabaseName,  itemId);
   }

}



private static void DeleteItemInDatabases(IEnumerable databases, string itemId)
{
   foreach(string database in databases)
            {
                DeleteItemInDatabase(database, itemId);
            }
}
 
private static void DeleteItemInDatabase(string databaseName, string itemId)
{
      Database database = Factory.GetDatabase(databaseName);
      DeleteItem(database.GetItem(itemId));
}
 
private static void DeleteItem(Item item)
{
            
            if (Settings.RecycleBinActive)
            {
                item.Recycle();
            }
            else
            {
                item.Delete();
            }
}
Advertisements

WebAPI 2 native support in Sitecore 8.2

webapi-pic-300x300-2-2

Here we go with an other episode of the WebAPI and Sitecore saga…

As you probably know each version of Sitecore has managed WebAPI in a different way….

https://sitecorecommerce.wordpress.com/2014/11/30/webapi-attribute-routing-is-not-working-with-sitecore-7-5/

http://kamsar.net/index.php/2014/05/using-web-api-2-attribute-routing-with-sitecore/

Within 8.2 finally Sitecore is officially supporting WebApi NATIVELY

So what do you need to do to write your first WebAPI Controller and start using it?

very simple:

public class MyCustomController : Controller
{
   public ActionResult Test()
   {
      return Content("This is a test");
    }
}

and this is the Url where you can access your controller

Url: http://yourInstanceName/api/sitecore/MyCustom/test

public class MyOtherCustomController : Controller
{
   public ActionResult OtherTest(string mycustomId)
   {
      return Content("This is a test with params" + mycustomId);
    }
}

Url: http://yourInstanceName/api/sitecore/MyOtherCustom/OtherTest/thisIsMyParam

will return “This is a test with params thisIsMyParam”

Happy Sitecore and happy webApi to everybody!

LifestyleTransient, CastleWindsor, avoid memory leak…

Before starting this post, I have to say that I am not an huge expert of CastleWindsor & my knowledge on garbage collection and memory management is limited, if you think that I am saying something wrong, please leave a comment or point to an useful link.

Let’s say that you are coding a pipeline component and you want to make it easy to test and isolate the dependency and most of the logic for your pipeline component is handled in an external class….

let’s say that your class was installed as LifestyleTransient

The easiest way to use it in your pipeline component would be using the service locator pattern:

var classInstance = DIContainer.Resolve<IYourInterface>()

but you have to be aware that following this route, nobody will dispose for you the memory…. initially I thought that the GarbageCollector would do the job, but talking with some CastleWindsor expert, he explained me that your DIContainer has a reference to your class and it won’t clean it up when you need memory since it is still referenced by the Container object….

The only solution that I have been suggested is to Release your object explicitally in your pipeline component otherwise you will have a good chance to get a memory leak (obviously it depend on the size in memory that your class take and the traffic of your website…)

DIContainer.Release(classInstance);

Installer.Cs
container.Register(Component.For<ISomeClass>()
                    .ImplementedBy<SomeClass>()
                    .LifestyleTransient());

 
public class PipelineSample : HttpRequestProcessor
 {
  public override void Process(HttpRequestArgs args)
     {
       var classInstance = DIContainer.Resolve<ISomeClass>;
//Do Something
//Do not forget at the end of your method
DIContainer.Release(classInstance); 
      }
 }

The issue that I have faced is related to the ServiceLocator pattern and it does not affect the ControllerFactory at all since the controller factory dispose all the dependency passed in the constructor.

Most of developers are using Sitecore & CastleWindsor mostly for histoical reasons, than for a specific reason since from Sitecore 8, DI is completely decoupled from Sitecore and Glass but I would expect that also the other DI would be affected from the same issue if they implement the same concept of lifestyle transient…

An other possible solution to avoid this issue is install the components without using LifestyleTransient but using LifestylePerWebRequest assuming that your code would work reusing the same class multiple time per the request…
 

Custom RSS Atom Feed in a Sitecore Application

I had the necessity to customize the order and the number of items provided in the RSS feed generated within Sitecore and I have written the following code…

I am not particularly proud of it, but there are scenarios where this is the easiest solution to fix a production issue….

You could also reuse the following code to expose a custom RSS endpoint within your Sitecore web site

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sitecore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Http;
using Sitecore.Data.Items;
using System.Xml;
using System.ServiceModel.Syndication;
using System.Net.Http;
using System.IO;
using System.Net; 

public class CustomRSSController : ApiController
  {
     [HttpGet]
public HttpResponseMessage GetNews()
     {
     string url = "http://yourWebSite.com/rss" ;
      XmlReader reader = XmlReader.Create(url);
      SyndicationFeed feed = SyndicationFeed.Load(reader);
      reader.Close();

      var output = new MemoryStream();
      string xml;

      feed.Items = feed.Items.OrderByDescending(x=>x.PublishDate).Take(5);

      var formatter = new Rss20FeedFormatter(feed);
      var xws = new XmlWriterSettings { Encoding = Encoding.UTF8 };
            using (var xmlWriter = XmlWriter.Create(output, xws))
            {
                formatter.WriteTo(xmlWriter);
                xmlWriter.Flush();
            }
            using (var sr = new StreamReader(output))
            {
                output.Position = 0;
                xml = sr.ReadToEnd();
                sr.Close();
            }

 var response = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(xml, Encoding .UTF8, "application/atom+xml") };
            return response;        
       }

TDD – HttpContextFactory

TDD on the controllers could be painful, this class abstract the httpcontext making your code easier to test

using System;
using System.Web;

namespace SdbBackbone.Factories
{
    public class HttpContextFactory
    {
        private static HttpContextBase _context;

        public static HttpContextBase Current
        {
            get
            {
                if (_context != null)
                    return _context;

                if (HttpContext.Current == null)
                    throw new InvalidOperationException("HttpContext not available");

                return new HttpContextWrapper(HttpContext.Current);
            }
        }

        public static void SetCurrentContext(HttpContextBase context)
        {
            _context = context;
        }
    }
}

Xml Serializer utility

Every developer at somepoint has to deal with Xml Serialization.

This utility make it simple the object serialization and deserialization

 

using System.IO;
using System.Xml.Serialization;

namespace SdbBackbone.Extensions
{
    public static class XmlTools
    {
        public static string ToXmlString<T>(this T input)
        {
            using (var writer = new StringWriter())
            {
                input.ToXml(writer);
                return writer.ToString();
            }
        }

        public static void ToXml<T>(this T objectToSerialize, Stream stream)
        {
            new XmlSerializer(typeof(T)).Serialize(stream, objectToSerialize);
        }

        public static void ToXml<T>(this T objectToSerialize, StringWriter writer)
        {
            var ns = new XmlSerializerNamespaces();
            ns.Add("", "http://www.w3.org/2001/XMLSchema-instance");
            ns.Add("", "http://www.w3.org/2001/XMLSchema");

            new XmlSerializer(typeof(T)).Serialize(writer, objectToSerialize, ns);
        }
    }
}

String Extension

Who does not need some extension to the string class?

this is an useful one to have in your core library

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Web;
using System.Xml;
using System.Xml.Serialization;
using HtmlAgilityPack;
using SdbBackbone.Utils.Xml;

namespace SdbBackbone.Extensions
{
    public static class StringExtensions
    {
        public static bool Contains(this string target, string value, StringComparison comparison)
        {
            return target.IndexOf(value, comparison) >= 0;
        }

        /// <summary>Cleans the html string and removes the body tag if present</summary>
        /// <param name="html">The html</param>
        /// <param name="errors">Retuns error when parsing the html. If html is valid, returns an empty IEnumerable</param>
        /// <returns>The cleaned html</returns>
        public static string CleanHtml(this string html, out IEnumerable<string> errors)
        {
            var doc = new HtmlDocument {OptionWriteEmptyNodes = true};
            doc.LoadHtml(html);
            errors = doc.ParseErrors != null && doc.ParseErrors.Any()
                         ? doc.ParseErrors.Select(e => e.ToString())
                         : Enumerable.Empty<string>();
            return doc.DocumentNode.SelectSingleNode("//body") != null
                       ? doc.DocumentNode.SelectSingleNode("//body").InnerHtml
                       : doc.DocumentNode.InnerHtml;
        }

        public static string FormatWith(this string str, params object[] args)
        {
            return String.Format(str, args);
        }

        public static bool IsEmpty(this string str)
        {
            return String.IsNullOrWhiteSpace(str);
        }

        public static string RemoveHtmlTags(this string text)
        {
            return Regex.Replace(text ?? string.Empty, "</?[^>]*>", string.Empty, RegexOptions.IgnoreCase);
        }

        /// <summary>
        ///     A case insenstive replace function.
        /// </summary>
        /// <param name="originalString">The string to examine.(HayStack)</param>
        /// <param name="oldValue">The value to replace.(Needle)</param>
        /// <param name="newValue">The new value to be inserted</param>
        /// <returns>A string</returns>
        public static string CaseInsenstiveReplace(string originalString, string oldValue, string newValue)
        {
            var regEx = new Regex(oldValue, RegexOptions.IgnoreCase | RegexOptions.Multiline);
            return regEx.Replace(originalString, newValue);
        }

        public static string StripForUrl(this string title)
        {
            string url = title;

            url.RemoveHtmlTags();
            url = HttpUtility.HtmlDecode(url);

            //get rid of URI escapes
            url = Regex.Replace(url, "%[a-fA-F0-9]{2}", "", RegexOptions.Compiled);

            //get rid of HTML escapes
            url = Regex.Replace(url, "&(\\#{1}\\d*|\\w*);", "", RegexOptions.Compiled);

            //go to town on special characters
            url = Regex.Replace(url, "[~`!@#$%^*()<>?,.\\|/:;'\"\\\\]", "", RegexOptions.Compiled);

            //get rid of all non-ascii characters
            url = Regex.Replace(url, "[^\\x20-\\x7E]", "", RegexOptions.Compiled);

            //All theese lines are covered by otehr lines in this function
            //strOutput = Regex.Replace(strOutput, "</?[^>]*>", "", RegexOptions.Compiled) ' Removes any HTML tags from the text
            //strOutput = Regex.Replace(strOutput, "[^\w \s\./-]", "", RegexOptions.Compiled)
            //strOutput = Regex.Replace(strOutput, "\s", "-", RegexOptions.Compiled)
            //strOutput = Regex.Replace(strOutput, "\.", "", RegexOptions.Compiled)
            //strOutput = Regex.Replace(strOutput, "/.", "", RegexOptions.Compiled)

            //replace ampersands with the word 'and', providing they're surrounded by white space
            url = Regex.Replace(url, "\\s&\\s", " and ", RegexOptions.Compiled);

            //replace all other ampersands with hyphens
            url = Regex.Replace(url, "&", "-", RegexOptions.Compiled);

            //get rid of groups of spaces (ie 2 or more in a row)
            url = Regex.Replace(url, "\\s{2,}", "", RegexOptions.Compiled);

            //replace spaces with dashes
            url = url.Trim();
            url = Regex.Replace(url, "\\s", "-", RegexOptions.Compiled);
            url = Regex.Replace(url, "-{2,}", "-", RegexOptions.Compiled);

            const int maxLength = 100;
            if (url.Length > maxLength)
            {
                if (url.IndexOf("-", StringComparison.Ordinal) > maxLength)
                {
                    url = url.Substring(0, maxLength);
                }
                else
                {
                    string strTemp = "";

                    foreach (string word in url.Split('-'))
                    {
                        if (strTemp.Length + word.Length > maxLength)
                        {
                            break;
                        }

                        if (strTemp.Length == 0)
                        {
                            strTemp += word;
                        }
                        else
                        {
                            strTemp += "-" + word;
                        }
                    }

                    url = strTemp;
                }
            }

            return url;
        }

        public static string EscapeSingleQuotes(this string value)
        {
            return value.Replace("'", "\\u0027");
        }

        public static string UnescapeSingleQuotes(this string value)
        {
            return value.Replace("\\u0027", "'");
        }

        public static string Decode(this string value)
        {
            return WebUtility.HtmlDecode(value);
        }

        public static Stream ToStream(this string @this)
        {
            var stream = new MemoryStream();
            var writer = new StreamWriter(stream);
            writer.Write(@this);
            writer.Flush();
            stream.Position = 0;
            return stream;
        }

        /// <summary>
        /// Deserialize an XML string to an object of type T
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <returns></returns>
        public static T DeserializeXML<T>(this string value)
        {
            var serializer = new XmlSerializer(typeof (T));
            var reader = new NamespaceIgnorantXmlTextReader(new StringReader(value));  //XmlReader.Create(value.Trim().ToStream());
            return (T) serializer.Deserialize(reader);
        }

        public static string SerializeXML<T>(this string value, T obj)
        {
            var serializer = new XmlSerializer(typeof (T));
            var stream = new MemoryStream();
            using (var tw = new XmlTextWriter(stream, System.Text.Encoding.UTF8))
            {
                serializer.Serialize(stream, obj);
                return System.Text.Encoding.UTF8.GetString(stream.ToArray());   
            }
        }
         
    }
}