The validating request event args
This can be used by event subscribers to cancel image processing based on the information contained in the request, or can be used to directly manipulate the querystring parameter that will be used to process the image.
Inheritance: System.ComponentModel.CancelEventArgs
        /// <summary>
        /// Processes the image.
        /// </summary>
        /// <param name="context">
        /// the <see cref="T:System.Web.HttpContext">HttpContext</see> object that provides
        /// references to the intrinsic server objects
        /// </param>
        /// <returns>
        /// The <see cref="T:System.Threading.Tasks.Task"/>.
        /// </returns>
        private async Task ProcessImageAsync(HttpContext context)
        {
            HttpRequest request = context.Request;

            // Should we ignore this request?
            if (request.Unvalidated.RawUrl.ToUpperInvariant().Contains("IPIGNORE=TRUE"))
            {
                return;
            }

            IImageService currentService = this.GetImageServiceForRequest(request);

            if (currentService != null)
            {
                bool isFileLocal = currentService.IsFileLocalService;
                string url = request.Url.ToString();
                bool isLegacy = ProtocolRegex.Matches(url).Count > 1;
                bool hasMultiParams = url.Count(f => f == '?') > 1;
                string requestPath;
                string queryString = string.Empty;
                string urlParameters = string.Empty;

                // Legacy support. I'd like to remove this asap.
                if (isLegacy && hasMultiParams)
                {
                    // We need to split the querystring to get the actual values we want.
                    string[] paths = url.Split('?');
                    requestPath = paths[1];

                    // Handle extension-less urls.
                    if (paths.Length > 3)
                    {
                        queryString = paths[3];
                        urlParameters = paths[2];
                    }
                    else if (paths.Length > 1)
                    {
                        queryString = paths[2];
                    }
                }
                else
                {
                    if (string.IsNullOrWhiteSpace(currentService.Prefix))
                    {
                        requestPath = currentService.IsFileLocalService
                            ? HostingEnvironment.MapPath(request.Path)
                            : request.Path;
                        queryString = request.QueryString.ToString();
                    }
                    else
                    {
                        // Parse any protocol values from settings.
                        string protocol = currentService.Settings.ContainsKey("Protocol")
                                              ? currentService.Settings["Protocol"] + "://"
                                              : currentService.GetType() == typeof(RemoteImageService) ? request.Url.Scheme + "://" : string.Empty;

                        // Handle requests that require parameters.
                        if (hasMultiParams)
                        {
                            string[] paths = url.Split('?');
                            requestPath = protocol
                                          + request.Path.TrimStart('/').Remove(0, currentService.Prefix.Length).TrimStart('/')
                                          + "?" + paths[1];
                            queryString = paths[2];
                        }
                        else
                        {
                            requestPath = protocol + request.Path.TrimStart('/').Remove(0, currentService.Prefix.Length).TrimStart('/');
                            queryString = request.QueryString.ToString();
                        }
                    }
                }

                // Replace any presets in the querystring with the actual value.
                queryString = this.ReplacePresetsInQueryString(queryString);

                HttpContextWrapper httpContextBase = new HttpContextWrapper(context);

                // Execute the handler which can change the querystring 
                //  LEGACY:
#pragma warning disable 618
                queryString = this.CheckQuerystringHandler(context, queryString, request.Unvalidated.RawUrl);
#pragma warning restore 618

                // NEW WAY:
                ValidatingRequestEventArgs validatingArgs = new ValidatingRequestEventArgs(httpContextBase, queryString);
                this.OnValidatingRequest(validatingArgs);

                // If the validation has failed based on events, return
                if (validatingArgs.Cancel)
                {
                    ImageProcessorBootstrapper.Instance.Logger.Log<ImageProcessingModule>("Image processing has been cancelled by an event");
                    return;
                }

                // Re-assign based on event handlers
                queryString = validatingArgs.QueryString;

                // Break out if we don't meet critera.
                bool interceptAll = interceptAllRequests != null && interceptAllRequests.Value;
                if (string.IsNullOrWhiteSpace(requestPath) || (!interceptAll && string.IsNullOrWhiteSpace(queryString)))
                {
                    return;
                }

                string parts = !string.IsNullOrWhiteSpace(urlParameters) ? "?" + urlParameters : string.Empty;
                string fullPath = string.Format("{0}{1}?{2}", requestPath, parts, queryString);
                object resourcePath;

                // More legacy support code.
                if (hasMultiParams)
                {
                    resourcePath = string.IsNullOrWhiteSpace(urlParameters)
                        ? new Uri(requestPath, UriKind.RelativeOrAbsolute)
                        : new Uri(requestPath + "?" + urlParameters, UriKind.RelativeOrAbsolute);
                }
                else
                {
                    resourcePath = requestPath;
                }

                // Check whether the path is valid for other requests.
                // We've already checked the unprefixed requests in GetImageServiceForRequest().
                if (!string.IsNullOrWhiteSpace(currentService.Prefix) && !currentService.IsValidRequest(resourcePath.ToString()))
                {
                    return;
                }

                string combined = requestPath + fullPath;

                using (await Locker.LockAsync(combined))
                {
                    // Create a new cache to help process and cache the request.
                    this.imageCache = (IImageCache)ImageProcessorConfiguration.Instance
                        .ImageCache.GetInstance(requestPath, fullPath, queryString);

                    // Is the file new or updated?
                    bool isNewOrUpdated = await this.imageCache.IsNewOrUpdatedAsync();
                    string cachedPath = this.imageCache.CachedPath;

                    // Only process if the file has been updated.
                    if (isNewOrUpdated)
                    {
                        byte[] imageBuffer = null;
                        string mimeType;

                        try
                        {
                            imageBuffer = await currentService.GetImage(resourcePath);
                        }
                        catch (HttpException ex)
                        {
                            // We want 404's to be handled by IIS so that other handlers/modules can still run.
                            if (ex.GetHttpCode() == (int)HttpStatusCode.NotFound)
                            {
                                ImageProcessorBootstrapper.Instance.Logger.Log<ImageProcessingModule>(ex.Message);
                                return;
                            }
                        }

                        if (imageBuffer == null)
                        {
                            return;
                        }

                        using (MemoryStream inStream = new MemoryStream(imageBuffer))
                        {
                            // Process the Image
                            MemoryStream outStream = new MemoryStream();

                            if (!string.IsNullOrWhiteSpace(queryString))
                            {
                                // Animation is not a processor but can be a specific request so we should allow it.
                                bool processAnimation;
                                AnimationProcessMode mode = this.ParseAnimationMode(queryString, out processAnimation);

                                // Attempt to match querystring and processors.
                                IWebGraphicsProcessor[] processors = ImageFactoryExtensions.GetMatchingProcessors(queryString);
                                if (processors.Any() || processAnimation)
                                {
                                    // Process the image.
                                    bool exif = preserveExifMetaData != null && preserveExifMetaData.Value;
                                    bool gamma = fixGamma != null && fixGamma.Value;
                                  
                                    using (ImageFactory imageFactory = new ImageFactory(exif, gamma) { AnimationProcessMode = mode })
                                    {
                                        imageFactory.Load(inStream).AutoProcess(processors).Save(outStream);
                                        mimeType = imageFactory.CurrentImageFormat.MimeType;
                                    }
                                }
                                else if (this.ParseCacheBuster(queryString))
                                {
                                    // We're cachebustng. Allow the value to be cached
                                    await inStream.CopyToAsync(outStream);
                                    mimeType = FormatUtilities.GetFormat(outStream).MimeType;
                                }
                                else
                                {
                                    // No match? Someone is either attacking the server or hasn't read the instructions. 
                                    // Either way throw an exception to prevent caching.
                                    string message = string.Format(
                                            "The request {0} could not be understood by the server due to malformed syntax.",
                                            request.Unvalidated.RawUrl);
                                    ImageProcessorBootstrapper.Instance.Logger.Log<ImageProcessingModule>(message);
                                    throw new HttpException((int)HttpStatusCode.BadRequest, message);
                                }
                            }
                            else
                            {
                                // We're capturing all requests.
                                await inStream.CopyToAsync(outStream);
                                mimeType = FormatUtilities.GetFormat(outStream).MimeType;
                            }

                            // Fire the post processing event.
                            EventHandler<PostProcessingEventArgs> handler = OnPostProcessing;
                            if (handler != null)
                            {
                                string extension = Path.GetExtension(cachedPath);
                                PostProcessingEventArgs args = new PostProcessingEventArgs
                                {
                                    Context = context,
                                    ImageStream = outStream,
                                    ImageExtension = extension
                                };

                                handler(this, args);
                                outStream = args.ImageStream;
                            }

                            // Add to the cache.
                            await this.imageCache.AddImageToCacheAsync(outStream, mimeType);

                            // Cleanup
                            outStream.Dispose();
                        }

                        // Store the response type and cache dependency in the context for later retrieval.
                        context.Items[CachedResponseTypeKey] = mimeType;
                        bool isFileCached = new Uri(cachedPath).IsFile;

                        if (isFileLocal)
                        {
                            if (isFileCached)
                            {
                                // Some services might only provide filename so we can't monitor for the browser.
                                context.Items[CachedResponseFileDependency] = Path.GetFileName(requestPath) == requestPath
                                    ? new[] { cachedPath }
                                    : new[] { requestPath, cachedPath };
                            }
                            else
                            {
                                context.Items[CachedResponseFileDependency] = Path.GetFileName(requestPath) == requestPath
                                    ? null
                                    : new[] { requestPath };
                            }
                        }
                        else if (isFileCached)
                        {
                            context.Items[CachedResponseFileDependency] = new[] { cachedPath };
                        }
                    }

                    // The cached file is valid so just rewrite the path.
                    this.imageCache.RewritePath(context);

                    // Redirect if not a locally store file.
                    if (!new Uri(cachedPath).IsFile)
                    {
                        context.ApplicationInstance.CompleteRequest();
                    }
                }
            }
        }
 /// <summary>
 /// Raises the ValidatingRequest event
 /// </summary>
 /// <param name="args">The <see cref="ValidatingRequestEventArgs"/></param>
 private void OnValidatingRequest(ValidatingRequestEventArgs args)
 {
     ValidatingRequest?.Invoke(this, args);
 }
        /// <summary>
        /// Processes the image.
        /// </summary>
        /// <param name="context">
        /// the <see cref="T:System.Web.HttpContext">HttpContext</see> object that provides
        /// references to the intrinsic server objects
        /// </param>
        /// <returns>
        /// The <see cref="T:System.Threading.Tasks.Task"/>.
        /// </returns>
        private async Task ProcessImageAsync(HttpContext context)
        {
            HttpRequest request = context.Request;
            string rawUrl = request.Unvalidated.RawUrl;

            // Should we ignore this request?
            if (string.IsNullOrWhiteSpace(rawUrl) || rawUrl.ToUpperInvariant().Contains("IPIGNORE=TRUE"))
            {
                return;
            }

            // Sometimes the request is url encoded so we have to decode.
            // See https://github.com/JimBobSquarePants/ImageProcessor/issues/478
            // This causes a bit of a nightmare as the incoming request is corrupted and cannot be used for splitting 
            // out each url part. This becomes a manual job.
            string url = this.DecodeUrlString(rawUrl);
            string applicationPath = request.ApplicationPath;

            IImageService currentService = this.GetImageServiceForRequest(url, applicationPath);

            if (currentService != null)
            {
                // Remove any service identifier prefixes from the url.
                string prefix = currentService.Prefix;
                if (!string.IsNullOrWhiteSpace(prefix))
                {
                    url = url.Split(new[] { prefix }, StringSplitOptions.None)[1].TrimStart("?");
                }

                // Identify each part of the incoming request.
                int queryCount = url.Count(f => f == '?');
                bool hasParams = queryCount > 0;
                bool hasMultiParams = queryCount > 1;
                string[] splitPath = url.Split('?');

                // Ensure we include any relevent querystring parameters into our request path for third party requests.
                string requestPath = hasMultiParams ? string.Join("?", splitPath.Take(splitPath.Length - 1)) : splitPath[0];
                string queryString = hasParams ? splitPath[splitPath.Length - 1] : string.Empty;

                // Map the request path if file local.
                bool isFileLocal = currentService.IsFileLocalService;
                if (currentService.IsFileLocalService)
                {
                    requestPath = HostingEnvironment.MapPath(requestPath);
                }

                // Parse any protocol values from settings if no protocol is present.
                if (currentService.Settings.ContainsKey("Protocol") && (ProtocolRegex.Matches(url).Count == 0 || ProtocolRegex.Matches(url)[0].Index > 0))
                {
                    // ReSharper disable once PossibleNullReferenceException
                    requestPath = currentService.Settings["Protocol"] + "://" + requestPath.TrimStart('/');
                }

                // Replace any presets in the querystring with the actual value.
                queryString = this.ReplacePresetsInQueryString(queryString);

                HttpContextWrapper httpContextBase = new HttpContextWrapper(context);

                // Execute the handler which can change the querystring 
                //  LEGACY:
#pragma warning disable 618
                queryString = this.CheckQuerystringHandler(context, queryString, rawUrl);
#pragma warning restore 618

                // NEW WAY:
                ValidatingRequestEventArgs validatingArgs = new ValidatingRequestEventArgs(httpContextBase, queryString);
                this.OnValidatingRequest(validatingArgs);

                // If the validation has failed based on events, return
                if (validatingArgs.Cancel)
                {
                    ImageProcessorBootstrapper.Instance.Logger.Log<ImageProcessingModule>("Image processing has been cancelled by an event");
                    return;
                }

                // Re-assign based on event handlers
                queryString = validatingArgs.QueryString;

                // Break out if we don't meet critera.
                bool interceptAll = interceptAllRequests != null && interceptAllRequests.Value;
                if (string.IsNullOrWhiteSpace(requestPath) || (!interceptAll && string.IsNullOrWhiteSpace(queryString)))
                {
                    return;
                }

                // Check whether the path is valid for other requests.
                // We've already checked the unprefixed requests in GetImageServiceForRequest().
                if (!string.IsNullOrWhiteSpace(prefix) && !currentService.IsValidRequest(requestPath))
                {
                    return;
                }

                using (await Locker.LockAsync(rawUrl))
                {
                    // Create a new cache to help process and cache the request.
                    this.imageCache = (IImageCache)ImageProcessorConfiguration.Instance
                        .ImageCache.GetInstance(requestPath, url, queryString);

                    // Is the file new or updated?
                    bool isNewOrUpdated = await this.imageCache.IsNewOrUpdatedAsync();
                    string cachedPath = this.imageCache.CachedPath;

                    // Only process if the file has been updated.
                    if (isNewOrUpdated)
                    {
                        byte[] imageBuffer = null;
                        string mimeType;

                        try
                        {
                            imageBuffer = await currentService.GetImage(requestPath);
                        }
                        catch (HttpException ex)
                        {
                            // We want 404's to be handled by IIS so that other handlers/modules can still run.
                            if (ex.GetHttpCode() == (int)HttpStatusCode.NotFound)
                            {
                                ImageProcessorBootstrapper.Instance.Logger.Log<ImageProcessingModule>(ex.Message);
                                return;
                            }
                        }

                        if (imageBuffer == null)
                        {
                            return;
                        }

                        using (MemoryStream inStream = new MemoryStream(imageBuffer))
                        {
                            // Process the Image
                            MemoryStream outStream = new MemoryStream();

                            if (!string.IsNullOrWhiteSpace(queryString))
                            {
                                // Animation is not a processor but can be a specific request so we should allow it.
                                bool processAnimation;
                                AnimationProcessMode mode = this.ParseAnimationMode(queryString, out processAnimation);

                                // Attempt to match querystring and processors.
                                IWebGraphicsProcessor[] processors = ImageFactoryExtensions.GetMatchingProcessors(queryString);
                                if (processors.Any() || processAnimation)
                                {
                                    // Process the image.
                                    bool exif = preserveExifMetaData != null && preserveExifMetaData.Value;
                                    bool gamma = fixGamma != null && fixGamma.Value;

                                    using (ImageFactory imageFactory = new ImageFactory(exif, gamma) { AnimationProcessMode = mode })
                                    {
                                        imageFactory.Load(inStream).AutoProcess(processors).Save(outStream);
                                        mimeType = imageFactory.CurrentImageFormat.MimeType;
                                    }
                                }
                                else if (this.ParseCacheBuster(queryString))
                                {
                                    // We're cachebustng. Allow the value to be cached
                                    await inStream.CopyToAsync(outStream);
                                    mimeType = FormatUtilities.GetFormat(outStream).MimeType;
                                }
                                else
                                {
                                    // No match? Someone is either attacking the server or hasn't read the instructions. 
                                    // Either way throw an exception to prevent caching.
                                    string message = $"The request {request.Unvalidated.RawUrl} could not be understood by the server due to malformed syntax.";
                                    ImageProcessorBootstrapper.Instance.Logger.Log<ImageProcessingModule>(message);
                                    throw new HttpException((int)HttpStatusCode.BadRequest, message);
                                }
                            }
                            else
                            {
                                // We're capturing all requests.
                                await inStream.CopyToAsync(outStream);
                                mimeType = FormatUtilities.GetFormat(outStream).MimeType;
                            }

                            // Fire the post processing event.
                            EventHandler<PostProcessingEventArgs> handler = OnPostProcessing;
                            if (handler != null)
                            {
                                string extension = Path.GetExtension(cachedPath);
                                PostProcessingEventArgs args = new PostProcessingEventArgs
                                {
                                    Context = context,
                                    ImageStream = outStream,
                                    ImageExtension = extension
                                };

                                handler(this, args);
                                outStream = args.ImageStream;
                            }

                            // Add to the cache.
                            await this.imageCache.AddImageToCacheAsync(outStream, mimeType);

                            // Cleanup
                            outStream.Dispose();
                        }

                        // Store the response type and cache dependency in the context for later retrieval.
                        context.Items[CachedResponseTypeKey] = mimeType;
                        bool isFileCached = new Uri(cachedPath).IsFile;

                        if (isFileLocal)
                        {
                            if (isFileCached)
                            {
                                // Some services might only provide filename so we can't monitor for the browser.
                                context.Items[CachedResponseFileDependency] = Path.GetFileName(requestPath) == requestPath
                                    ? new[] { cachedPath }
                                    : new[] { requestPath, cachedPath };
                            }
                            else
                            {
                                context.Items[CachedResponseFileDependency] = Path.GetFileName(requestPath) == requestPath
                                    ? null
                                    : new[] { requestPath };
                            }
                        }
                        else if (isFileCached)
                        {
                            context.Items[CachedResponseFileDependency] = new[] { cachedPath };
                        }
                    }

                    // The cached file is valid so just rewrite the path.
                    this.imageCache.RewritePath(context);

                    // Redirect if not a locally store file.
                    if (!new Uri(cachedPath).IsFile)
                    {
                        context.ApplicationInstance.CompleteRequest();
                    }
                }
            }
        }