diff --git a/src/Observability/Runtime/Builder.cs b/src/Observability/Runtime/Builder.cs
index c237520d..f8985619 100644
--- a/src/Observability/Runtime/Builder.cs
+++ b/src/Observability/Runtime/Builder.cs
@@ -98,6 +98,7 @@ private void EnsureBuilt()
tracerProviderBuilder.Build();
}
+ EnvironmentUtils.Initialize(configuration: this.Configuration);
_isBuilt = true;
}
diff --git a/src/Observability/Runtime/Common/Agent365EndpointDiscovery.cs b/src/Observability/Runtime/Common/Agent365EndpointDiscovery.cs
new file mode 100644
index 00000000..2d4704ef
--- /dev/null
+++ b/src/Observability/Runtime/Common/Agent365EndpointDiscovery.cs
@@ -0,0 +1,41 @@
+// ------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// ------------------------------------------------------------------------------
+
+using System;
+
+namespace Microsoft.Agents.A365.Observability.Runtime.Common
+{
+ ///
+ /// Provides discovery for Agent365 endpoints.
+ ///
+ public sealed class Agent365EndpointDiscovery
+ {
+ private readonly string clusterCategory;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The cluster category.
+ public Agent365EndpointDiscovery(string clusterCategory)
+ {
+ this.clusterCategory = clusterCategory ?? "production";
+ }
+
+ ///
+ /// Gets the base host for the specified cluster category.
+ ///
+ public string GetHost()
+ {
+ switch (this.clusterCategory?.ToLowerInvariant())
+ {
+ case "firstrelease":
+ case "production":
+ case "prod":
+ return "agent365.svc.cloud.microsoft";
+ default:
+ throw new ArgumentException($"Invalid ClusterCategory value: {clusterCategory}");
+ }
+ }
+ }
+}
diff --git a/src/Observability/Runtime/Common/EnvironmentUtils.cs b/src/Observability/Runtime/Common/EnvironmentUtils.cs
index fb3f7f31..fab0bbfc 100644
--- a/src/Observability/Runtime/Common/EnvironmentUtils.cs
+++ b/src/Observability/Runtime/Common/EnvironmentUtils.cs
@@ -2,6 +2,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------------------------
+using Microsoft.Extensions.Configuration;
using System;
namespace Microsoft.Agents.A365.Observability.Runtime.Common
@@ -14,6 +15,9 @@ public class EnvironmentUtils
private const string ProdObservabilityScope = "https://api.powerplatform.com/.default";
private const string ProdObservabilityClusterCategory = "prod";
private const string DevelopmentEnvironmentName = "development";
+ private const string Agent365EndpointProdObservabilityScope = "api://9b975845-388f-4429-889e-eab1ef63949c/.default";
+ private static bool _initialized;
+ private static bool _customDomainEnabled;
///
/// Returns the scope for authenticating to the observability service based on the current environment.
@@ -21,19 +25,7 @@ public class EnvironmentUtils
/// The authentication scope.
public static string[] GetObservabilityAuthenticationScope()
{
- return new[] { ProdObservabilityScope };
- }
-
- ///
- /// [Deprecated] Returns the scope for authenticating to the observability service based on the cluster category.
- ///
- /// Cluster category (deprecated, defaults to production).
- /// The authentication scope.
- [Obsolete("Cluster category argument is deprecated and will be removed in future versions. Defaults to production.")]
- public static string[] GetObservabilityAuthenticationScope(string clusterCategory = ProdObservabilityClusterCategory)
- {
- // clusterCategory is ignored; always returns production scope
- return new[] { ProdObservabilityScope };
+ return IsCustomDomainEnabled() ? new[] { Agent365EndpointProdObservabilityScope } : new[] { ProdObservabilityScope };
}
///
@@ -66,6 +58,40 @@ public static bool IsDevelopmentEnvironment()
return string.Equals(environment, DevelopmentEnvironmentName, StringComparison.OrdinalIgnoreCase);
}
+ ///
+ /// Initializes the cached configuration values for environment utilities. Should be called once at application startup.
+ ///
+ /// The configuration instance.
+ /// When true, re-initializes even if already initialized.
+ public static void Initialize(IConfiguration? configuration, bool force = false)
+ {
+ if (configuration == null)
+ {
+ throw new ArgumentNullException(nameof(configuration));
+ }
+
+ if (_initialized && !force)
+ {
+ return;
+ }
+
+ string enabled = configuration["EnableAgent365CustomDomain"] ?? string.Empty;
+ _customDomainEnabled = enabled.Equals(bool.TrueString, StringComparison.OrdinalIgnoreCase);
+ _initialized = true;
+ }
+
+ ///
+ /// Returns true if the custom domain feature is enabled.
+ ///
+ public static bool IsCustomDomainEnabled()
+ {
+ if (!_initialized)
+ {
+ throw new InvalidOperationException("EnvironmentUtils is not initialized. Call Initialize() before using this method.");
+ }
+ return _customDomainEnabled;
+ }
+
///
/// Gets the current environment name.
///
diff --git a/src/Observability/Runtime/Tracing/Exporters/Agent365ExporterCore.cs b/src/Observability/Runtime/Tracing/Exporters/Agent365ExporterCore.cs
index bab1a631..3615b000 100644
--- a/src/Observability/Runtime/Tracing/Exporters/Agent365ExporterCore.cs
+++ b/src/Observability/Runtime/Tracing/Exporters/Agent365ExporterCore.cs
@@ -4,6 +4,7 @@
using Microsoft.Agents.A365.Observability.Runtime.Common;
using Microsoft.Agents.A365.Observability.Runtime.Tracing.Scopes;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using OpenTelemetry;
@@ -26,6 +27,7 @@ namespace Microsoft.Agents.A365.Observability.Runtime.Tracing.Exporters
public class Agent365ExporterCore
{
private const string CorrelationIdHeaderKey = "x-ms-correlation-id";
+ private const string TenantIdHeaderKey = "x-ms-tenant-id";
private readonly ExportFormatter _formatter;
private readonly ILogger _logger;
@@ -86,9 +88,11 @@ public Agent365ExporterCore(ExportFormatter formatter, ILoggerThe endpoint path string.
public string BuildEndpointPath(string agentId, bool useS2SEndpoint)
{
- return useS2SEndpoint
- ? $"/maven/agent365/service/agents/{agentId}/traces"
- : $"/maven/agent365/agents/{agentId}/traces";
+ return EnvironmentUtils.IsCustomDomainEnabled()
+ ? $"/agents/{agentId}/traces"
+ : useS2SEndpoint
+ ? $"/maven/agent365/service/agents/{agentId}/traces"
+ : $"/maven/agent365/agents/{agentId}/traces";
}
///
@@ -97,7 +101,7 @@ public string BuildEndpointPath(string agentId, bool useS2SEndpoint)
/// The base endpoint.
/// The endpoint path.
/// The full request URI string.
- public string BuildRequestUri(string endpoint, string endpointPath)
+ public static string BuildRequestUri(string endpoint, string endpointPath)
{
return $"https://{endpoint}{endpointPath}?api-version=1";
}
@@ -124,11 +128,11 @@ public async Task ExportBatchCoreAsync(
var json = _formatter.FormatMany(activities, resource);
using var content = new StringContent(json, Encoding.UTF8, "application/json");
- var ppapiDiscovery = new PowerPlatformApiDiscovery(options.ClusterCategory);
- var ppapiEndpoint = ppapiDiscovery.GetTenantIslandClusterEndpoint(tenantId);
-
- var endpointPath = BuildEndpointPath(agentId, options.UseS2SEndpoint);
- var requestUri = BuildRequestUri(ppapiEndpoint, endpointPath);
+ string endpointPath = this.BuildEndpointPath(agentId: agentId, useS2SEndpoint: options.UseS2SEndpoint);
+ var endpoint = EnvironmentUtils.IsCustomDomainEnabled()
+ ? new Agent365EndpointDiscovery(options.ClusterCategory).GetHost()
+ : new PowerPlatformApiDiscovery(options.ClusterCategory).GetTenantIslandClusterEndpoint(tenantId);
+ var requestUri = Agent365ExporterCore.BuildRequestUri(endpoint, endpointPath);
using var request = new HttpRequestMessage(HttpMethod.Post, requestUri)
{
@@ -149,6 +153,11 @@ public async Task ExportBatchCoreAsync(
if (!string.IsNullOrEmpty(token))
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
+ if (EnvironmentUtils.IsCustomDomainEnabled() && !string.IsNullOrEmpty(tenantId))
+ {
+ request.Headers.TryAddWithoutValidation(Agent365ExporterCore.TenantIdHeaderKey, tenantId);
+ }
+
HttpResponseMessage? resp = null;
try
{
diff --git a/src/Observability/Runtime/Tracing/Exporters/ObservabilityTracerProviderBuilderExtensions.cs b/src/Observability/Runtime/Tracing/Exporters/ObservabilityTracerProviderBuilderExtensions.cs
index ce981d1a..e8cbf23a 100644
--- a/src/Observability/Runtime/Tracing/Exporters/ObservabilityTracerProviderBuilderExtensions.cs
+++ b/src/Observability/Runtime/Tracing/Exporters/ObservabilityTracerProviderBuilderExtensions.cs
@@ -77,9 +77,11 @@ private static TracerProviderBuilder ConfigureInternal(IServiceProvider serviceP
var coreLogger = serviceProvider.GetService>() ?? loggerFactory.CreateLogger();
var formatterLogger = serviceProvider.GetService>() ?? loggerFactory.CreateLogger();
+ var configuration = serviceProvider.GetService();
+
// Create ExportFormatter and Agent365ExporterCore
- var exportFormatter = new ExportFormatter(formatterLogger);
- var exporterCore = new Agent365ExporterCore(exportFormatter, coreLogger);
+ var exportFormatter = new ExportFormatter(logger: formatterLogger);
+ var exporterCore = new Agent365ExporterCore(formatter: exportFormatter, logger: coreLogger);
switch (exporterType)
{
diff --git a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Common/Agent365EndpointDiscoveryTest.cs b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Common/Agent365EndpointDiscoveryTest.cs
new file mode 100644
index 00000000..9400c7a2
--- /dev/null
+++ b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Common/Agent365EndpointDiscoveryTest.cs
@@ -0,0 +1,46 @@
+// ------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// ------------------------------------------------------------------------------
+
+using Microsoft.Agents.A365.Observability.Runtime.Common;
+
+namespace Microsoft.Agents.A365.Observability.Runtime.Tests.Common;
+
+
+[TestClass]
+public class Agent365EndpointDiscoveryTests
+{
+ [TestMethod]
+ public void GetHost_Mapping_IsCorrect()
+ {
+ var expected = new Dictionary
+ {
+ ["firstrelease"] = "agent365.svc.cloud.microsoft",
+ ["production"] = "agent365.svc.cloud.microsoft",
+ ["prod"] = "agent365.svc.cloud.microsoft",
+ };
+
+ foreach (var kv in expected)
+ {
+ var disc = new Agent365EndpointDiscovery(kv.Key);
+ Assert.AreEqual(kv.Value, disc.GetHost());
+ }
+ }
+
+ [TestMethod]
+ public void GetHost_IsCaseInsensitive()
+ {
+ var disc1 = new Agent365EndpointDiscovery("PRODUCTION");
+ Assert.AreEqual("agent365.svc.cloud.microsoft", disc1.GetHost());
+
+ var disc2 = new Agent365EndpointDiscovery("PROD");
+ Assert.AreEqual("agent365.svc.cloud.microsoft", disc2.GetHost());
+ }
+
+ [TestMethod]
+ public void GetHost_ThrowsForUnknown()
+ {
+ var disc = new Agent365EndpointDiscovery("unknown-category");
+ Assert.ThrowsException(() => disc.GetHost());
+ }
+}
diff --git a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Common/EnvironmentUtilsTests.cs b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Common/EnvironmentUtilsTests.cs
new file mode 100644
index 00000000..88ef839f
--- /dev/null
+++ b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Common/EnvironmentUtilsTests.cs
@@ -0,0 +1,126 @@
+// ------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// ------------------------------------------------------------------------------
+
+using FluentAssertions;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Agents.A365.Observability.Runtime.Common;
+
+namespace Microsoft.Agents.A365.Observability.Runtime.Tests.Common
+{
+ [TestClass]
+ public class EnvironmentUtilsTests
+ {
+ private static IConfiguration BuildConfig(IDictionary? values = null)
+ {
+ var builder = new ConfigurationBuilder();
+ if (values != null)
+ {
+ builder.AddInMemoryCollection(values);
+ }
+ return builder.Build();
+ }
+
+ [TestMethod]
+ public void GetObservabilityAuthenticationScope_UsesAgent365Scope_WhenCustomDomainEnabled()
+ {
+ // Arrange
+ var configEnabled = BuildConfig(new Dictionary
+ {
+ { "EnableAgent365CustomDomain", "true" }
+ });
+
+ EnvironmentUtils.Initialize(configuration: configEnabled, force: true);
+
+ // Act
+ var scope = EnvironmentUtils.GetObservabilityAuthenticationScope();
+
+ // Assert
+ scope.Should().ContainSingle().Which.Should().Be("api://9b975845-388f-4429-889e-eab1ef63949c/.default");
+ }
+
+ [TestMethod]
+ public void GetObservabilityAuthenticationScope_UsesProdScope_WhenCustomDomainDisabled()
+ {
+ // Arrange
+ var configDisabled = BuildConfig(new Dictionary
+ {
+ { "EnableAgent365CustomDomain", "false" }
+ });
+
+ EnvironmentUtils.Initialize(configuration: configDisabled, force: true);
+
+ // Act
+ var scope = EnvironmentUtils.GetObservabilityAuthenticationScope();
+
+ // Assert
+ scope.Should().ContainSingle().Which.Should().Be("https://api.powerplatform.com/.default");
+ }
+
+ [TestMethod]
+ public void GetObservabilityAuthenticationScope_UsesProdScope_WhenCustomDomainSettingMissing()
+ {
+ // Arrange
+ var configMissing = BuildConfig();
+
+ EnvironmentUtils.Initialize(configuration: configMissing, force: true);
+
+ // Act
+ var scope = EnvironmentUtils.GetObservabilityAuthenticationScope();
+
+ // Assert
+ scope.Should().ContainSingle().Which.Should().Be("https://api.powerplatform.com/.default");
+ }
+
+ [TestMethod]
+ public void IsCustomDomainEnabled_ReturnsTrue_WhenConfigValueTrue()
+ {
+ // Arrange
+ var config = BuildConfig(new Dictionary
+ {
+ { "EnableAgent365CustomDomain", "true" }
+ });
+
+ EnvironmentUtils.Initialize(configuration: config, force: true);
+
+ // Act
+ var result = EnvironmentUtils.IsCustomDomainEnabled();
+
+ // Assert
+ result.Should().BeTrue();
+ }
+
+ [TestMethod]
+ public void IsCustomDomainEnabled_ReturnsFalse_WhenConfigValueFalse()
+ {
+ // Arrange
+ var config = BuildConfig(new Dictionary
+ {
+ { "EnableAgent365CustomDomain", "false" }
+ });
+
+ EnvironmentUtils.Initialize(configuration: config, force: true);
+
+ // Act
+ var result = EnvironmentUtils.IsCustomDomainEnabled();
+
+ // Assert
+ result.Should().BeFalse();
+ }
+
+ [TestMethod]
+ public void IsCustomDomainEnabled_ReturnsFalse_WhenConfigValueMissing()
+ {
+ // Arrange
+ var config = BuildConfig();
+
+ EnvironmentUtils.Initialize(configuration: config, force: true);
+
+ // Act
+ var result = EnvironmentUtils.IsCustomDomainEnabled();
+
+ // Assert
+ result.Should().BeFalse();
+ }
+ }
+}
diff --git a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Exporters/Agent365ExporterTests.cs b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Exporters/Agent365ExporterTests.cs
index 7eaf1a5d..96ddf829 100644
--- a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Exporters/Agent365ExporterTests.cs
+++ b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Exporters/Agent365ExporterTests.cs
@@ -1,4 +1,4 @@
-using FluentAssertions;
+using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Agents.A365.Observability.Runtime.Common;
using Microsoft.Agents.A365.Observability.Runtime.Tracing.Exporters;
@@ -7,6 +7,7 @@
using OpenTelemetry.Resources;
using System.Diagnostics;
using System.Reflection;
+using Microsoft.Extensions.Configuration;
namespace Microsoft.Agents.A365.Observability.Tests.Tracing.Exporters;
@@ -112,7 +113,17 @@ private static Agent365Exporter CreateExporter(Func? to
resource);
}
- [TestMethod]
+ private static void SetCustomDomain(bool enabled)
+ {
+ var _configuration = new ConfigurationBuilder()
+ .AddInMemoryCollection(new Dictionary
+ {
+ { "EnableAgent365CustomDomain", enabled ? bool.TrueString : bool.FalseString }
+ })
+ .Build();
+ EnvironmentUtils.Initialize(configuration: _configuration, force: true);
+ }
+
public void Constructor_NullLogger_Throws()
{
var options = new Agent365ExporterOptions
@@ -1014,4 +1025,145 @@ public void Export_StandardEndpoint_WithDifferentClusterCategories_ProcessesCorr
}
#endregion
+
+ #region Build Endpoint and URI Tests
+ [TestMethod]
+ public void BuildEndpointPath_CustomDomain_UsesAgentsRoot()
+ {
+ Agent365ExporterTests.SetCustomDomain(true);
+ var path = Agent365ExporterTests._agent365ExporterCore.BuildEndpointPath("agent-123", useS2SEndpoint: true);
+ path.Should().Be("/agents/agent-123/traces");
+ }
+
+ [TestMethod]
+ public void BuildEndpointPath_NonCustomDomain_UsesServicePathsDependingOnS2S()
+ {
+ Agent365ExporterTests.SetCustomDomain(false);
+ var s2s = Agent365ExporterTests._agent365ExporterCore.BuildEndpointPath("agent-123", useS2SEndpoint: true);
+ var standard = Agent365ExporterTests._agent365ExporterCore.BuildEndpointPath("agent-123", useS2SEndpoint: false);
+ s2s.Should().Be("/maven/agent365/service/agents/agent-123/traces");
+ standard.Should().Be("/maven/agent365/agents/agent-123/traces");
+ }
+
+ [TestMethod]
+ public void BuildRequestUri_ComposesCorrectly()
+ {
+ var uri = Agent365ExporterCore.BuildRequestUri("example.com", "/agents/agent-123/traces");
+ uri.Should().Be("https://example.com/agents/agent-123/traces?api-version=1");
+ }
+ #endregion
+
+ #region ExportBatchCoreAsync Request Uri and Headers Tests
+ [TestMethod]
+ public async Task ExportBatchCoreAsync_CustomDomain_UsesBaseHostAndAddsTenantHeader()
+ {
+ // Arrange
+ Agent365ExporterTests.SetCustomDomain(true);
+ var tenantId = "tenant-xyz";
+ var agentId = "agent-abc";
+ var resource = ResourceBuilder.CreateEmpty().AddService("unit-test-service", serviceVersion: "1.0.0").Build();
+ var options = new Agent365ExporterOptions
+ {
+ TokenResolver = (_, _) => Task.FromResult(null),
+ UseS2SEndpoint = true,
+ ClusterCategory = "prod"
+ };
+
+ // Create a fake activity with identity
+ using var activity = CreateActivity(tenantId: tenantId, agentId: agentId);
+ var groups = new List<(string TenantId, string AgentId, List Activities)>
+ {
+ (tenantId, agentId, new List { activity })
+ };
+
+ // Capture request details
+ string? capturedHost = null;
+ string? capturedPathAndQuery = null;
+ string? capturedTenantHeader = null;
+
+ var expectedHost = new Agent365EndpointDiscovery(options.ClusterCategory).GetHost();
+
+ Task sendAsync(HttpRequestMessage req)
+ {
+ capturedHost = req.RequestUri!.Host;
+ capturedPathAndQuery = req.RequestUri!.PathAndQuery;
+ req.Headers.TryGetValues("x-ms-tenant-id", out var vals);
+ capturedTenantHeader = vals?.FirstOrDefault();
+ var response = new HttpResponseMessage(System.Net.HttpStatusCode.InternalServerError);
+ response.Headers.Add("x-ms-correlation-id", Guid.NewGuid().ToString());
+ return Task.FromResult(response);
+ }
+
+ // Act
+ var result = await Agent365ExporterTests._agent365ExporterCore.ExportBatchCoreAsync(
+ groups,
+ resource,
+ options,
+ (a, t) => Task.FromResult(null),
+ sendAsync);
+
+ // Assert
+ result.Should().Be(ExportResult.Failure); // Expected to fail due to no real endpoint
+ capturedHost.Should().Be(expectedHost);
+ capturedPathAndQuery.Should().StartWith("/agents/" + agentId + "/traces");
+ capturedPathAndQuery.Should().EndWith("?api-version=1");
+ capturedTenantHeader.Should().Be(tenantId);
+ }
+
+ [TestMethod]
+ public async Task ExportBatchCoreAsync_NonCustomDomain_UsesPowerPlatformEndpoint()
+ {
+ // Arrange
+ Agent365ExporterTests.SetCustomDomain(false);
+ var tenantId = "tenant-xyz";
+ var agentId = "agent-abc";
+ var resource = ResourceBuilder.CreateEmpty().AddService("unit-test-service", serviceVersion: "1.0.0").Build();
+ var options = new Agent365ExporterOptions
+ {
+ TokenResolver = (_, _) => Task.FromResult(null),
+ UseS2SEndpoint = true,
+ ClusterCategory = "prod"
+ };
+
+ using var activity = CreateActivity(tenantId: tenantId, agentId: agentId);
+ var groups = new List<(string TenantId, string AgentId, List Activities)>
+ {
+ (tenantId, agentId, new List { activity })
+ };
+
+ string? capturedHost = null;
+ string? capturedPathAndQuery = null;
+ string? capturedTenantHeader = null;
+
+ var expectedHost = new PowerPlatformApiDiscovery(options.ClusterCategory).GetTenantIslandClusterEndpoint(tenantId);
+
+ Task sendAsync(HttpRequestMessage req)
+ {
+ capturedHost = req.RequestUri!.Host;
+ capturedPathAndQuery = req.RequestUri!.PathAndQuery;
+ if (req.Headers.TryGetValues("x-ms-tenant-id", out var vals))
+ {
+ capturedTenantHeader = vals.FirstOrDefault();
+ }
+ var response = new HttpResponseMessage(System.Net.HttpStatusCode.InternalServerError);
+ response.Headers.Add("x-ms-correlation-id", Guid.NewGuid().ToString());
+ return Task.FromResult(response);
+ }
+
+ // Act
+ var result = await Agent365ExporterTests._agent365ExporterCore.ExportBatchCoreAsync(
+ groups,
+ resource,
+ options,
+ (a, t) => Task.FromResult(null),
+ sendAsync);
+
+ // Assert
+ result.Should().Be(ExportResult.Failure); // Expected to fail due to no real endpoint
+ capturedHost.Should().Be(expectedHost);
+ capturedPathAndQuery.Should().StartWith("/maven/agent365/service/agents/" + agentId + "/traces");
+ capturedPathAndQuery.Should().EndWith("?api-version=1");
+ capturedTenantHeader.Should().BeNull();
+ }
+ #endregion
}
\ No newline at end of file