/// <inheritdoc/>
        public async Task <Dictionary <string, string> > GetExtensionLabelsAsync(ApplicationWrapper application, ServiceWrapper service, CancellationToken cancellationToken)
        {
            _ = application ?? throw new ArgumentNullException(nameof(application));
            _ = service ?? throw new ArgumentNullException(nameof(service));
            _ = application.ApplicationTypeName ?? throw new ArgumentNullException($"{nameof(application)}.{nameof(application.ApplicationTypeName)}");
            _ = application.ApplicationTypeVersion ?? throw new ArgumentNullException($"{nameof(application)}.{nameof(application.ApplicationTypeVersion)}");
            _ = service.ServiceTypeName ?? throw new ArgumentNullException($"{nameof(service)}.{nameof(service.ServiceTypeName)}");
            _ = service.ServiceName ?? throw new ArgumentNullException($"{nameof(service)}.{nameof(service.ServiceName)}");

            string serviceManifestName;

            try
            {
                serviceManifestName = await _serviceFabricCaller.GetServiceManifestName(application.ApplicationTypeName, application.ApplicationTypeVersion, service.ServiceTypeName, cancellationToken);
            }
            catch (OperationCanceledException) when(cancellationToken.IsCancellationRequested)
            {
                throw;
            }
            catch (Exception ex) // TODO: davidni: not fatal?
            {
                throw new ServiceFabricIntegrationException($"Failed to get service manifest name for service type {service.ServiceTypeName} of application type {application.ApplicationTypeName} {application.ApplicationTypeVersion} from Service Fabric: {ex}.");
            }

            if (serviceManifestName == null)
            {
                throw new ServiceFabricIntegrationException($"No service manifest name was found for service type {service.ServiceTypeName} of application type {application.ApplicationTypeName} {application.ApplicationTypeVersion}.");
            }

            string rawServiceManifest;

            try
            {
                rawServiceManifest = await _serviceFabricCaller.GetServiceManifestAsync(application.ApplicationTypeName, application.ApplicationTypeVersion, serviceManifestName, cancellationToken);
            }
            catch (OperationCanceledException) when(cancellationToken.IsCancellationRequested)
            {
                throw;
            }
            catch (Exception ex) // TODO: davidni: not fatal?
            {
                throw new ServiceFabricIntegrationException($"Failed to get service manifest {serviceManifestName} of service type {service.ServiceTypeName} of application type {application.ApplicationTypeName} {application.ApplicationTypeVersion} from Service Fabric: {ex}.");
            }

            if (rawServiceManifest == null)
            {
                throw new ServiceFabricIntegrationException($"No service manifest named '{serviceManifestName}' was found for service type {service.ServiceTypeName} of application type {application.ApplicationTypeName} {application.ApplicationTypeVersion}.");
            }

            // TODO: gathering labels from multiple servicetypes within the same service would result in multiple
            // calls to the SF client and multiple XML parses. We should consider creating an instance of this class
            // per application type to reuse that data. Since this is uncommon, for now we follow the na�ve implementation.
            var result = ExtractLabels(rawServiceManifest, service.ServiceTypeName);

            ApplyAppParamReplacements(result, application, service);

            if (result.GetValueOrDefault("YARP.EnableDynamicOverrides", null)?.ToLower() == "true")
            {
                // Override with properties
                IDictionary <string, string> properties;
                try
                {
                    properties = await _serviceFabricCaller.EnumeratePropertiesAsync(service.ServiceName, cancellationToken);
                }
                catch (OperationCanceledException) when(cancellationToken.IsCancellationRequested)
                {
                    throw;
                }
                catch (Exception ex)
                {
                    throw new ServiceFabricIntegrationException($"Failed to get properties for {service.ServiceName}.", ex);
                }

                OverrideLabels(ref result, properties);
            }

            return(result);
        }
        /// <summary>
        /// The Service Manifest can specify a label with value <c>[AppParamName]</c>, in which case we replace it
        /// with the value of an application parameter with the given name <c>AppParamName</c>.
        /// Application parameter names are case insensitive in Service Fabric.
        /// If no such app param exists, we replace with empty string.
        /// </summary>
        private void ApplyAppParamReplacements(Dictionary <string, string> labels, ApplicationWrapper app, ServiceWrapper service)
        {
            var replacements = new List <KeyValuePair <string, string> >();

            foreach (var label in labels)
            {
                var value = label.Value;
                if (value.Length > 2 && value[0] == '[' && value[value.Length - 1] == ']')
                {
                    var appParamName = value.Substring(1, value.Length - 2);
                    if (app.ApplicationParameters == null ||
                        !app.ApplicationParameters.TryGetValue(appParamName, out var appParamValue))
                    {
                        // TODO: This should trigger a Warning or Error health report on the faulty service.
                        // This is not critical because if the absence of the setting leads to invalid configs, we *do* already report error
                        // (for example, if a route's rule were missing).
                        Log.InvalidApplicationParameter(_logger, app.ApplicationName, app.ApplicationTypeName, app.ApplicationTypeVersion, service.ServiceName, label.Key, appParamName);
                        appParamValue = string.Empty;
                    }

                    replacements.Add(KeyValuePair.Create(label.Key, appParamValue));
                }
            }

            foreach (var replacement in replacements)
            {
                labels[replacement.Key] = replacement.Value;
            }
        }