diff --git a/src/Aspire.Dashboard/Model/ConnectionStringParser.cs b/src/Aspire.Dashboard/Model/ConnectionStringParser.cs
new file mode 100644
index 00000000000..67c52d6c75b
--- /dev/null
+++ b/src/Aspire.Dashboard/Model/ConnectionStringParser.cs
@@ -0,0 +1,497 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Text.RegularExpressions;
+
+namespace Aspire.Dashboard.Model;
+
+/// 
+/// Provides utilities for parsing connection strings to extract host and port information.
+/// Supports various connection string formats including URIs, key-value pairs, and delimited lists.
+/// 
+internal static partial class ConnectionStringParser
+{
+    private static readonly Dictionary s_schemeDefaultPorts = new(StringComparer.OrdinalIgnoreCase)
+    {
+        ["http"] = 80,
+        ["https"] = 443,
+        ["ftp"] = 21,
+        ["ftps"] = 990,
+        ["ssh"] = 22,
+        ["telnet"] = 23,
+        ["smtp"] = 25,
+        ["dns"] = 53,
+        ["dhcp"] = 67,
+        ["tftp"] = 69,
+        ["pop3"] = 110,
+        ["ntp"] = 123,
+        ["imap"] = 143,
+        ["snmp"] = 161,
+        ["ldap"] = 389,
+        ["smtps"] = 465,
+        ["ldaps"] = 636,
+        ["imaps"] = 993,
+        ["pop3s"] = 995,
+        ["mssql"] = 1433,
+        ["mysql"] = 3306,
+        ["postgresql"] = 5432,
+        ["postgres"] = 5432,
+        ["redis"] = 6379,
+        ["mongodb"] = 27017,
+        ["amqp"] = 5672,
+        ["amqps"] = 5671,
+        ["kafka"] = 9092
+    };
+
+    private static readonly string[] s_hostAliases = ["host", "server", "data source", "addr", "address", "endpoint", "contact points"];
+
+    private static readonly string[] s_knownProtocols = ["tcp", "udp", "ssl", "tls", "http", "https", "ftp", "ssh"];
+
+    /// 
+    /// Matches host:port or host,port patterns with optional IPv6 bracket notation.
+    /// Examples: "localhost:5432", "127.0.0.1,1433", "[::1]:6379"
+    /// 
+    [GeneratedRegex(@"(\[[^\]]+\]|[^,:;\s]+)[:|,](\d{1,5})", RegexOptions.Compiled)]
+    private static partial Regex HostPortRegex();
+
+    /// 
+    /// Matches JDBC URLs to extract host and optional port.
+    /// Examples: "jdbc:postgresql://localhost:5432/db", "jdbc:mysql://server/database"
+    /// 
+    [GeneratedRegex(@"^jdbc:[^:]+://([^:/\s]+)(?::(\d+))?(?:/.*)?", RegexOptions.IgnoreCase | RegexOptions.Compiled)]
+    private static partial Regex JdbcUrlRegex();
+
+    /// 
+    /// Attempts to extract a host and optional port from an arbitrary connection string.
+    /// Returns true if a host could be identified; otherwise false.
+    /// 
+    /// Supports the following connection string formats:
+    /// - URIs: "postgres://user:pass@host:5432/db", "redis://host:6379"
+    /// - Key-value pairs: "Host=localhost;Port=5432", "Server=tcp:host,1433"
+    /// - Delimited lists: "broker1:9092,broker2:9092" (returns first broker)
+    /// - Single hostnames: "localhost", "api.example.com"
+    /// 
+    /// The connection string to parse.
+    /// When this method returns true, contains the host part with surrounding brackets removed; otherwise, an empty string.
+    /// When this method returns true, contains the explicit port, scheme-derived default, or null when unavailable; otherwise, null.
+    /// true if a host was found; otherwise, false.
+    public static bool TryDetectHostAndPort(
+        string connectionString,
+        [NotNullWhen(true)] out string? host,
+        out int? port)
+    {
+        host = null;
+        port = null;
+
+        if (string.IsNullOrWhiteSpace(connectionString))
+        {
+            return false;
+        }
+
+        // Strategy 1: Parse as URI (including JDBC URLs)
+        // Examples: "postgres://host:5432/db", "jdbc:mysql://host/db"
+        if (TryParseAsUri(connectionString, out host, out port))
+        {
+            return true;
+        }
+
+        // Strategy 2: Parse as key-value pairs
+        // Examples: "Host=localhost;Port=5432", "Server=tcp:host,1433"
+        if (TryParseAsKeyValuePairs(connectionString, out host, out port))
+        {
+            return true;
+        }
+
+        // Strategy 3: Use regex heuristic for host:port patterns
+        // Examples: "localhost:5432", "127.0.0.1,1433", "[::1]:6379"
+        if (TryParseWithRegexHeuristic(connectionString, out host, out port))
+        {
+            return true;
+        }
+
+        // Strategy 4: Treat as single hostname (conservative approach)
+        // Examples: "localhost", "api.example.com" (but not file paths)
+        if (TryParseAsSingleHost(connectionString, out host, out port))
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+    /// 
+    /// Attempts to parse the connection string as a URI (including JDBC URLs).
+    /// 
+    /// The string to parse as a URI. Examples: "postgres://host:5432/db", "jdbc:mysql://host/db"
+    /// The extracted host name, or null if parsing failed.
+    /// The extracted port number, or null if no port was found.
+    /// True if a host was successfully extracted; otherwise false.
+    private static bool TryParseAsUri(string connectionString, [NotNullWhen(true)] out string? host, out int? port)
+    {
+        host = null;
+        port = null;
+
+        // Handle JDBC URLs specially since they're not recognized by Uri.TryCreate
+        // Example: "jdbc:postgresql://localhost:5432/database"
+        if (connectionString.StartsWith("jdbc:", StringComparison.OrdinalIgnoreCase))
+        {
+            return TryParseJdbcUrl(connectionString, out host, out port);
+        }
+
+        // Standard URI parsing for protocols like postgres://, redis://, etc.
+        if (Uri.TryCreate(connectionString, UriKind.Absolute, out var uri) && !string.IsNullOrEmpty(uri.Host))
+        {
+            host = TrimBrackets(uri.Host);
+            port = uri.Port != -1 ? uri.Port : DefaultPortFromScheme(uri.Scheme);
+            return true;
+        }
+
+        return false;
+    }
+
+    /// 
+    /// Attempts to parse key-value pair connection strings.
+    /// 
+    /// The connection string with key-value pairs. Examples: "Host=localhost;Port=5432", "Server=tcp:host,1433"
+    /// The extracted host name, or null if parsing failed.
+    /// The extracted port number, or null if no port was found.
+    /// True if a host was successfully extracted; otherwise false.
+    private static bool TryParseAsKeyValuePairs(string connectionString, [NotNullWhen(true)] out string? host, out int? port)
+    {
+        host = null;
+        port = null;
+
+        var keyValuePairs = SplitIntoDictionary(connectionString);
+        
+        foreach (var hostAlias in s_hostAliases)
+        {
+            if (keyValuePairs.TryGetValue(hostAlias, out var token))
+            {
+                // First, check if the token is a complete URL
+                // Example: "Endpoint=https://storage.azure.com"
+                if (TryParseAsUri(token, out var tokenHost, out var tokenPort))
+                {
+                    host = tokenHost;
+                    port = tokenPort;
+                    return true;
+                }
+                
+                // Handle special case of multiple contact points (should return false to be conservative)
+                // Example: "contact points=node1,node2,node3" should not be parsed
+                if (hostAlias.Equals("contact points", StringComparison.OrdinalIgnoreCase) && 
+                    token.Contains(',') && token.Split(',').Length > 1)
+                {
+                    return false;
+                }
+                
+                // Remove protocol prefixes like "tcp:", "udp:", etc.
+                // Example: "Server=tcp:localhost,1433" becomes "localhost,1433"
+                token = RemoveProtocolPrefix(token);
+                
+                if (token.Contains(',') || token.Contains(':'))
+                {
+                    // Handle host:port or host,port patterns
+                    // Examples: "localhost:5432", "127.0.0.1,1433", "[::1]:6379"
+                    if (TryParseHostPortToken(token, keyValuePairs, out host, out port))
+                    {
+                        return true;
+                    }
+                }
+                else if (!string.IsNullOrEmpty(token))
+                {
+                    // Single hostname without port
+                    // Example: "Host=localhost"
+                    host = TrimBrackets(token);
+                    port = PortFromKV(keyValuePairs);
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /// 
+    /// Uses regex heuristics to find host:port patterns in the connection string.
+    /// 
+    /// The connection string to search. Examples: "broker1:9092,broker2:9092", "localhost:5432"
+    /// The extracted host name, or null if parsing failed.
+    /// The extracted port number, or null if no port was found.
+    /// True if a host:port pattern was found; otherwise false.
+    private static bool TryParseWithRegexHeuristic(string connectionString, [NotNullWhen(true)] out string? host, out int? port)
+    {
+        host = null;
+        port = null;
+
+        var match = HostPortRegex().Match(connectionString);
+        if (match.Success)
+        {
+            var hostPart = match.Groups[1].Value;
+            var portPart = match.Groups[2].Value;
+            if (!string.IsNullOrEmpty(hostPart))
+            {
+                host = TrimBrackets(hostPart);
+                port = ParseIntSafe(portPart);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /// 
+    /// Attempts to treat the entire connection string as a single hostname (conservative approach).
+    /// 
+    /// The string to evaluate as a hostname. Examples: "localhost", "api.example.com"
+    /// The hostname if it looks valid, or null if it appears to be a file path or other non-hostname.
+    /// Always null for single hostname parsing.
+    /// True if the string looks like a valid hostname; otherwise false.
+    private static bool TryParseAsSingleHost(string connectionString, [NotNullWhen(true)] out string? host, out int? port)
+    {
+        host = null;
+        port = null;
+
+        if (LooksLikeHost(connectionString))
+        {
+            host = TrimBrackets(connectionString);
+            port = null;
+            return true;
+        }
+
+        return false;
+    }
+
+    /// 
+    /// Parses a host:port or host,port token, with special handling for IPv6 addresses.
+    /// 
+    /// The token to parse. Examples: "localhost:5432", "[::1]:6379", "host,1433"
+    /// Additional key-value pairs that might contain a separate port value.
+    /// The extracted host name, or null if parsing failed.
+    /// The extracted port number, or null if no port was found.
+    /// True if parsing succeeded; otherwise false.
+    private static bool TryParseHostPortToken(string token, Dictionary keyValuePairs, [NotNullWhen(true)] out string? host, out int? port)
+    {
+        host = null;
+        port = null;
+
+        // Special handling for IPv6 addresses in brackets
+        // Example: "[::1]:6379" or "[::1],6379"
+        if (token.StartsWith('[') && token.Contains(']'))
+        {
+            var bracketEnd = token.IndexOf(']');
+            if (bracketEnd > 0)
+            {
+                host = TrimBrackets(token[..(bracketEnd + 1)]);
+                // Look for port after the bracket (could be colon or comma separated)
+                var afterBracket = token[(bracketEnd + 1)..];
+                if ((afterBracket.StartsWith(':') || afterBracket.StartsWith(',')) && afterBracket.Length > 1)
+                {
+                    port = ParseIntSafe(afterBracket[1..]) ?? PortFromKV(keyValuePairs);
+                }
+                else
+                {
+                    port = PortFromKV(keyValuePairs);
+                }
+                return true;
+            }
+        }
+
+        // Regular host:port or host,port parsing
+        var (hostPart, portPart) = SplitOnLast(token);
+        if (!string.IsNullOrEmpty(hostPart))
+        {
+            host = TrimBrackets(hostPart);
+            port = ParseIntSafe(portPart) ?? PortFromKV(keyValuePairs);
+            return true;
+        }
+
+        return false;
+    }
+
+    /// 
+    /// Parses JDBC URLs which have the format: jdbc:subprotocol://host:port/database
+    /// 
+    /// The JDBC URL to parse. Examples: "jdbc:postgresql://localhost:5432/db", "jdbc:mysql://server/database"
+    /// The extracted host name, or null if parsing failed.
+    /// The extracted port number, or null if no port was specified.
+    /// True if the JDBC URL was successfully parsed; otherwise false.
+    private static bool TryParseJdbcUrl(string jdbcUrl, [NotNullWhen(true)] out string? host, out int? port)
+    {
+        host = null;
+        port = null;
+
+        var match = JdbcUrlRegex().Match(jdbcUrl);
+        if (match.Success)
+        {
+            host = match.Groups[1].Value;
+            if (match.Groups[2].Success && int.TryParse(match.Groups[2].Value, out var portValue))
+            {
+                port = portValue;
+            }
+            return true;
+        }
+
+        return false;
+    }
+
+    /// 
+    /// Removes square brackets from the beginning and end of a string.
+    /// 
+    /// The string to trim. Example: "[::1]" becomes "::1"
+    /// The string with brackets removed.
+    private static string TrimBrackets(string s) => s.Trim('[', ']');
+
+    /// 
+    /// Removes known protocol prefixes from connection string values.
+    /// 
+    /// The value to clean. Examples: "tcp:localhost" becomes "localhost", "ssl:host:443" becomes "host:443"
+    /// The value with protocol prefix removed, or the original value if no known prefix is found.
+    private static string RemoveProtocolPrefix(string value)
+    {
+        // Remove common protocol prefixes like "tcp:", "udp:", "ssl:", etc.
+        if (string.IsNullOrEmpty(value))
+        {
+            return value;
+        }
+
+        var colonIndex = value.IndexOf(':');
+        if (colonIndex > 0 && colonIndex < value.Length - 1)
+        {
+            var prefix = value[..colonIndex].ToLowerInvariant();
+            // Only remove known protocol prefixes, not arbitrary single letters
+            if (s_knownProtocols.Contains(prefix))
+            {
+                return value[(colonIndex + 1)..];
+            }
+        }
+
+        return value;
+    }
+
+    /// 
+    /// Gets the default port number for a given URI scheme.
+    /// 
+    /// The URI scheme. Examples: "postgres", "redis", "https"
+    /// The default port number for the scheme, or null if no default is known.
+    private static int? DefaultPortFromScheme(string? scheme)
+    {
+        if (string.IsNullOrEmpty(scheme))
+        {
+            return null;
+        }
+
+        return s_schemeDefaultPorts.TryGetValue(scheme, out var port) ? port : null;
+    }
+
+    /// 
+    /// Extracts a port value from key-value pairs using the "port" key.
+    /// 
+    /// The dictionary of key-value pairs to search.
+    /// The port number if found and valid, or null otherwise.
+    private static int? PortFromKV(Dictionary keyValuePairs)
+    {
+        return keyValuePairs.TryGetValue("port", out var portValue) ? ParseIntSafe(portValue) : null;
+    }
+
+    /// 
+    /// Safely parses a string as an integer port number (0-65535).
+    /// 
+    /// The string to parse. Examples: "5432", "443", "invalid"
+    /// The parsed port number if valid, or null if parsing failed or the number is out of range.
+    private static int? ParseIntSafe(string? s)
+    {
+        if (string.IsNullOrEmpty(s))
+        {
+            return null;
+        }
+
+        if (int.TryParse(s, NumberStyles.None, CultureInfo.InvariantCulture, out var value) &&
+            value >= 0 && value <= 65535)
+        {
+            return value;
+        }
+
+        return null;
+    }
+
+    /// 
+    /// Splits a connection string into key-value pairs using semicolon or whitespace delimiters.
+    /// 
+    /// The connection string to split. Examples: "Host=localhost;Port=5432", "server=host port=1433"
+    /// A dictionary of key-value pairs with case-insensitive keys.
+    private static Dictionary SplitIntoDictionary(string connectionString)
+    {
+        var result = new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+        // Split by semicolon first, then by whitespace if no semicolons found
+        var parts = connectionString.Contains(';')
+            ? connectionString.Split(';', StringSplitOptions.RemoveEmptyEntries)
+            : connectionString.Split([' ', '\t', '\n', '\r'], StringSplitOptions.RemoveEmptyEntries);
+
+        foreach (var part in parts)
+        {
+            var trimmedPart = part.Trim();
+            var equalIndex = trimmedPart.IndexOf('=');
+            if (equalIndex > 0 && equalIndex < trimmedPart.Length - 1)
+            {
+                var key = trimmedPart[..equalIndex].Trim();
+                var value = trimmedPart[(equalIndex + 1)..].Trim();
+                if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value))
+                {
+                    result[key] = value;
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /// 
+    /// Splits a token on the last occurrence of ':' or ',' to separate host and port.
+    /// 
+    /// The token to split. Examples: "localhost:5432", "host,1433", "host:8080:extra"
+    /// A tuple with the host part and port part. Port part may be empty if no delimiter is found.
+    private static (string host, string port) SplitOnLast(string token)
+    {
+        // Split on the last occurrence of ':' or ','
+        var lastColonIndex = token.LastIndexOf(':');
+        var lastCommaIndex = token.LastIndexOf(',');
+        var splitIndex = Math.Max(lastColonIndex, lastCommaIndex);
+
+        if (splitIndex > 0 && splitIndex < token.Length - 1)
+        {
+            return (token[..splitIndex].Trim(), token[(splitIndex + 1)..].Trim());
+        }
+
+        return (token, string.Empty);
+    }
+
+    /// 
+    /// Determines if a string looks like a hostname rather than a file path or other non-hostname string.
+    /// Uses URI validation with conservative heuristics to avoid false positives.
+    /// 
+    /// The string to evaluate. Examples: "localhost" (valid), "/path/to/file.db" (invalid), "api.example.com" (valid)
+    /// True if the string appears to be a hostname; otherwise false.
+    private static bool LooksLikeHost(string connectionString)
+    {
+        // Reject strings with '=' (likely key-value pairs)
+        if (connectionString.Contains('='))
+        {
+            return false;
+        }
+
+        // Reject obvious file path indicators
+        if (connectionString.StartsWith('/') || connectionString.StartsWith('\\') ||
+            connectionString.StartsWith("./") || connectionString.StartsWith("../") ||
+            (connectionString.Length > 2 && connectionString[1] == ':' && char.IsLetter(connectionString[0])))
+        {
+            return false;
+        }
+
+        // Use Uri parsing to validate hostname - create a fake URI and see if it parses
+        var fakeUri = $"scheme://{connectionString.Trim()}";
+        return Uri.TryCreate(fakeUri, UriKind.Absolute, out var uri) && !string.IsNullOrEmpty(uri.Host);
+    }
+}
\ No newline at end of file
diff --git a/src/Aspire.Dashboard/Model/ResourceOutgoingPeerResolver.cs b/src/Aspire.Dashboard/Model/ResourceOutgoingPeerResolver.cs
index 7a8277a31be..15b9401fef0 100644
--- a/src/Aspire.Dashboard/Model/ResourceOutgoingPeerResolver.cs
+++ b/src/Aspire.Dashboard/Model/ResourceOutgoingPeerResolver.cs
@@ -42,29 +42,30 @@ public ResourceOutgoingPeerResolver(IDashboardClient resourceService)
 
             await foreach (var changes in subscription.WithCancellation(_watchContainersTokenSource.Token).ConfigureAwait(false))
             {
-                var hasUrlChanges = false;
+                var hasPeerRelevantChanges = false;
 
                 foreach (var (changeType, resource) in changes)
                 {
                     if (changeType == ResourceViewModelChangeType.Upsert)
                     {
-                        if (!_resourceByName.TryGetValue(resource.Name, out var existingResource) || !AreEquivalent(resource.Urls, existingResource.Urls))
+                        if (!_resourceByName.TryGetValue(resource.Name, out var existingResource) || 
+                            !ArePeerRelevantPropertiesEquivalent(resource, existingResource))
                         {
-                            hasUrlChanges = true;
+                            hasPeerRelevantChanges = true;
                         }
 
                         _resourceByName[resource.Name] = resource;
                     }
                     else if (changeType == ResourceViewModelChangeType.Delete)
                     {
-                        hasUrlChanges = true;
+                        hasPeerRelevantChanges = true;
 
                         var removed = _resourceByName.TryRemove(resource.Name, out _);
                         Debug.Assert(removed, "Cannot remove unknown resource.");
                     }
                 }
 
-                if (hasUrlChanges)
+                if (hasPeerRelevantChanges)
                 {
                     await RaisePeerChangesAsync().ConfigureAwait(false);
                 }
@@ -72,7 +73,30 @@ public ResourceOutgoingPeerResolver(IDashboardClient resourceService)
         });
     }
 
-    private static bool AreEquivalent(ImmutableArray urls1, ImmutableArray urls2)
+    private static bool ArePeerRelevantPropertiesEquivalent(ResourceViewModel resource1, ResourceViewModel resource2)
+    {
+        // Check if URLs are equivalent
+        if (!AreUrlsEquivalent(resource1.Urls, resource2.Urls))
+        {
+            return false;
+        }
+
+        // Check if connection string properties are equivalent
+        if (!ArePropertyValuesEquivalent(resource1, resource2, KnownProperties.Resource.ConnectionString))
+        {
+            return false;
+        }
+
+        // Check if parameter value properties are equivalent
+        if (!ArePropertyValuesEquivalent(resource1, resource2, KnownProperties.Parameter.Value))
+        {
+            return false;
+        }
+
+        return true;
+    }
+
+    private static bool AreUrlsEquivalent(ImmutableArray urls1, ImmutableArray urls2)
     {
         // Compare if the two sets of URLs are equivalent.
         if (urls1.Length != urls2.Length)
@@ -94,30 +118,79 @@ private static bool AreEquivalent(ImmutableArray urls1, ImmutableA
         return true;
     }
 
-    public bool TryResolvePeer(KeyValuePair[] attributes, out string? name, out ResourceViewModel? matchedResource)
+    private static bool ArePropertyValuesEquivalent(ResourceViewModel resource1, ResourceViewModel resource2, string propertyName)
     {
-        return TryResolvePeerNameCore(_resourceByName, attributes, out name, out matchedResource);
+        var hasProperty1 = resource1.Properties.TryGetValue(propertyName, out var property1);
+        var hasProperty2 = resource2.Properties.TryGetValue(propertyName, out var property2);
+
+        // If both don't have the property, they're equivalent
+        if (!hasProperty1 && !hasProperty2)
+        {
+            return true;
+        }
+
+        // If only one has the property, they're not equivalent
+        if (hasProperty1 != hasProperty2)
+        {
+            return false;
+        }
+
+        // Both have the property, compare values
+        var value1 = property1!.Value.TryConvertToString(out var str1) ? str1 : string.Empty;
+        var value2 = property2!.Value.TryConvertToString(out var str2) ? str2 : string.Empty;
+
+        return string.Equals(value1, value2, StringComparison.Ordinal);
     }
 
-    internal static bool TryResolvePeerNameCore(IDictionary resources, KeyValuePair[] attributes, [NotNullWhen(true)] out string? name, [NotNullWhen(true)] out ResourceViewModel? resourceMatch)
+    public bool TryResolvePeer(KeyValuePair[] attributes, out string? name, out ResourceViewModel? matchedResource)
     {
         var address = OtlpHelpers.GetPeerAddress(attributes);
         if (address != null)
         {
-            // Match exact value.
-            if (TryMatchResourceAddress(address, out name, out resourceMatch))
+            // Apply transformers to the peer address cumulatively
+            var transformedAddress = address;
+            
+            // First check exact match
+            if (TryMatchAgainstResources(transformedAddress, _resourceByName, out name, out matchedResource))
             {
                 return true;
             }
+            
+            // Then apply each transformer cumulatively and check
+            foreach (var transformer in s_addressTransformers)
+            {
+                transformedAddress = transformer(transformedAddress);
+                if (TryMatchAgainstResources(transformedAddress, _resourceByName, out name, out matchedResource))
+                {
+                    return true;
+                }
+            }
+        }
+
+        name = null;
+        matchedResource = null;
+        return false;
+    }
 
-            // Resource addresses have the format "127.0.0.1:5000". Some libraries modify the peer.service value on the span.
-            // If there isn't an exact match then transform the peer.service value and try to match again.
-            // Change from transformers are cumulative. e.g. "localhost,5000" -> "localhost:5000" -> "127.0.0.1:5000"
+    internal static bool TryResolvePeerNameCore(IDictionary resources, KeyValuePair[] attributes, [NotNullWhen(true)] out string? name, [NotNullWhen(true)] out ResourceViewModel? resourceMatch)
+    {
+        var address = OtlpHelpers.GetPeerAddress(attributes);
+        if (address != null)
+        {
+            // Apply transformers to the peer address cumulatively
             var transformedAddress = address;
+            
+            // First check exact match
+            if (TryMatchAgainstResources(transformedAddress, resources, out name, out resourceMatch))
+            {
+                return true;
+            }
+            
+            // Then apply each transformer cumulatively and check
             foreach (var transformer in s_addressTransformers)
             {
                 transformedAddress = transformer(transformedAddress);
-                if (TryMatchResourceAddress(transformedAddress, out name, out resourceMatch))
+                if (TryMatchAgainstResources(transformedAddress, resources, out name, out resourceMatch))
                 {
                     return true;
                 }
@@ -127,28 +200,51 @@ internal static bool TryResolvePeerNameCore(IDictionary
+    /// Checks if a transformed peer address matches any of the resource addresses using their cached addresses.
+    /// Applies the same transformations to resource addresses for consistent matching.
+    /// 
+    private static bool TryMatchAgainstResources(string peerAddress, IDictionary resources, [NotNullWhen(true)] out string? name, [NotNullWhen(true)] out ResourceViewModel? resourceMatch)
+    {
+        foreach (var (_, resource) in resources)
         {
-            foreach (var (resourceName, resource) in resources)
+            foreach (var resourceAddress in resource.CachedAddresses)
             {
-                foreach (var service in resource.Urls)
+                if (DoesAddressMatch(resourceAddress, peerAddress))
                 {
-                    var hostAndPort = service.Url.GetComponents(UriComponents.HostAndPort, UriFormat.UriEscaped);
-
-                    if (string.Equals(hostAndPort, value, StringComparison.OrdinalIgnoreCase))
-                    {
-                        name = ResourceViewModel.GetResourceName(resource, resources);
-                        resourceMatch = resource;
-                        return true;
-                    }
+                    name = ResourceViewModel.GetResourceName(resource, resources);
+                    resourceMatch = resource;
+                    return true;
                 }
             }
+        }
 
-            name = null;
-            resourceMatch = null;
-            return false;
+        name = null;
+        resourceMatch = null;
+        return false;
+    }
+
+    private static bool DoesAddressMatch(string endpoint, string value)
+    {
+        if (string.Equals(endpoint, value, StringComparison.OrdinalIgnoreCase))
+        {
+            return true;
         }
+
+        // Apply the same transformations that are applied to the peer service value
+        var transformedEndpoint = endpoint;
+        foreach (var transformer in s_addressTransformers)
+        {
+            transformedEndpoint = transformer(transformedEndpoint);
+            if (string.Equals(transformedEndpoint, value, StringComparison.OrdinalIgnoreCase))
+            {
+                return true;
+            }
+        }
+
+        return false;
     }
 
     private static readonly List> s_addressTransformers = [
diff --git a/src/Aspire.Dashboard/Model/ResourceViewModel.cs b/src/Aspire.Dashboard/Model/ResourceViewModel.cs
index 68d7705aa96..4fc40862b20 100644
--- a/src/Aspire.Dashboard/Model/ResourceViewModel.cs
+++ b/src/Aspire.Dashboard/Model/ResourceViewModel.cs
@@ -22,6 +22,7 @@ public sealed class ResourceViewModel
 {
     private readonly ImmutableArray _healthReports = [];
     private readonly KnownResourceState? _knownState;
+    private Lazy>? _cachedAddresses;
 
     public required string Name { get; init; }
     public required string ResourceType { get; init; }
@@ -43,6 +44,44 @@ public sealed class ResourceViewModel
     public bool IsHidden { private get; init; }
     public bool SupportsDetailedTelemetry { get; init; }
 
+    /// 
+    /// Gets the cached addresses for this resource that can be used for peer matching.
+    /// This includes addresses extracted from URLs, connection strings, and parameter values.
+    /// 
+    public ImmutableArray CachedAddresses => (_cachedAddresses ??= new Lazy>(ExtractResourceAddresses)).Value;
+
+    private ImmutableArray ExtractResourceAddresses()
+    {
+        var addresses = new List();
+
+        // Extract addresses from URL endpoints
+        foreach (var service in Urls)
+        {
+            var hostAndPort = service.Url.GetComponents(UriComponents.HostAndPort, UriFormat.UriEscaped);
+            addresses.Add(hostAndPort);
+        }
+
+        // Extract addresses from connection strings using comprehensive parsing
+        if (Properties.TryGetValue(KnownProperties.Resource.ConnectionString, out var connectionStringProperty) &&
+            connectionStringProperty.Value.TryConvertToString(out var connectionString) &&
+            ConnectionStringParser.TryDetectHostAndPort(connectionString, out var host, out var port))
+        {
+            var endpoint = port.HasValue ? $"{host}:{port.Value}" : host;
+            addresses.Add(endpoint);
+        }
+
+        // Extract addresses from parameter values (for Parameter resources that contain URLs or host:port values)
+        if (Properties.TryGetValue(KnownProperties.Parameter.Value, out var parameterValueProperty) &&
+            parameterValueProperty.Value.TryConvertToString(out var parameterValue) &&
+            ConnectionStringParser.TryDetectHostAndPort(parameterValue, out var parameterHost, out var parameterPort))
+        {
+            var parameterEndpoint = parameterPort.HasValue ? $"{parameterHost}:{parameterPort.Value}" : parameterHost;
+            addresses.Add(parameterEndpoint);
+        }
+
+        return addresses.ToImmutableArray();
+    }
+
     public required ImmutableArray HealthReports
     {
         get => _healthReports;
diff --git a/src/Aspire.Hosting.GitHub.Models/GitHubModelsExtensions.cs b/src/Aspire.Hosting.GitHub.Models/GitHubModelsExtensions.cs
index cabfd188121..27e5f2af9c8 100644
--- a/src/Aspire.Hosting.GitHub.Models/GitHubModelsExtensions.cs
+++ b/src/Aspire.Hosting.GitHub.Models/GitHubModelsExtensions.cs
@@ -34,11 +34,29 @@ public static IResourceBuilder AddGitHubModel(this IDistrib
             {
                 ResourceType = "GitHubModel",
                 CreationTimeStamp = DateTime.UtcNow,
-                State = new ResourceStateSnapshot(KnownResourceStates.Running, KnownResourceStateStyles.Success),
+                State = KnownResourceStates.Waiting,
                 Properties =
-                    [
-                        new(CustomResourceKnownProperties.Source, "GitHub Models")
-                    ]
+                [
+                    new(CustomResourceKnownProperties.Source, "GitHub Models")
+                ]
+            })
+            .OnInitializeResource(async (r, evt, ct) =>
+            {
+                // Connection string resolution is dependent on parameters being resolved
+                // We use this to wait for the parameters to be resolved before we can compute the connection string.
+                var cs = await r.ConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false);
+
+                // Publish the update with the connection string value and the state as running.
+                // This will allow health checks to start running.
+                await evt.Notifications.PublishUpdateAsync(r, s => s with
+                {
+                    State = KnownResourceStates.Running,
+                    Properties = [.. s.Properties, new(CustomResourceKnownProperties.ConnectionString, cs) { IsSensitive = true }]
+                }).ConfigureAwait(false);
+
+                // Publish the connection string available event for other resources that may depend on this resource.
+                await evt.Eventing.PublishAsync(new ConnectionStringAvailableEvent(r, evt.Services), ct)
+                                  .ConfigureAwait(false);
             });
     }
 
@@ -94,7 +112,7 @@ public static IResourceBuilder WithHealthCheck(this IResour
                 {
                     // Cache the health check instance so we can reuse its result in order to avoid multiple API calls
                     // that would exhaust the rate limit.
-                    
+
                     if (healthCheck is not null)
                     {
                         return healthCheck;
diff --git a/tests/Aspire.Dashboard.Tests/ConnectionStringParserTests.cs b/tests/Aspire.Dashboard.Tests/ConnectionStringParserTests.cs
new file mode 100644
index 00000000000..e5a897cc401
--- /dev/null
+++ b/tests/Aspire.Dashboard.Tests/ConnectionStringParserTests.cs
@@ -0,0 +1,155 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Aspire.Dashboard.Model;
+using Xunit;
+
+namespace Aspire.Dashboard.Tests;
+
+public class ConnectionStringParserTests
+{
+    [Theory]
+    [InlineData("redis://[fe80::1]:6380", true, "fe80::1", 6380)]
+    [InlineData("postgres://h/db", true, "h", 5432)]
+    [InlineData("Endpoint=h:6379;password=pw", true, "h", 6379)]
+    [InlineData("host=h;user=foo", true, "h", null)]
+    [InlineData("broker1:9092,broker2:9092", true, "broker1", 9092)]
+    [InlineData("/var/sqlite/file.db", false, "", null)]
+    [InlineData("foo bar baz", false, "", null)]
+    [InlineData("https://models.github.ai/inference", true, "models.github.ai", 443)]
+    [InlineData("Server=tcp:localhost,1433;Database=test", true, "localhost", 1433)]
+    [InlineData("Server=localhost;port=5432", true, "localhost", 5432)]
+    // SQL Server patterns
+    [InlineData("Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=myPassword;", true, "myServerAddress", null)]
+    [InlineData("Server=myServerAddress,1433;Database=myDataBase;Trusted_Connection=True;", true, "myServerAddress", 1433)]
+    [InlineData("Data Source=tcp:localhost,1433;Initial Catalog=TestDB;", true, "localhost", 1433)]
+    [InlineData("Data Source=.\\SQLEXPRESS;AttachDbFilename=|DataDirectory|mydbfile.mdf;Integrated Security=true;User Instance=true;", true, ".\\SQLEXPRESS", null)]
+    [InlineData("Server=(localdb)\\MSSQLLocalDB;Database=AspNetCore.StarterSite;Trusted_Connection=true;MultipleActiveResultSets=true", true, "(localdb)\\MSSQLLocalDB", null)]
+    // PostgreSQL patterns  
+    [InlineData("Host=localhost;Database=mydb;Username=myuser;Password=mypass", true, "localhost", null)]
+    [InlineData("Host=localhost;Port=5432;Database=mydb;Username=myuser;Password=mypass", true, "localhost", 5432)]
+    [InlineData("postgresql://user:password@localhost:5432/dbname", true, "localhost", 5432)]
+    [InlineData("postgres://user:password@localhost/dbname", true, "localhost", 5432)]
+    // MySQL patterns
+    [InlineData("Server=localhost;Database=myDataBase;Uid=myUsername;Pwd=myPassword;", true, "localhost", null)]
+    [InlineData("Server=localhost;Port=3306;Database=myDataBase;Uid=myUsername;Pwd=myPassword;", true, "localhost", 3306)]
+    [InlineData("mysql://user:password@localhost:3306/database", true, "localhost", 3306)]
+    // MongoDB patterns
+    [InlineData("mongodb://localhost:27017", true, "localhost", 27017)]
+    [InlineData("mongodb://user:password@localhost:27017/database", true, "localhost", 27017)]
+    [InlineData("mongodb://localhost", true, "localhost", 27017)]
+    [InlineData("mongodb+srv://cluster0.example.mongodb.net/database", true, "cluster0.example.mongodb.net", null)]
+    // Redis patterns
+    [InlineData("localhost:6379", true, "localhost", 6379)]
+    [InlineData("redis://localhost:6379", true, "localhost", 6379)]
+    [InlineData("rediss://localhost:6380", true, "localhost", 6380)]
+    [InlineData("redis://user:password@localhost:6379/0", true, "localhost", 6379)]
+    [InlineData("Endpoint=localhost:6379;Password=mypassword", true, "localhost", 6379)]
+    // Oracle patterns
+    [InlineData("Data Source=localhost:1521/XE;User Id=hr;Password=password;", true, "localhost", null)] // Won't parse port from path syntax
+    // JDBC patterns (basic ones that should work - but many JDBC URLs are complex)
+    [InlineData("jdbc:postgresql://localhost:5432/database", true, "localhost", 5432)]
+    [InlineData("jdbc:mysql://localhost:3306/database", true, "localhost", 3306)]
+    [InlineData("jdbc:sqlserver://localhost:1433;databaseName=TestDB", true, "localhost", 1433)]
+    // Cloud provider patterns
+    [InlineData("https://myaccount.blob.core.windows.net/", true, "myaccount.blob.core.windows.net", 443)]
+    [InlineData("https://myvault.vault.azure.net:8080/", true, "myvault.vault.azure.net", 8080)]
+    [InlineData("Server=tcp:myserver.database.windows.net,1433;Database=mydatabase;", true, "myserver.database.windows.net", 1433)]
+    // Kafka patterns
+    [InlineData("localhost:9092,localhost:9093,localhost:9094", true, "localhost", 9092)]
+    [InlineData("broker-1:9092,broker-2:9092", true, "broker-1", 9092)]
+    // RabbitMQ patterns
+    [InlineData("amqp://localhost", true, "localhost", 5672)]
+    [InlineData("amqp://user:pass@localhost:5672/vhost", true, "localhost", 5672)]
+    [InlineData("amqps://localhost:5671", true, "localhost", 5671)]
+    [InlineData("Host=localhost;Port=5672;VirtualHost=/;Username=guest;Password=guest", true, "localhost", 5672)]
+    // Elasticsearch patterns
+    [InlineData("http://localhost:9200", true, "localhost", 9200)]
+    [InlineData("https://elastic:password@localhost:9200", true, "localhost", 9200)]
+    // InfluxDB patterns  
+    [InlineData("http://localhost:8086", true, "localhost", 8086)]
+    [InlineData("https://localhost:8086", true, "localhost", 8086)]
+    // Cassandra patterns
+    [InlineData("Contact Points=localhost;Port=9042", true, "localhost", 9042)]
+    [InlineData("Contact Points=node1,node2,node3;Port=9042", false, "", null)] // Multiple contact points - too complex
+    // Neo4j patterns
+    [InlineData("bolt://localhost:7687", true, "localhost", 7687)]
+    [InlineData("neo4j://localhost:7687", true, "localhost", 7687)]
+    // Docker/container patterns
+    [InlineData("server.local", true, "server.local", null)]
+    [InlineData("my-service:5432", true, "my-service", 5432)]
+    [InlineData("my-namespace.my-service.svc.cluster.local:5432", true, "my-namespace.my-service.svc.cluster.local", 5432)]
+    // IPv6 patterns
+    [InlineData("Server=[::1],1433", true, "::1", 1433)]
+    [InlineData("Host=[2001:db8::1];Port=5432", true, "2001:db8::1", 5432)]
+    [InlineData("http://[2001:db8::1]:8080", true, "2001:db8::1", 8080)]
+    // Edge cases and invalid patterns
+    [InlineData("", false, "", null)]
+    [InlineData("   ", false, "", null)]
+    [InlineData("=", false, "", null)]
+    [InlineData("key=", false, "", null)]
+    [InlineData("=value", false, "", null)]
+    [InlineData("C:\\path\\to\\file.db", false, "", null)]
+    [InlineData("./relative/path/file.db", false, "", null)]
+    [InlineData("/absolute/path/file.db", false, "", null)]
+    [InlineData("just some random text", false, "", null)]
+    [InlineData("host=;port=5432", false, "", null)] // Empty host
+    [InlineData("server=localhost;port=abc", true, "localhost", null)] // Invalid port
+    [InlineData("server=localhost;port=99999", true, "localhost", null)] // Port out of range
+    public void TryDetectHostAndPort_VariousFormats_ReturnsExpectedResults(
+        string connectionString, 
+        bool expectedResult, 
+        string expectedHost, 
+        int? expectedPort)
+    {
+        // Act
+        var result = ConnectionStringParser.TryDetectHostAndPort(connectionString, out var host, out var port);
+        
+        // Assert
+        Assert.Equal(expectedResult, result);
+        if (expectedResult)
+        {
+            Assert.Equal(expectedHost, host);
+            Assert.Equal(expectedPort, port);
+        }
+        else
+        {
+            Assert.Null(host);
+            Assert.Null(port);
+        }
+    }
+
+    [Fact]
+    public void TryDetectHostAndPort_IPv6URI_ReturnsCorrectHost()
+    {
+        // Test case specifically for IPv6 addresses with brackets
+        var connectionString = "redis://[fe80::1]:6380";
+        var result = ConnectionStringParser.TryDetectHostAndPort(connectionString, out var host, out var port);
+        
+        Assert.True(result);
+        Assert.Equal("fe80::1", host); // Brackets should be trimmed
+        Assert.Equal(6380, port);
+    }
+
+    [Fact]
+    public void TryDetectHostAndPort_KeyValuePairsWithSemicolon_ParsesCorrectly()
+    {
+        var connectionString = "Endpoint=h:6379;password=pw;database=0";
+        var result = ConnectionStringParser.TryDetectHostAndPort(connectionString, out var host, out var port);
+        
+        Assert.True(result);
+        Assert.Equal("h", host);
+        Assert.Equal(6379, port);
+    }
+
+    [Fact]
+    public void TryDetectHostAndPort_DelimitedList_TakesFirstEntry()
+    {
+        var connectionString = "broker1:9092,broker2:9093,broker3:9094";
+        var result = ConnectionStringParser.TryDetectHostAndPort(connectionString, out var host, out var port);
+        
+        Assert.True(result);
+        Assert.Equal("broker1", host);
+        Assert.Equal(9092, port);
+    }
+}
\ No newline at end of file
diff --git a/tests/Aspire.Dashboard.Tests/ResourceOutgoingPeerResolverTests.cs b/tests/Aspire.Dashboard.Tests/ResourceOutgoingPeerResolverTests.cs
index 4914d27ec0c..32fe3c48a5a 100644
--- a/tests/Aspire.Dashboard.Tests/ResourceOutgoingPeerResolverTests.cs
+++ b/tests/Aspire.Dashboard.Tests/ResourceOutgoingPeerResolverTests.cs
@@ -8,6 +8,7 @@
 using Aspire.Tests.Shared.DashboardModel;
 using Microsoft.AspNetCore.InternalTesting;
 using Xunit;
+using Value = Google.Protobuf.WellKnownTypes.Value;
 
 namespace Aspire.Dashboard.Tests;
 
@@ -219,6 +220,143 @@ private static bool TryResolvePeerName(IDictionary re
         return ResourceOutgoingPeerResolver.TryResolvePeerNameCore(resources, attributes, out peerName, out _);
     }
 
+    [Fact]
+    public void ConnectionStringWithEndpoint_Match()
+    {
+        // Arrange - GitHub Models resource with connection string containing endpoint
+        var connectionString = "Endpoint=https://models.github.ai/inference;Key=test-key;Model=openai/gpt-4o-mini;DeploymentId=openai/gpt-4o-mini";
+        var resources = new Dictionary
+        {
+            ["github-model"] = CreateResourceWithConnectionString("github-model", connectionString)
+        };
+
+        // Act & Assert
+        Assert.True(TryResolvePeerName(resources, [KeyValuePair.Create("peer.service", "models.github.ai:443")], out var value));
+        Assert.Equal("github-model", value);
+    }
+
+    [Fact]
+    public void ConnectionStringWithEndpointOrganization_Match()
+    {
+        // Arrange - GitHub Models resource with organization endpoint
+        var connectionString = "Endpoint=https://models.github.ai/orgs/myorg/inference;Key=test-key;Model=openai/gpt-4o-mini;DeploymentId=openai/gpt-4o-mini";
+        var resources = new Dictionary
+        {
+            ["github-model"] = CreateResourceWithConnectionString("github-model", connectionString)
+        };
+
+        // Act & Assert
+        Assert.True(TryResolvePeerName(resources, [KeyValuePair.Create("peer.service", "models.github.ai:443")], out var value));
+        Assert.Equal("github-model", value);
+    }
+
+    [Fact]
+    public void ParameterWithUrlValue_Match()
+    {
+        // Arrange - Parameter resource with URL value
+        var resources = new Dictionary
+        {
+            ["api-url-param"] = CreateResourceWithParameterValue("api-url-param", "https://api.example.com:8080/endpoint")
+        };
+
+        // Act & Assert
+        Assert.True(TryResolvePeerName(resources, [KeyValuePair.Create("peer.service", "api.example.com:8080")], out var value));
+        Assert.Equal("api-url-param", value);
+    }
+
+    [Fact]
+    public void ConnectionStringWithoutEndpoint_NoMatch()
+    {
+        // Arrange - Connection string without Endpoint property
+        var connectionString = "Server=localhost;Database=test;User=admin;Password=secret";
+        var resources = new Dictionary
+        {
+            ["sql-connection"] = CreateResourceWithConnectionString("sql-connection", connectionString)
+        };
+
+        // Act & Assert
+        Assert.False(TryResolvePeerName(resources, [KeyValuePair.Create("peer.service", "localhost:1433")], out _));
+    }
+
+    [Fact]
+    public void ParameterWithNonUrlValue_NoMatch()
+    {
+        // Arrange - Parameter resource with non-URL value
+        var resources = new Dictionary
+        {
+            ["config-param"] = CreateResourceWithParameterValue("config-param", "simple-config-value")
+        };
+
+        // Act & Assert
+        Assert.False(TryResolvePeerName(resources, [KeyValuePair.Create("peer.service", "localhost:5000")], out _));
+    }
+
+    [Fact]
+    public void ConnectionStringAsDirectUrl_Match()
+    {
+        // Arrange - Connection string that is itself a URL (e.g., blob storage)
+        var connectionString = "https://mystorageaccount.blob.core.windows.net/";
+        var resources = new Dictionary
+        {
+            ["blob-storage"] = CreateResourceWithConnectionString("blob-storage", connectionString)
+        };
+
+        // Act & Assert
+        Assert.True(TryResolvePeerName(resources, [KeyValuePair.Create("peer.service", "mystorageaccount.blob.core.windows.net:443")], out var value));
+        Assert.Equal("blob-storage", value);
+    }
+
+    [Fact]
+    public void ConnectionStringAsDirectUrlWithCustomPort_Match()
+    {
+        // Arrange - Connection string that is itself a URL with custom port
+        var connectionString = "https://myvault.vault.azure.net:8080/";
+        var resources = new Dictionary
+        {
+            ["key-vault"] = CreateResourceWithConnectionString("key-vault", connectionString)
+        };
+
+        // Act & Assert
+        Assert.True(TryResolvePeerName(resources, [KeyValuePair.Create("peer.service", "myvault.vault.azure.net:8080")], out var value));
+        Assert.Equal("key-vault", value);
+    }
+
+    private static ResourceViewModel CreateResourceWithConnectionString(string name, string connectionString)
+    {
+        var properties = new Dictionary
+        {
+            [KnownProperties.Resource.ConnectionString] = new(
+                name: KnownProperties.Resource.ConnectionString,
+                value: Value.ForString(connectionString),
+                isValueSensitive: false,
+                knownProperty: null,
+                priority: 0)
+        };
+
+        return ModelTestHelpers.CreateResource(
+            appName: name,
+            resourceType: KnownResourceTypes.ConnectionString,
+            properties: properties);
+    }
+
+    private static ResourceViewModel CreateResourceWithParameterValue(string name, string value)
+    {
+        var properties = new Dictionary
+        {
+            [KnownProperties.Parameter.Value] = new(
+                name: KnownProperties.Parameter.Value,
+                value: Value.ForString(value),
+                isValueSensitive: false,
+                knownProperty: null,
+                priority: 0)
+        };
+
+        return ModelTestHelpers.CreateResource(
+            appName: name,
+            resourceType: KnownResourceTypes.Parameter,
+            properties: properties);
+    }
+
     private sealed class MockDashboardClient(Task subscribeResult) : IDashboardClient
     {
         public bool IsEnabled => true;