Files
Umbraco-CMS/src/umbraco.cms/businesslogic/template/Template.cs
Morten Christensen b17dcd2b56 Fixes U4-1516 by ensuring that referenced templates are deleted prior to deleting the actual template.
Removing uncommented code from the Media class as well as from the ContentExtension class.
Added saving of Media Xml to MediaService upon saving.
Removing referenced media test from csproj as cs file was missing.
2013-01-24 09:50:27 -01:00

881 lines
31 KiB
C#

using System;
using System.Linq;
using System.Collections;
using System.Xml;
using Umbraco.Core;
using Umbraco.Core.Logging;
using umbraco.DataLayer;
using System.Text.RegularExpressions;
using System.IO;
using System.Collections.Generic;
using umbraco.cms.businesslogic.cache;
using umbraco.BusinessLogic;
using umbraco.IO;
using umbraco.cms.businesslogic.web;
namespace umbraco.cms.businesslogic.template
{
/// <summary>
/// Summary description for Template.
/// </summary>
//[Obsolete("Obsolete, This class will eventually be phased out - Use Umbraco.Core.Models.Template", false)]
public class Template : CMSNode
{
#region Private members
private string _OutputContentType;
private string _design;
private string _alias;
private string _oldAlias;
private int _mastertemplate;
private bool _hasChildrenInitialized = false;
private bool _hasChildren;
#endregion
#region Static members
public static readonly string UmbracoMasterTemplate = SystemDirectories.Umbraco + "/masterpages/default.master";
private static Hashtable _templateAliases = new Hashtable();
private static volatile bool _templateAliasesInitialized = false;
private static object templateCacheSyncLock = new object();
private static readonly string UmbracoTemplateCacheKey = "UmbracoTemplateCache";
private static object _templateLoaderLocker = new object();
private static Guid _objectType = new Guid("6fbde604-4178-42ce-a10b-8a2600a2f07d");
private static readonly char[] NewLineChars = Environment.NewLine.ToCharArray();
#endregion
[Obsolete("Use TemplateFilePath instead")]
public string MasterPageFile
{
get { return TemplateFilePath; }
}
/// <summary>
/// Returns the file path for the current template
/// </summary>
public string TemplateFilePath
{
get
{
switch (DetermineRenderingEngine(this))
{
case RenderingEngine.Mvc:
return ViewHelper.GetFilePath(this);
case RenderingEngine.WebForms:
return MasterPageHelper.GetFilePath(this);
default:
throw new ArgumentOutOfRangeException();
}
}
}
public static Hashtable TemplateAliases
{
get { return _templateAliases; }
set { _templateAliases = value; }
}
#region Constructors
public Template(int id) : base(id) { }
public Template(Guid id) : base(id) { }
#endregion
/// <summary>
/// Used to persist object changes to the database. In Version3.0 it's just a stub for future compatibility
/// </summary>
public override void Save()
{
SaveEventArgs e = new SaveEventArgs();
FireBeforeSave(e);
if (!e.Cancel)
{
FlushCache();
base.Save();
FireAfterSave(e);
}
}
public string GetRawText()
{
return base.Text;
}
public override string Text
{
get
{
string tempText = base.Text;
if (!tempText.StartsWith("#"))
return tempText;
else
{
language.Language lang = language.Language.GetByCultureCode(System.Threading.Thread.CurrentThread.CurrentCulture.Name);
if (lang != null)
{
if (Dictionary.DictionaryItem.hasKey(tempText.Substring(1, tempText.Length - 1)))
{
Dictionary.DictionaryItem di = new Dictionary.DictionaryItem(tempText.Substring(1, tempText.Length - 1));
if (di != null)
return di.Value(lang.id);
}
}
return "[" + tempText + "]";
}
}
set
{
FlushCache();
base.Text = value;
}
}
public string OutputContentType
{
get { return _OutputContentType; }
set { _OutputContentType = value; }
}
protected override void setupNode()
{
base.setupNode();
IRecordsReader dr = SqlHelper.ExecuteReader("Select alias,design,master from cmsTemplate where nodeId = " + this.Id);
bool hasRows = dr.Read();
if (hasRows)
{
_alias = dr.GetString("alias");
_design = dr.GetString("design");
//set the master template to zero if it's null
_mastertemplate = dr.IsNull("master") ? 0 : dr.GetInt("master");
}
dr.Close();
if (Umbraco.Core.Configuration.UmbracoSettings.DefaultRenderingEngine == RenderingEngine.Mvc && ViewHelper.ViewExists(this))
_design = ViewHelper.GetFileContents(this);
else
_design = MasterPageHelper.GetFileContents(this);
}
public new string Path
{
get
{
List<int> path = new List<int>();
Template working = this;
while (working != null)
{
path.Add(working.Id);
try
{
if (working.MasterTemplate != 0)
{
working = new Template(working.MasterTemplate);
}
else
{
working = null;
}
}
catch (ArgumentException)
{
working = null;
}
}
path.Add(-1);
path.Reverse();
string sPath = string.Join(",", path.ConvertAll(item => item.ToString()).ToArray());
return sPath;
}
set
{
base.Path = value;
}
}
public string Alias
{
get { return _alias; }
set
{
FlushCache();
_oldAlias = _alias;
_alias = value;
SqlHelper.ExecuteNonQuery("Update cmsTemplate set alias = @alias where NodeId = " + this.Id, SqlHelper.CreateParameter("@alias", _alias));
_templateAliasesInitialized = false;
initTemplateAliases();
}
}
public bool HasMasterTemplate
{
get { return (_mastertemplate > 0); }
}
public override bool HasChildren
{
get
{
if (!_hasChildrenInitialized)
{
_hasChildren = SqlHelper.ExecuteScalar<int>("select count(NodeId) as tmp from cmsTemplate where master = " + Id) > 0;
}
return _hasChildren;
}
set
{
_hasChildrenInitialized = true;
_hasChildren = value;
}
}
public int MasterTemplate
{
get { return _mastertemplate; }
set
{
FlushCache();
_mastertemplate = value;
//set to null if it's zero
object masterVal = value;
if (value == 0) masterVal = DBNull.Value;
SqlHelper.ExecuteNonQuery("Update cmsTemplate set master = @master where NodeId = @nodeId",
SqlHelper.CreateParameter("@master", masterVal),
SqlHelper.CreateParameter("@nodeId", this.Id));
}
}
public string Design
{
get { return _design; }
set
{
FlushCache();
_design = value.Trim(NewLineChars);
//we only switch to MVC View editing if the template has a view file, and MVC editing is enabled
if (Umbraco.Core.Configuration.UmbracoSettings.DefaultRenderingEngine == RenderingEngine.Mvc && !MasterPageHelper.IsMasterPageSyntax(_design))
{
MasterPageHelper.RemoveMasterPageFile(this.Alias);
MasterPageHelper.RemoveMasterPageFile(_oldAlias);
_design = ViewHelper.UpdateViewFile(this, _oldAlias);
}
else if (UmbracoSettings.UseAspNetMasterPages)
{
ViewHelper.RemoveViewFile(this.Alias);
ViewHelper.RemoveViewFile(_oldAlias);
_design = MasterPageHelper.UpdateMasterPageFile(this, _oldAlias);
}
SqlHelper.ExecuteNonQuery("Update cmsTemplate set design = @design where NodeId = @id",
SqlHelper.CreateParameter("@design", _design),
SqlHelper.CreateParameter("@id", Id));
}
}
public XmlNode ToXml(XmlDocument doc)
{
XmlNode template = doc.CreateElement("Template");
template.AppendChild(xmlHelper.addTextNode(doc, "Name", this.Text));
template.AppendChild(xmlHelper.addTextNode(doc, "Alias", this.Alias));
if (this.MasterTemplate != 0)
{
template.AppendChild(xmlHelper.addTextNode(doc, "Master", new Template(this.MasterTemplate).Alias));
}
template.AppendChild(xmlHelper.addCDataNode(doc, "Design", this.Design));
return template;
}
/// <summary>
/// Removes any references to this templates from child templates, documenttypes and documents
/// </summary>
public void RemoveAllReferences()
{
if (HasChildren)
{
foreach (Template t in Template.GetAllAsList().FindAll(delegate(Template t) { return t.MasterTemplate == this.Id; }))
{
t.MasterTemplate = 0;
}
}
RemoveFromDocumentTypes();
// remove from documents
Document.RemoveTemplateFromDocument(this.Id);
}
public void RemoveFromDocumentTypes()
{
foreach (DocumentType dt in DocumentType.GetAllAsList().Where(x => x.allowedTemplates.Select(t => t.Id).Contains(this.Id)))
{
dt.RemoveTemplate(this.Id);
dt.Save();
}
}
public IEnumerable<DocumentType> GetDocumentTypes()
{
return DocumentType.GetAllAsList().Where(x => x.allowedTemplates.Select(t => t.Id).Contains(this.Id));
}
/// <summary>
/// This checks what the default rendering engine is set in config but then also ensures that there isn't already
/// a template that exists in the opposite rendering engine's template folder, then returns the appropriate
/// rendering engine to use.
/// </summary>
/// <param name="t"></param>
/// <param name="design">If a template body is specified we'll check if it contains master page markup, if it does we'll auto assume its webforms </param>
/// <returns></returns>
/// <remarks>
/// The reason this is required is because for example, if you have a master page file already existing under ~/masterpages/Blah.aspx
/// and then you go to create a template in the tree called Blah and the default rendering engine is MVC, it will create a Blah.cshtml
/// empty template in ~/Views. This means every page that is using Blah will go to MVC and render an empty page.
/// This is mostly related to installing packages since packages install file templates to the file system and then create the
/// templates in business logic. Without this, it could cause the wrong rendering engine to be used for a package.
/// </remarks>
private static RenderingEngine DetermineRenderingEngine(Template t, string design = null)
{
var engine = Umbraco.Core.Configuration.UmbracoSettings.DefaultRenderingEngine;
if (!design.IsNullOrWhiteSpace() && MasterPageHelper.IsMasterPageSyntax(design))
{
//there is a design but its definitely a webforms design
return RenderingEngine.WebForms;
}
switch (engine)
{
case RenderingEngine.Mvc:
//check if there's a view in ~/masterpages
if (MasterPageHelper.MasterPageExists(t) && !ViewHelper.ViewExists(t))
{
//change this to webforms since there's already a file there for this template alias
engine = RenderingEngine.WebForms;
}
break;
case RenderingEngine.WebForms:
//check if there's a view in ~/views
if (ViewHelper.ViewExists(t) && !MasterPageHelper.MasterPageExists(t))
{
//change this to mvc since there's already a file there for this template alias
engine = RenderingEngine.Mvc;
}
break;
}
return engine;
}
public static Template MakeNew(string Name, BusinessLogic.User u, Template master)
{
return MakeNew(Name, u, master, null);
}
private static Template MakeNew(string name, BusinessLogic.User u, string design)
{
return MakeNew(name, u, null, design);
}
public static Template MakeNew(string name, BusinessLogic.User u)
{
return MakeNew(name, u, design: null);
}
private static Template MakeNew(string name, BusinessLogic.User u, Template master, string design)
{
// CMSNode MakeNew(int parentId, Guid objectType, int userId, int level, string text, Guid uniqueID)
CMSNode n = CMSNode.MakeNew(-1, _objectType, u.Id, 1, name, Guid.NewGuid());
//ensure unique alias
name = helpers.Casing.SafeAlias(name);
if (GetByAlias(name) != null)
name = EnsureUniqueAlias(name, 1);
name = name.Replace("/", ".").Replace("\\", "");
if (name.Length > 100)
name = name.Substring(0, 95) + "...";
SqlHelper.ExecuteNonQuery("INSERT INTO cmsTemplate (NodeId, Alias, design, master) VALUES (@nodeId, @alias, @design, @master)",
SqlHelper.CreateParameter("@nodeId", n.Id),
SqlHelper.CreateParameter("@alias", name),
SqlHelper.CreateParameter("@design", ' '),
SqlHelper.CreateParameter("@master", DBNull.Value));
Template t = new Template(n.Id);
NewEventArgs e = new NewEventArgs();
t.OnNew(e);
if (master != null)
t.MasterTemplate = master.Id;
switch (DetermineRenderingEngine(t, design))
{
case RenderingEngine.Mvc:
ViewHelper.CreateViewFile(t, true);
break;
case RenderingEngine.WebForms:
MasterPageHelper.CreateMasterPage(t, true);
break;
}
//if a design is supplied ensure it is updated.
if (!design.IsNullOrWhiteSpace())
{
t.ImportDesign(design);
}
return t;
}
private static string EnsureUniqueAlias(string alias, int attempts)
{
if (GetByAlias(alias + attempts.ToString()) == null)
return alias + attempts.ToString();
else
{
attempts++;
return EnsureUniqueAlias(alias, attempts);
}
}
public static Template GetByAlias(string Alias)
{
return GetByAlias(Alias, false);
}
public static Template GetByAlias(string Alias, bool useCache)
{
if (!useCache)
{
try
{
return new Template(SqlHelper.ExecuteScalar<int>("select nodeId from cmsTemplate where alias = @alias", SqlHelper.CreateParameter("@alias", Alias)));
}
catch
{
return null;
}
}
//return from cache instead
var id = GetTemplateIdFromAlias(Alias);
return id == 0 ? null : GetTemplate(id);
}
[Obsolete("Obsolete, please use GetAllAsList() method instead", true)]
public static Template[] getAll()
{
return GetAllAsList().ToArray();
}
public static List<Template> GetAllAsList()
{
Guid[] ids = CMSNode.TopMostNodeIds(_objectType);
List<Template> retVal = new List<Template>();
foreach (Guid id in ids)
{
retVal.Add(new Template(id));
}
retVal.Sort(delegate(Template t1, Template t2) { return t1.Text.CompareTo(t2.Text); });
return retVal;
}
public static int GetTemplateIdFromAlias(string alias)
{
alias = alias.ToLower();
initTemplateAliases();
if (TemplateAliases.ContainsKey(alias))
return (int)TemplateAliases[alias];
else
return 0;
}
private static void initTemplateAliases()
{
if (!_templateAliasesInitialized)
{
lock (_templateLoaderLocker)
{
//double check
if (!_templateAliasesInitialized)
{
_templateAliases.Clear();
foreach (Template t in GetAllAsList())
TemplateAliases.Add(t.Alias.ToLower(), t.Id);
_templateAliasesInitialized = true;
}
}
}
}
public override void delete()
{
// don't allow template deletion if it has child templates
if (this.HasChildren)
{
var ex = new InvalidOperationException("Can't delete a master template. Remove any bindings from child templates first.");
LogHelper.Error<Template>("Can't delete a master template. Remove any bindings from child templates first.", ex);
throw ex;
}
// NH: Changed this; if you delete a template we'll remove all references instead of
// throwing an exception
if (DocumentType.GetAllAsList().Where(x => x.allowedTemplates.Select(t => t.Id).Contains(this.Id)).Count() > 0)
RemoveAllReferences();
DeleteEventArgs e = new DeleteEventArgs();
FireBeforeDelete(e);
if (!e.Cancel)
{
//re-set the template aliases
_templateAliasesInitialized = false;
initTemplateAliases();
//delete the template
SqlHelper.ExecuteNonQuery("delete from cmsTemplate where NodeId =" + this.Id);
base.delete();
// remove masterpages
if (System.IO.File.Exists(MasterPageFile))
System.IO.File.Delete(MasterPageFile);
if (System.IO.File.Exists(Umbraco.Core.IO.IOHelper.MapPath(ViewHelper.ViewPath(this.Alias))))
System.IO.File.Delete(Umbraco.Core.IO.IOHelper.MapPath(ViewHelper.ViewPath(this.Alias)));
FireAfterDelete(e);
}
}
[Obsolete("This method, doesnt actually do anything, as the file is created when the design is set", false)]
public void _SaveAsMasterPage()
{
//SaveMasterPageFile(ConvertToMasterPageSyntax(Design));
}
public string GetMasterContentElement(int masterTemplateId)
{
if (masterTemplateId != 0)
{
string masterAlias = new Template(masterTemplateId).Alias.Replace(" ", "");
return
String.Format("<asp:Content ContentPlaceHolderID=\"{1}ContentPlaceHolder\" runat=\"server\">",
Alias.Replace(" ", ""), masterAlias);
}
else
return
String.Format("<asp:Content ContentPlaceHolderID=\"ContentPlaceHolderDefault\" runat=\"server\">",
Alias.Replace(" ", ""));
}
public List<string> contentPlaceholderIds()
{
List<string> retVal = new List<string>();
string masterPageFile = this.MasterPageFile;
string mp = System.IO.File.ReadAllText(masterPageFile);
string pat = "<asp:ContentPlaceHolder+(\\s+[a-zA-Z]+\\s*=\\s*(\"([^\"]*)\"|'([^']*)'))*\\s*/?>";
Regex r = new Regex(pat, RegexOptions.IgnoreCase);
Match m = r.Match(mp);
while (m.Success)
{
CaptureCollection cc = m.Groups[3].Captures;
foreach (Capture c in cc)
{
if (c.Value != "server")
retVal.Add(c.Value);
}
m = m.NextMatch();
}
return retVal;
}
public string ConvertToMasterPageSyntax(string templateDesign)
{
string masterPageContent = GetMasterContentElement(MasterTemplate) + Environment.NewLine;
masterPageContent += templateDesign;
// Parse the design for getitems
masterPageContent = EnsureMasterPageSyntax(masterPageContent);
// append ending asp:content element
masterPageContent += Environment.NewLine
+ "</asp:Content>"
+ Environment.NewLine;
return masterPageContent;
}
public string EnsureMasterPageSyntax(string masterPageContent)
{
ReplaceElement(ref masterPageContent, "?UMBRACO_GETITEM", "umbraco:Item", true);
ReplaceElement(ref masterPageContent, "?UMBRACO_GETITEM", "umbraco:Item", false);
// Parse the design for macros
ReplaceElement(ref masterPageContent, "?UMBRACO_MACRO", "umbraco:Macro", true);
ReplaceElement(ref masterPageContent, "?UMBRACO_MACRO", "umbraco:Macro", false);
// Parse the design for load childs
masterPageContent = masterPageContent.Replace("<?UMBRACO_TEMPLATE_LOAD_CHILD/>", GetAspNetMasterPageContentContainer()).Replace("<?UMBRACO_TEMPLATE_LOAD_CHILD />", GetAspNetMasterPageContentContainer());
// Parse the design for aspnet forms
GetAspNetMasterPageForm(ref masterPageContent);
masterPageContent = masterPageContent.Replace("</?ASPNET_FORM>", "</form>");
// Parse the design for aspnet heads
masterPageContent = masterPageContent.Replace("</ASPNET_HEAD>", String.Format("<head id=\"{0}Head\" runat=\"server\">", Alias.Replace(" ", "")));
masterPageContent = masterPageContent.Replace("</?ASPNET_HEAD>", "</head>");
return masterPageContent;
}
public void ImportDesign(string design)
{
Design = design;
}
public void SaveMasterPageFile(string masterPageContent)
{
//this will trigger the helper and store everything
this.Design = masterPageContent;
}
private void GetAspNetMasterPageForm(ref string design)
{
Match formElement = Regex.Match(design, GetElementRegExp("?ASPNET_FORM", false), RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
if (formElement != null && formElement.Value != "")
{
string formReplace = String.Format("<form id=\"{0}Form\" runat=\"server\">", Alias.Replace(" ", ""));
if (formElement.Groups.Count == 0)
{
formReplace += "<asp:scriptmanager runat=\"server\"></asp:scriptmanager>";
}
design = design.Replace(formElement.Value, formReplace);
}
}
private string GetAspNetMasterPageContentContainer()
{
return String.Format(
"<asp:ContentPlaceHolder ID=\"{0}ContentPlaceHolder\" runat=\"server\"></asp:ContentPlaceHolder>",
Alias.Replace(" ", ""));
}
private void ReplaceElement(ref string design, string elementName, string newElementName, bool checkForQuotes)
{
MatchCollection m =
Regex.Matches(design, GetElementRegExp(elementName, checkForQuotes),
RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
foreach (Match match in m)
{
GroupCollection groups = match.Groups;
// generate new element (compensate for a closing trail on single elements ("/"))
string elementAttributes = groups[1].Value;
// test for macro alias
if (elementName == "?UMBRACO_MACRO")
{
Hashtable tags = helpers.xhtml.ReturnAttributes(match.Value);
if (tags["macroAlias"] != null)
elementAttributes = String.Format(" Alias=\"{0}\"", tags["macroAlias"].ToString()) + elementAttributes;
else if (tags["macroalias"] != null)
elementAttributes = String.Format(" Alias=\"{0}\"", tags["macroalias"].ToString()) + elementAttributes;
}
string newElement = "<" + newElementName + " runat=\"server\" " + elementAttributes.Trim() + ">";
if (elementAttributes.EndsWith("/"))
{
elementAttributes = elementAttributes.Substring(0, elementAttributes.Length - 1);
}
else if (groups[0].Value.StartsWith("</"))
// It's a closing element, so generate that instead of a starting element
newElement = "</" + newElementName + ">";
if (checkForQuotes)
{
// if it's inside quotes, we'll change element attribute quotes to single quotes
newElement = newElement.Replace("\"", "'");
newElement = String.Format("\"{0}\"", newElement);
}
design = design.Replace(match.Value, newElement);
}
}
private string GetElementRegExp(string elementName, bool checkForQuotes)
{
if (checkForQuotes)
return String.Format("\"<[^>\\s]*\\b{0}(\\b[^>]*)>\"", elementName);
else
return String.Format("<[^>\\s]*\\b{0}(\\b[^>]*)>", elementName);
}
protected virtual void FlushCache()
{
// clear local cache
cache.Cache.ClearCacheItem(GetCacheKey(Id));
}
public static Template GetTemplate(int id)
{
return Cache.GetCacheItem<Template>(GetCacheKey(id), templateCacheSyncLock,
TimeSpan.FromMinutes(30),
delegate
{
try
{
return new Template(id);
}
catch
{
return null;
}
});
}
private static string GetCacheKey(int id)
{
return UmbracoTemplateCacheKey + id;
}
public static Template Import(XmlNode n, User u)
{
string alias = xmlHelper.GetNodeValue(n.SelectSingleNode("Alias"));
Template t = Template.GetByAlias(alias);
var design = xmlHelper.GetNodeValue(n.SelectSingleNode("Design"));
if (t == null)
{
//create the template with the design if one is specified
t = MakeNew(xmlHelper.GetNodeValue(n.SelectSingleNode("Name")), u,
design.IsNullOrWhiteSpace() ? null : design);
}
t.Alias = alias;
return t;
}
#region Events
//EVENTS
/// <summary>
/// The save event handler
/// </summary>
public delegate void SaveEventHandler(Template sender, SaveEventArgs e);
/// <summary>
/// The new event handler
/// </summary>
public delegate void NewEventHandler(Template sender, NewEventArgs e);
/// <summary>
/// The delete event handler
/// </summary>
public delegate void DeleteEventHandler(Template sender, DeleteEventArgs e);
/// <summary>
/// Occurs when [before save].
/// </summary>
public static event SaveEventHandler BeforeSave;
/// <summary>
/// Raises the <see cref="E:BeforeSave"/> event.
/// </summary>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
protected virtual void FireBeforeSave(SaveEventArgs e)
{
if (BeforeSave != null)
BeforeSave(this, e);
}
/// <summary>
/// Occurs when [after save].
/// </summary>
public static event SaveEventHandler AfterSave;
/// <summary>
/// Raises the <see cref="E:AfterSave"/> event.
/// </summary>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
protected virtual void FireAfterSave(SaveEventArgs e)
{
if (AfterSave != null)
AfterSave(this, e);
}
/// <summary>
/// Occurs when [new].
/// </summary>
public static event NewEventHandler New;
/// <summary>
/// Raises the <see cref="E:New"/> event.
/// </summary>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
protected virtual void OnNew(NewEventArgs e)
{
if (New != null)
New(this, e);
}
/// <summary>
/// Occurs when [before delete].
/// </summary>
public static event DeleteEventHandler BeforeDelete;
/// <summary>
/// Raises the <see cref="E:BeforeDelete"/> event.
/// </summary>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
protected virtual void FireBeforeDelete(DeleteEventArgs e)
{
if (BeforeDelete != null)
BeforeDelete(this, e);
}
/// <summary>
/// Occurs when [after delete].
/// </summary>
public static event DeleteEventHandler AfterDelete;
/// <summary>
/// Raises the <see cref="E:AfterDelete"/> event.
/// </summary>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
protected virtual void FireAfterDelete(DeleteEventArgs e)
{
if (AfterDelete != null)
AfterDelete(this, e);
}
#endregion
}
}