/// <summary>
        /// Creates or update a registration session. This method operates
        /// inside a database lock to prevent other sessions from being
        /// modified at the same time.
        /// </summary>
        /// <param name="sessionGuid">The session unique identifier.</param>
        /// <param name="createSession">The method to call to get a new <see cref="RegistrationSession"/> object that will be persisted to the database.</param>
        /// <param name="updateSession">The method to call to update an existing <see cref="RegistrationSession"/> object with any new information.</param>
        /// <param name="errorMessage">The error message.</param>
        /// <returns>The <see cref="RegistrationSession"/> that was created or updated; or <c>null</c> if an error occurred.</returns>
        public static RegistrationSession CreateOrUpdateSession(Guid sessionGuid, Func <RegistrationSession> createSession, Action <RegistrationSession> updateSession, out string errorMessage)
        {
            using (var rockContext = new RockContext())
            {
                var registrationSessionService          = new RegistrationSessionService(rockContext);
                RegistrationSession registrationSession = null;
                string internalErrorMessage             = null;

                rockContext.WrapTransactionIf(() =>
                {
                    /*
                     * This is an anti-pattern. Do not just copy this and use it
                     * as a pattern elsewhere. Discuss with the team before trying
                     * to use this anywhere else.
                     *
                     * This single use-case was discussed between Jon and Daniel on
                     * 9/17/2021 and deemed the best choice for the moment. In the
                     * future we might convert this to a helper method that doesn't
                     * require custom SQL each place it is used - but we really
                     * shouldn't do full table locks as a matter of practice.
                     *
                     * Daniel Hazelbaker 9/17/2021
                     */

                    // Initiate a full table lock so nothing else can query data,
                    // otherwise they might get a count that will no longer be
                    // valid after our transaction is committed.
                    rockContext.Database.ExecuteSqlCommand("SELECT TOP 1 Id FROM [RegistrationSession] WITH (TABLOCKX, HOLDLOCK)");

                    var registrationService = new RegistrationService(rockContext);

                    // Load the registration session and determine if it was expired already.
                    registrationSession      = registrationSessionService.Get(sessionGuid);
                    var wasExpired           = registrationSession != null && registrationSession.ExpirationDateTime < RockDateTime.Now;
                    var oldRegistrationCount = registrationSession?.RegistrationCount ?? 0;

                    // If the session didn't exist then create a new one, otherwise
                    // update the existing one.
                    if (registrationSession == null)
                    {
                        registrationSession = createSession();

                        registrationSessionService.Add(registrationSession);
                    }
                    else
                    {
                        updateSession(registrationSession);
                    }

                    // Get the context information about the registration, specifically
                    // the timeout and spots available.
                    var context = registrationService.GetRegistrationContext(registrationSession.RegistrationInstanceId, out internalErrorMessage);

                    if (internalErrorMessage.IsNotNullOrWhiteSpace())
                    {
                        return(false);
                    }

                    // Set the new expiration date.
                    registrationSession.ExpirationDateTime = context.RegistrationSettings.TimeoutMinutes.HasValue
                        ? RockDateTime.Now.AddMinutes(context.RegistrationSettings.TimeoutMinutes.Value)
                        : RockDateTime.Now.AddDays(1);

                    // Determine the number of registrants. If the registration was
                    // expired then we need all spots requested again. Otherwise we
                    // just need to be able to reserve the number of new spots since
                    // the last session save.
                    var newRegistrantCount = wasExpired
                        ? registrationSession.RegistrationCount
                        : (registrationSession.RegistrationCount - oldRegistrationCount);

                    // Handle the possibility that there is a change in the number of
                    // registrants in the session.
                    if (context.SpotsRemaining.HasValue && context.SpotsRemaining.Value < newRegistrantCount)
                    {
                        internalErrorMessage = "There is not enough capacity remaining for this many registrants.";
                        return(false);
                    }

                    rockContext.SaveChanges();
                    internalErrorMessage = string.Empty;

                    return(true);
                });

                errorMessage = internalErrorMessage;

                return(errorMessage.IsNullOrWhiteSpace() ? registrationSession : null);
            }
        }
Beispiel #2
0
        /// <summary>
        /// Handles the SaveClick event of the mdSendTest control.
        /// </summary>
        protected void mdSendTest_SaveClick(object sender, EventArgs e)
        {
            const bool DISABLE_PERSON_HISTORY = true;
            string     currentEmail           = CurrentPerson.Email;

            using (var rockContext = new RockContext())
            {
                rockContext.WrapTransactionIf(() =>
                {
                    try
                    {
                        var mergeInfo = BuildSystemCommunication();

                        var rockEmailMessage = mergeInfo.RockEmailMessageRecord;

                        if (rockEmailMessage == null)
                        {
                            throw new Exception($"A valid system communication was not selected.");
                        }

                        if (rockEmailMessage.SystemCommunicationId.GetValueOrDefault(0) == 0)
                        {
                            throw new Exception($"The system communication specified is not valid.");
                        }

                        var emailPerson = GetTargetPerson(rockContext);

                        // Remove the lava debug command if it is specified in the message template.
                        var message = rockEmailMessage.Message.Replace(PageConstants.LavaDebugCommand, string.Empty);

                        rockEmailMessage.AdditionalMergeFields = mergeInfo.MergeFields.ToDictionary(k => k.Key, v => ( object )v.Value);
                        rockEmailMessage.CurrentPerson         = emailPerson;
                        rockEmailMessage.Message = message;

                        var sendErrorMessages = new List <string>();

                        // Set person email to the email specified in the dialog
                        emailPerson.Email = ebSendTest.Text;
                        rockContext.SaveChanges(disablePrePostProcessing: DISABLE_PERSON_HISTORY);

                        var recipient = new RockEmailMessageRecipient(emailPerson, mergeInfo.MergeFields);
                        rockEmailMessage.AddRecipient(recipient);
                        rockEmailMessage.Send(out sendErrorMessages);

                        if (sendErrorMessages.Count == 0)
                        {
                            nbSendTest.Text = $"Email submitted to <i>{recipient.EmailAddress}</i>";
                            nbSendTest.NotificationBoxType = NotificationBoxType.Info;
                            nbSendTest.Visible             = true;
                        }
                        else
                        {
                            var errorString = $"<ul>[ERRORS]</ul>";
                            var sbError     = new StringBuilder();

                            foreach (var error in sendErrorMessages)
                            {
                                sbError.AppendLine($"<li>{error}</li>");
                            }

                            errorString = errorString.Replace("[ERRORS]", sbError.ToString());

                            nbSendTest.Text = errorString;
                            nbSendTest.NotificationBoxType = NotificationBoxType.Danger;
                            nbSendTest.Visible             = true;
                        }

                        // Restore email to original email address
                        emailPerson.Email = currentEmail;
                        rockContext.SaveChanges(disablePrePostProcessing: DISABLE_PERSON_HISTORY);
                    }
                    catch (Exception ex)
                    {
                        nbSendTest.Text = ex.Message;
                        nbSendTest.NotificationBoxType = NotificationBoxType.Danger;
                        nbSendTest.Visible             = true;
                        return(false);
                    }

                    return(true);
                });  // End transaction
            }
        }