/// <summary>
        /// In a specified input string, replaces each link parameter with another link parameter returned by a callback function.
        /// </summary>
        /// <param name="input">The string to search for a match.</param>
        /// <param name="callback">A custom method that examines each match and returns either the original matched link parameter or a replacement link parameter.</param>
        /// <returns>
        /// The processed input text.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">input or callback</exception>
        public virtual string ReplaceLinks(string input, Action <ActionLink> callback)
        {
            if (input == null)
            {
                throw new ArgumentNullException(nameof(input));
            }
            if (callback == null)
            {
                throw new ArgumentNullException(nameof(callback));
            }

            return(s_scannerRegex.Replace(input, match =>
            {
                int actionId, contactId;
                if (!int.TryParse(match.Groups["actionId"].Value, out actionId))
                {
                    return match.Value;
                }
                var linkParams = new ActionLink
                {
                    ActionId = actionId,
                    CustomUri = match.Groups["customUri"]?.Value
                };
                if (int.TryParse(match.Groups["contactId"]?.Value, out contactId))
                {
                    linkParams.ContactId = contactId;
                }
                callback(linkParams);
                return s_routePrefix + UrlTokenEncode(linkParams);
            }));
        }
        /// <summary>
        /// Creates a new action link parameters that can be shared on the Web.
        /// </summary>
        /// <param name="actionLink">An object that configures the action link will be created.</param>
        /// <returns>
        /// The link.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">actionLink</exception>
        public virtual string UrlTokenEncode(ActionLink actionLink)
        {
            if (actionLink == null)
            {
                throw new ArgumentNullException(nameof(actionLink));
            }

            var customUri = actionLink.CustomUri != null?_uriPathNormalizer.Normalize(actionLink.CustomUri) : default(string);

            var builder = new StringBuilder();

            builder.Append(GetChecksum(actionLink.ActionId, actionLink.ContactId, customUri));
            builder.Append(ValueSeparator);
            builder.Append(actionLink.ActionId);

            if (actionLink.ContactId != null)
            {
                builder.Append(ValueSeparator);
                builder.Append(actionLink.ContactId);
            }

            if (actionLink.CustomUri != null)
            {
                builder.Append('/');
                builder.Append(customUri);
            }

            return(builder.ToString());
        }
        /// <summary>
        /// Creates a new action link that can be shared on the Web.
        /// </summary>
        /// <param name="actionLink">An object that configures the action link will be created.</param>
        /// <param name="absolute">It will create an absolute URL if true.</param>
        /// <returns>
        /// The encoded link.
        /// </returns>
        public virtual string CreateLink(ActionLink actionLink, bool absolute)
        {
            var uri = new Uri(ServerPaths.BaseUri, s_routePrefix + UrlTokenEncode(actionLink));

            return(absolute ? uri.AbsoluteUri : uri.AbsolutePath);
        }