protected override async Task <List <int> > SaveExecuteAsync(List <MessageTemplateForSave> entities, bool returnIds)
        {
            #region Validation

            var definitionedCollections = new string[]
            {
                nameof(Document),
                nameof(Resource),
                nameof(Agent),
                nameof(Lookup)
            };

            foreach (var(entity, index) in entities.Indexed())
            {
                if (entity.Cardinality == Cardinalities.Multiple)
                {
                    // ListExpression
                    if (string.IsNullOrWhiteSpace(entity.ListExpression))
                    {
                        var path = $"[{index}].{nameof(entity.ListExpression)}";
                        var msg  = _localizer[ErrorMessages.Error_Field0IsRequired, _localizer["NotificationTemplate_ListExpression"]];
                        ModelState.AddError(path, msg);
                    }
                    else
                    {
                        try
                        {
                            TemplexBase.Parse(entity.ListExpression);
                        }
                        catch
                        {
                            var path = $"[{index}].{nameof(entity.ListExpression)}";
                            var msg  = _localizer["Error_InvalidTemplateExpression0", entity.ListExpression];
                            ModelState.AddError(path, msg);
                        }
                    }
                }

                if (entity.Trigger == Triggers.Automatic)
                {
                    // Automatic notifications can only be created by a Read-All user.
                    var permissions = await FactBehavior.UserPermissions(view : "all", action : "Read", cancellation : default);
        protected override async Task <List <int> > SaveExecuteAsync(List <NotificationTemplateForSave> entities, bool returnIds)
        {
            #region Validation

            var definitionedCollections = new string[]
            {
                nameof(Document),
                nameof(Resource),
                nameof(Agent),
                nameof(Lookup)
            };

            foreach (var(entity, index) in entities.Indexed())
            {
                if (entity.Cardinality == Cardinalities.Bulk)
                {
                    // ListExpression
                    if (string.IsNullOrWhiteSpace(entity.ListExpression))
                    {
                        var path = $"[{index}].{nameof(entity.ListExpression)}";
                        var msg  = _localizer[ErrorMessages.Error_Field0IsRequired, _localizer["NotificationTemplate_ListExpression"]];
                        ModelState.AddError(path, msg);
                    }
                    else
                    {
                        try
                        {
                            TemplexBase.Parse(entity.ListExpression);
                        }
                        catch
                        {
                            var path = $"[{index}].{nameof(entity.ListExpression)}";
                            var msg  = _localizer["Error_InvalidTemplateExpression0", entity.ListExpression];
                            ModelState.AddError(path, msg);
                        }
                    }

                    // AddressExpression
                    if (string.IsNullOrWhiteSpace(entity.AddressExpression))
                    {
                        var path = $"[{index}].{nameof(entity.AddressExpression)}";
                        var msg  = _localizer[ErrorMessages.Error_Field0IsRequired, _localizer["NotificationTemplate_AddressExpression"]];
                        ModelState.AddError(path, msg);
                    }
                    else
                    {
                        try
                        {
                            TemplexBase.Parse(entity.AddressExpression);
                        }
                        catch
                        {
                            var path = $"[{index}].{nameof(entity.AddressExpression)}";
                            var msg  = _localizer["Error_InvalidTemplateExpression0", entity.AddressExpression];
                            ModelState.AddError(path, msg);
                        }
                    }
                }

                if (entity.Trigger == Triggers.Automatic)
                {
                    // Schedule
                    if (string.IsNullOrWhiteSpace(entity.Schedule))
                    {
                        var path = $"[{index}].{nameof(entity.Schedule)}";
                        var msg  = _localizer[ErrorMessages.Error_Field0IsRequired, _localizer["NotificationTemplate_Schedule"]];
                        ModelState.AddError(path, msg);
                    }
                    else
                    {
                        string faultyCron = null;
                        try
                        {
                            var crons = entity.Schedule.Split(';')
                                        .Where(e => !string.IsNullOrWhiteSpace(e))
                                        .Select(e => e.Trim());

                            foreach (var cron in crons)
                            {
                                faultyCron = cron;
                                CronExpression.Parse(cron);
                            }
                        }
                        catch
                        {
                            var path = $"[{index}].{nameof(entity.Schedule)}";
                            var msg  = _localizer["Error_InvalidCronExpression0", faultyCron];
                            ModelState.AddError(path, msg);
                        }
                    }

                    if (string.IsNullOrWhiteSpace(entity.ConditionExpression))
                    {
                        // Doesn't matter
                    }
                    else
                    {
                        try
                        {
                            TemplexBase.Parse(entity.ConditionExpression);
                        }
                        catch
                        {
                            var path = $"[{index}].{nameof(entity.ConditionExpression)}";
                            var msg  = _localizer["Error_InvalidTemplateExpression0", entity.ConditionExpression];
                            ModelState.AddError(path, msg);
                        }
                    }
                }

                if (entity.Trigger == Triggers.Manual)
                {
                    // Usage
                    if (string.IsNullOrWhiteSpace(entity.Usage))
                    {
                        var path = $"[{index}].{nameof(entity.Usage)}";
                        var msg  = _localizer[ErrorMessages.Error_Field0IsRequired, _localizer["Template_Usage"]];
                        ModelState.AddError(path, msg);
                    }
                    else if (entity.Usage == TemplateUsages.FromSearchAndDetails || entity.Usage == TemplateUsages.FromDetails)
                    {
                        if (string.IsNullOrWhiteSpace(entity.Collection))
                        {
                            var path = $"[{index}].{nameof(entity.Collection)}";
                            var msg  = _localizer[ErrorMessages.Error_Field0IsRequired, _localizer["Template_Collection"]];
                            ModelState.AddError(path, msg);
                        }
                        else
                        {
                            if (definitionedCollections.Contains(entity.Collection))
                            {
                                // DefinitionId is required when querying by Id
                                if (entity.Usage == TemplateUsages.FromDetails && entity.DefinitionId == null)
                                {
                                    var path = $"[{index}].{nameof(entity.DefinitionId)}";
                                    var msg  = _localizer[ErrorMessages.Error_Field0IsRequired, _localizer["Template_DefinitionId"]];
                                    ModelState.AddError(path, msg);
                                }
                            }
                            else
                            {
                                entity.DefinitionId = null;
                            }
                        }
                    }
                    else if (entity.Usage == TemplateUsages.FromReport)
                    {
                        if (entity.ReportDefinitionId == null)
                        {
                            var path = $"[{index}].{nameof(entity.ReportDefinitionId)}";
                            var msg  = _localizer[ErrorMessages.Error_Field0IsRequired, _localizer["Template_ReportDefinitionId"]];
                            ModelState.AddError(path, msg);
                        }
                    }

                    // TODO Check that DefinitionId is compatible with Collection
                }

                if (entity.Channel == Channels.Email)
                {
                    // Subject
                    if (string.IsNullOrWhiteSpace(entity.Subject))
                    {
                        var path = $"[{index}].{nameof(entity.Subject)}";
                        var msg  = _localizer[ErrorMessages.Error_Field0IsRequired, _localizer["NotificationTemplate_Subject"]];
                        ModelState.AddError(path, msg);
                    }
                    else
                    {
                        try
                        {
                            TemplateTree.Parse(entity.Subject);
                        }
                        catch
                        {
                            var path = $"[{index}].{nameof(entity.Subject)}";
                            var msg  = _localizer["Error_InvalidTemplate"];
                            ModelState.AddError(path, msg);
                        }
                    }
                }

                // Body
                if (!string.IsNullOrWhiteSpace(entity.Body))
                {
                    if (entity.Channel == Channels.Sms)
                    {
                        const int maxSmsExpressionLength = 1024;
                        if (entity.Body.Length > maxSmsExpressionLength)
                        {
                            var path = $"[{index}].{nameof(entity.Body)}";
                            var msg  = _localizer[ErrorMessages.Error_Field0LengthMaximumOf1, _localizer["Template_Body"], maxSmsExpressionLength];
                            ModelState.AddError(path, msg);
                        }
                    }

                    try
                    {
                        TemplateTree.Parse(entity.Body);
                    }
                    catch
                    {
                        var path = $"[{index}].{nameof(entity.Body)}";
                        var msg  = _localizer["Error_InvalidTemplate"];
                        ModelState.AddError(path, msg);
                    }
                }

                // Caption
                try
                {
                    TemplateTree.Parse(entity.Caption);
                }
                catch
                {
                    var path = $"[{index}].{nameof(entity.Caption)}";
                    var msg  = _localizer["Error_InvalidTemplate"];
                    ModelState.AddError(path, msg);
                }

                var duplicateKeys = entity
                                    .Parameters
                                    .Select(e => e.Key)
                                    .GroupBy(e => e)
                                    .Where(e => e.Count() > 1)
                                    .Select(e => e.FirstOrDefault())
                                    .ToHashSet();

                // Parameters
                foreach (var(parameter, parameterIndex) in entity.Parameters.Indexed())
                {
                    if (!TemplexVariable.IsValidVariableName(parameter.Key))
                    {
                        var path = $"[{index}].{nameof(entity.Parameters)}[{parameterIndex}].{nameof(parameter.Key)}";
                        var msg  = "Invalid Key. Valid keys contain only alphanumeric characters, dollar symbols, and underscores and do not start with a number.";
                        ModelState.AddError(path, msg);
                    }
                    else if (duplicateKeys.Contains(parameter.Key))
                    {
                        var path = $"[{index}].{nameof(entity.Parameters)}[{parameterIndex}].{nameof(parameter.Key)}";
                        var msg  = $"The Key '{parameter.Key}' is used more than once.";
                        ModelState.AddError(path, msg);
                    }
                }

                // Attachments
                foreach (var(attachment, attachmentIndex) in entity.Attachments.Indexed())
                {
                    if (!string.IsNullOrWhiteSpace(attachment.ContextOverride))
                    {
                        try
                        {
                            TemplexBase.Parse(attachment.ContextOverride);
                        }
                        catch
                        {
                            var path = $"[{index}].{nameof(entity.Attachments)}[{attachmentIndex}].{nameof(attachment.ContextOverride)}";
                            var msg  = _localizer["Error_InvalidTemplateExpression0", attachment.ContextOverride];
                            ModelState.AddError(path, msg);
                        }
                    }

                    if (!string.IsNullOrWhiteSpace(attachment.DownloadNameOverride))
                    {
                        try
                        {
                            TemplateTree.Parse(attachment.DownloadNameOverride);
                        }
                        catch
                        {
                            var path = $"[{index}].{nameof(entity.Attachments)}[{attachmentIndex}].{nameof(attachment.DownloadNameOverride)}";
                            var msg  = _localizer["Error_InvalidTemplate0", attachment.DownloadNameOverride];
                            ModelState.AddError(path, msg);
                        }
                    }
                }

                // Subscribers
                foreach (var(subscriber, subscriberIndex) in entity.Subscribers.Indexed())
                {
                    if (subscriber.AddressType == AddressTypes.User)
                    {
                        if (subscriber.UserId == null)
                        {
                            var path = $"[{index}].{nameof(entity.Subscribers)}[{subscriberIndex}].{nameof(subscriber.UserId)}";
                            var msg  = _localizer[ErrorMessages.Error_Field0IsRequired, _localizer["NotificationTemplate_User"]];
                            ModelState.AddError(path, msg);
                        }
                    }

                    if (subscriber.AddressType == AddressTypes.Text)
                    {
                        if (entity.Channel == Channels.Email)
                        {
                            if (string.IsNullOrWhiteSpace(subscriber.Email))
                            {
                                var path = $"[{index}].{nameof(entity.Subscribers)}[{subscriberIndex}].{nameof(subscriber.Email)}";
                                var msg  = _localizer[ErrorMessages.Error_Field0IsRequired, _localizer["NotificationTemplate_Email"]];
                                ModelState.AddError(path, msg);
                            }
                            else
                            {
                                try
                                {
                                    TemplateTree.Parse(subscriber.Email);
                                }
                                catch
                                {
                                    var path = $"[{index}].{nameof(entity.Subscribers)}[{subscriberIndex}].{nameof(subscriber.Email)}";
                                    var msg  = _localizer["Error_InvalidTemplate"];
                                    ModelState.AddError(path, msg);
                                }
                            }
                        }

                        if (entity.Channel == Channels.Sms)
                        {
                            if (string.IsNullOrWhiteSpace(subscriber.Phone))
                            {
                                var path = $"[{index}].{nameof(entity.Subscribers)}[{subscriberIndex}].{nameof(subscriber.Phone)}";
                                var msg  = _localizer[ErrorMessages.Error_Field0IsRequired, _localizer["NotificationTemplate_Phone"]];
                                ModelState.AddError(path, msg);
                            }
                            else
                            {
                                try
                                {
                                    TemplateTree.Parse(subscriber.Phone);
                                }
                                catch
                                {
                                    var path = $"[{index}].{nameof(entity.Subscribers)}[{subscriberIndex}].{nameof(subscriber.Phone)}";
                                    var msg  = _localizer["Error_InvalidTemplate"];
                                    ModelState.AddError(path, msg);
                                }
                            }
                        }
                    }
                }
            }

            #endregion

            SaveOutput result = await _behavior.Repository.NotificationTemplates__Save(
                entities : entities,
                returnIds : returnIds,
                validateOnly : ModelState.IsError,
                top : ModelState.RemainingErrors,
                userId : UserId);

            AddErrorsAndThrowIfInvalid(result.Errors);

            return(result.Ids);
        }