99using CommandsResources = Aspire . Dashboard . Resources . Commands ;
1010using Aspire . Dashboard . Resources ;
1111using Aspire . Hosting ;
12+ using Google . Protobuf . Collections ;
1213
1314namespace Aspire . ResourceService . Proto . V1 ;
1415
@@ -17,7 +18,7 @@ partial class Resource
1718 /// <summary>
1819 /// Converts this gRPC message object to a view model for use in the dashboard UI.
1920 /// </summary>
20- public ResourceViewModel ToViewModel ( BrowserTimeProvider timeProvider , IKnownPropertyLookup knownPropertyLookup )
21+ public ResourceViewModel ToViewModel ( BrowserTimeProvider timeProvider , IKnownPropertyLookup knownPropertyLookup , ILogger logger )
2122 {
2223 try
2324 {
@@ -30,21 +31,7 @@ public ResourceViewModel ToViewModel(BrowserTimeProvider timeProvider, IKnownPro
3031 CreationTimeStamp = ValidateNotNull ( CreatedAt ) . ToDateTime ( ) ,
3132 StartTimeStamp = StartedAt ? . ToDateTime ( ) ,
3233 StopTimeStamp = StoppedAt ? . ToDateTime ( ) ,
33- Properties = Properties . ToImmutableDictionary (
34- keyComparer : StringComparers . ResourcePropertyName ,
35- keySelector : property => ValidateNotNull ( property . Name ) ,
36- elementSelector : property =>
37- {
38- var ( priority , knownProperty ) = knownPropertyLookup . FindProperty ( ResourceType , property . Name ) ;
39-
40- return new ResourcePropertyViewModel (
41- name : ValidateNotNull ( property . Name ) ,
42- value : ValidateNotNull ( property . Value ) ,
43- isValueSensitive : property . IsSensitive ,
44- knownProperty : knownProperty ,
45- priority : priority ,
46- timeProvider : timeProvider ) ;
47- } ) ,
34+ Properties = CreatePropertyViewModels ( Properties , timeProvider , knownPropertyLookup , logger ) ,
4835 Environment = GetEnvironment ( ) ,
4936 Urls = GetUrls ( ) ,
5037 Volumes = GetVolumes ( ) ,
@@ -160,16 +147,42 @@ static FluentUIIconVariant MapIconVariant(IconVariant iconVariant)
160147 } ;
161148 }
162149 }
150+ }
151+
152+ private ImmutableDictionary < string , ResourcePropertyViewModel > CreatePropertyViewModels ( RepeatedField < ResourceProperty > properties , BrowserTimeProvider timeProvider , IKnownPropertyLookup knownPropertyLookup , ILogger logger )
153+ {
154+ var builder = ImmutableDictionary . CreateBuilder < string , ResourcePropertyViewModel > ( StringComparers . ResourcePropertyName ) ;
163155
164- T ValidateNotNull < T > ( T value , [ CallerArgumentExpression ( nameof ( value ) ) ] string ? expression = null ) where T : class
156+ foreach ( var property in properties )
165157 {
166- if ( value is null )
158+ var ( priority , knownProperty ) = knownPropertyLookup . FindProperty ( ResourceType , property . Name ) ;
159+ var propertyViewModel = new ResourcePropertyViewModel (
160+ name : ValidateNotNull ( property . Name ) ,
161+ value : ValidateNotNull ( property . Value ) ,
162+ isValueSensitive : property . IsSensitive ,
163+ knownProperty : knownProperty ,
164+ priority : priority ,
165+ timeProvider : timeProvider ) ;
166+
167+ if ( builder . ContainsKey ( propertyViewModel . Name ) )
167168 {
168- throw new InvalidOperationException ( $ "Message field '{ expression } ' on resource with name ' { Name } ' cannot be null." ) ;
169+ logger . LogWarning ( "Duplicate property '{PropertyName }' found in resource '{ResourceName}'." , propertyViewModel . Name , Name ) ;
169170 }
170171
171- return value ;
172+ builder [ propertyViewModel . Name ] = propertyViewModel ;
173+ }
174+
175+ return builder . ToImmutable ( ) ;
176+ }
177+
178+ private T ValidateNotNull < T > ( T value , [ CallerArgumentExpression ( nameof ( value ) ) ] string ? expression = null ) where T : class
179+ {
180+ if ( value is null )
181+ {
182+ throw new InvalidOperationException ( $ "Message field '{ expression } ' on resource with name '{ Name } ' cannot be null.") ;
172183 }
184+
185+ return value ;
173186 }
174187}
175188
0 commit comments