using System.Linq.Expressions; using System.Text.Json.Serialization; namespace zero.Architecture; public sealed class DefaultShallowBlueprint : Blueprint { public DefaultShallowBlueprint(Type type) : base() { Alias = "shallow.default"; ContentType = type; LockAll(); } protected override BlueprintField AddField(Expression> selector) { throw new InvalidOperationException("Shallow blueprints can not contain unlocked fields"); } } /// /// /// public class Blueprint : Blueprint where T : ZeroEntity, new() { public List> UnlockedFields { get; private set; } = new(); /// /// These fields are not copied by the ObjectCloner /// as they are either locked or copied manually by ApplyDefaults() /// protected static string[] DefaultUncopyFields { get; set; } = new[] { "id", "url", "name", "alias", "key", "sort", "isActive", "hash", "languageId", "createdById", "createdDate", "lastModifiedById", "lastModifiedDate", "blueprint" }; public Blueprint() : base(typeof(T)) { } /// /// Merges blueprint data into a model based on the configuration /// public virtual T Apply(T blueprint, T model) { // reset blueprint options for model in case no blueprint was found if (blueprint == null || model.Blueprint == null) { model.Blueprint = null; return model; } // copy all properties which are synced from the blueprint to the model ApplyDefaults(blueprint, model); ObjectCopycat.CopyProperties(blueprint, model, DefaultUncopyFields.Union(model.Blueprint.Desync).ToArray()); return model; } /// public override ZeroEntity Apply(ZeroEntity blueprint, ZeroEntity model) => Apply(blueprint as T, model as T); /// /// Update default entity data /// protected virtual void ApplyDefaults(T blueprint, T model) { if (!model.Blueprint.Desync.Contains(nameof(ZeroEntity.Name), StringComparer)) { model.Name = blueprint.Name; model.Alias = blueprint.Alias; } if (!model.Blueprint.Desync.Contains(nameof(ZeroEntity.Key), StringComparer)) { model.Key = blueprint.Key; } if (!model.Blueprint.Desync.Contains(nameof(ZeroEntity.Sort), StringComparer)) { model.Sort = blueprint.Sort; } if (!model.Blueprint.Desync.Contains(nameof(ZeroEntity.IsActive), StringComparer)) { model.IsActive = blueprint.IsActive; } // these values can't be overridden model.Hash = blueprint.Hash; model.LanguageId = blueprint.LanguageId; model.CreatedById = blueprint.CreatedById; model.CreatedDate = blueprint.CreatedDate; model.LastModifiedById = blueprint.LastModifiedById; model.LastModifiedDate = blueprint.LastModifiedDate; } /// /// Lock a field so it always get synchronized no and cannot be changed /// public virtual void LockAll() { UnlockedFields = new(); } /// /// Lock a field so it always get synchronized no and cannot be changed /// public virtual void Lock(Expression> selector) { RemoveField(selector); } public virtual void Unlock(Expression> selector) { AddField(selector); } public virtual void UnlockDefaults() { AddField(x => x.Name); AddField(x => x.Alias); AddField(x => x.Sort); AddField(x => x.Key); AddField(x => x.IsActive); } /// public override IEnumerable GetUnlockedFieldNames() { foreach (BlueprintField field in UnlockedFields) { yield return field.FieldName.ToCamelCaseId(); } } /// /// Get existing field or create a new one /// protected virtual BlueprintField AddField(Expression> selector) { BlueprintField tempField = new(selector); BlueprintField storedField = UnlockedFields.FirstOrDefault(x => x.FieldName.Equals(tempField.FieldName, StringComparison.InvariantCultureIgnoreCase)); if (storedField != null) { return storedField; } UnlockedFields.Add(tempField); return tempField; } /// /// Removes a field /// protected virtual void RemoveField(Expression> selector) { BlueprintField tempField = new(selector); if (tempField != null) { UnlockedFields.RemoveAll(x => x.FieldName.Equals(tempField.FieldName, StringComparison.InvariantCultureIgnoreCase)); } } } /// /// /// public abstract class Blueprint { /// /// Type of the associated entity /// [JsonIgnore] public Type ContentType { get; protected set; } public string Alias { get; protected set; } /// /// String comparer for property name comparison /// protected readonly StringComparer StringComparer = StringComparer.InvariantCultureIgnoreCase; public Blueprint(Type type) { ContentType = type; Alias = type.Name.ToCamelCaseId(); } /// /// Merges blueprint data into a model based on the configuration /// public abstract ZeroEntity Apply(ZeroEntity blueprint, ZeroEntity model); /// /// Get all fields which have been unlocked /// public abstract IEnumerable GetUnlockedFieldNames(); } public class DefaultBlueprint : Blueprint where T : ZeroEntity, new() { public DefaultBlueprint(Action> expression = null) : base() { expression?.Invoke(this); } }