ZauberCMS
  • 🪄Welcome to ZauberCMS
  • Getting Started
    • 💡Quick Start
      • Databases
  • Creating A Website
    • 📹Website Build Video Tutorial
    • Content Types
      • Element Types
      • Compositions
    • Current Content Properties
      • Textbox & Textarea
      • Text Editor (Radzen)
      • Text Editor (TinyMCE)
      • Numeric
      • True / False
      • Rating
      • Checkbox, Dropdown & Radio Lists
      • Media Picker
      • Navigation
      • Material Icon Picker
      • Content Picker
      • Date Picker
      • Custom Component Picker
      • Api Key Picker
      • Colour Picker
      • Block List Editor
      • Editor Notes
      • Google Map Location Picker
      • Simple List Manager
      • Simple Dictionary Manager
      • SEO Property
      • Code Editor
      • Colour Theme Picker
    • Content
      • Publish & Unpublish
    • Querying Data
      • Extension Methods
    • Views
    • Controllers (Route Hi-Jacking)
    • Custom Components
    • Users & Roles
      • Restrict Access By Role
    • Logs
    • Audit
    • Global Settings
      • Using Global Settings
    • SEO Sitemaps
    • Hosting
  • Extending ZauberCMS
    • Overview
    • BlockListEditor
      • Content Block Preview
      • Content Block
    • Custom List Data
    • Custom Content Property
    • Custom Validation
    • Custom Admin Sections
      • Section Nav Group Menu
      • Trees
        • Tree Context Menus
      • Reusing Content Editors
    • Saving Custom Data
    • Using AppState Events
    • Before & After Save
    • Email & Storage Providers
    • Seed Data
    • SEO Checks
  • Identity
    • Overview
    • External Authentication Providers
    • Account Layout
  • Language
    • Overview
    • Adding Language Dictionaries
    • Setting The Language
    • Using Language Dictionaries
  • AppSettings
    • Detailed Errors
    • Media Settings
    • Enable Path Urls
Powered by GitBook
On this page
  • Interfaces
  • Usage
  • Advanced Example
  1. Extending ZauberCMS

Before & After Save

If you want to make changes to an entity (i.e. Content, Media etc...) before it's saved or after it's saved, or when something new is added or deleted. You can use the IBeforeEntitySave plugin.

Just implement the Interface

Interfaces

/// <summary>
/// Defines a contract for handling operations that need to occur before saving an entity to the database.
/// </summary>
public interface IBeforeEntitySave<TEntity>
{
    /// <summary>
    /// Executes operations needed before saving an entity to the database.
    /// </summary>
    /// <param name="entity">The entity instance that is about to be saved.</param>
    /// <param name="entityState">The state of the entity within the context (e.g., Added, Modified, Deleted).</param>
    /// <returns>Returns false if the save operation should be canceled; otherwise, true.</returns>
    bool BeforeSave(TEntity entity, EntityState entityState);

    /// <summary>
    /// Gets or sets the order in which operations or processes should be executed or entities should be managed.
    /// </summary>
    /// <remarks>
    /// This property is typically used to indicate the priority or position of an entity or operation
    /// in contexts such as sorting, processing, or UI display.
    /// </remarks>
    int SortOrder { get; }
}
public interface IAfterEntitySave<TEntity>
{
    /// <summary>
    /// Executes operations needed after saving an entity to the database.
    /// </summary>
    /// <param name="entity">The entity instance that is about to be saved.</param>
    /// <param name="entityState">The state of the entity within the context (e.g., Added, Modified, Deleted).</param>
    /// <returns>True if the entity needs to be resaved</returns>
    bool AfterSave(TEntity entity, EntityState entityState);

    /// <summary>
    /// Gets or sets the order in which the operation should be executed after saving an entity.
    /// </summary>
    int SortOrder { get; }
}

Usage

And then you can check the entity state or just just update the entity before it's saved. Very simple example below showing the save being abandoned

public class StopSaveIfBadWord : IBeforeEntitySave<Content>
{
    public bool BeforeSave(Content entity, EntityState entityState)
    {
        if (entity.Name != null && entity.Name.Contains("Arsenal"))
        {
            // Horrible word so don't let them save
            return false;
        }
        return true;
    }
}

Or an example showing some content being replaced

public class StopSaveIfBadWord : IBeforeEntitySave<Content>
{
    public bool BeforeSave(Content entity, EntityState entityState)
    {
        if (entity.Name != null && entity.Name.Contains("Arsenal"))
        {
            entity.Name = entity.Name.Replace("Arsenal", "Up The Spurs #COYS");
        }
        return true;
    }
}

Advanced Example

In this example, I am creating a autogenerated blog snippet of 30 words, taken from a specific element type in a block list. I then update the snippet on the content and use it for the meta description if that is blank (The meta description is part of the SEO property editor).

public partial class AddBlogSnippetOnSave : IBeforeEntitySave<Content>
{
    public bool BeforeSave(Content entity, EntityState entityState)
    {
        if (entity.ContentTypeAlias == "BlogPage")
        {
            var sb = new StringBuilder();
            foreach (var block in entity.GetValue<List<Content>>("MainContent") ?? [])
            {
                if (block.ContentTypeAlias == "RTEBlock")
                {
                    sb.Append(block.GetValue<string>("Content"));
                }
            }
            
            var allText = sb.ToString();
            
            // Parse HTML
            var htmlDoc = new HtmlDocument();
            htmlDoc.LoadHtml(allText);

            // Extract just the text from HTML (removes all tags)
            var plainText = htmlDoc.DocumentNode.InnerText;

            // Clean up the text (trim whitespace, normalize spaces)
            plainText = MyRegex().Replace(plainText.Trim(), " ");

            // Get the first 30 words
            var words = plainText.Split(' ');
            var snippet = string.Join(" ", words.Take(30));
            // No ellipsis needed, but still remove trailing punctuation
            snippet = RemovesPunctuation().Replace(snippet, "");

            var snippetPropertyValue = entity.PropertyData.FirstOrDefault(x => x.Alias == "BlogSnippet");
            if (snippetPropertyValue != null) snippetPropertyValue.Value = string.Concat(snippet, "...");
            
            // Now set Meta Description if one is not set
            var seoProperty = entity.PropertyData.FirstOrDefault(x => x.Alias == "SEO");
            if (seoProperty != null && string.IsNullOrWhiteSpace(seoProperty.Value))
            {
                var seoData = JsonSerializer.Deserialize<Meta>(seoProperty.Value);
                if (seoData?.MetaDescription.IsNullOrWhiteSpace() == true)
                {
                    seoData.MetaDescription = snippet;
                }
                seoProperty.Value = JsonSerializer.Serialize(seoData);
            }
        }
        return true;
    }

    [GeneratedRegex(@"\s+")]
    private static partial Regex MyRegex();
    [GeneratedRegex(@"[,.;:!?)\]}]+$")]
    private static partial Regex RemovesPunctuation();
}
PreviousUsing AppState EventsNextEmail & Storage Providers

Last updated 1 month ago