3
3
4
4
using System ;
5
5
using System . Collections . Generic ;
6
+ using System . Diagnostics ;
6
7
using System . Threading . Tasks ;
7
8
using Microsoft . AspNetCore . Components ;
8
9
using Microsoft . AspNetCore . Components . Rendering ;
@@ -157,6 +158,10 @@ public void AddAttribute(int sequence, string name, bool value)
157
158
// or absence of an attribute, and false => "False" which isn't falsy in js.
158
159
Append ( RenderTreeFrame . Attribute ( sequence , name , BoxedTrue ) ) ;
159
160
}
161
+ else
162
+ {
163
+ ClearAttributesWithName ( name ) ;
164
+ }
160
165
}
161
166
162
167
/// <summary>
@@ -178,6 +183,10 @@ public void AddAttribute(int sequence, string name, string value)
178
183
{
179
184
Append ( RenderTreeFrame . Attribute ( sequence , name , value ) ) ;
180
185
}
186
+ else
187
+ {
188
+ ClearAttributesWithName ( name ) ;
189
+ }
181
190
}
182
191
183
192
/// <summary>
@@ -275,6 +284,10 @@ public void AddAttribute(int sequence, string name, MulticastDelegate value)
275
284
{
276
285
Append ( RenderTreeFrame . Attribute ( sequence , name , value ) ) ;
277
286
}
287
+ else
288
+ {
289
+ ClearAttributesWithName ( name ) ;
290
+ }
278
291
}
279
292
280
293
/// <summary>
@@ -372,16 +385,24 @@ public void AddAttribute(int sequence, string name, object value)
372
385
{
373
386
if ( value == null )
374
387
{
375
- // Do nothing, treat 'null' attribute values for elements as a conditional attribute.
388
+ // Treat 'null' attribute values for elements as a conditional attribute.
389
+ ClearAttributesWithName ( name ) ;
376
390
}
377
391
else if ( value is bool boolValue )
378
392
{
379
393
if ( boolValue )
380
394
{
381
395
Append ( RenderTreeFrame . Attribute ( sequence , name , BoxedTrue ) ) ;
382
396
}
383
-
384
- // Don't add anything for false bool value.
397
+ else
398
+ {
399
+ // Don't add anything for false bool value.
400
+ ClearAttributesWithName ( name ) ;
401
+ }
402
+ }
403
+ else if ( value is IEventCallback callbackValue )
404
+ {
405
+ Append ( RenderTreeFrame . Attribute ( sequence , name , callbackValue . UnpackForRenderTree ( ) ) ) ;
385
406
}
386
407
else if ( value is MulticastDelegate )
387
408
{
@@ -395,6 +416,7 @@ public void AddAttribute(int sequence, string name, object value)
395
416
}
396
417
else if ( _lastNonAttributeFrameType == RenderTreeFrameType . Component )
397
418
{
419
+ // If this is a component, we always want to preserve the original type.
398
420
Append ( RenderTreeFrame . Attribute ( sequence , name , value ) ) ;
399
421
}
400
422
else
@@ -425,6 +447,38 @@ public void AddAttribute(int sequence, in RenderTreeFrame frame)
425
447
Append ( frame . WithAttributeSequence ( sequence ) ) ;
426
448
}
427
449
450
+ /// <summary>
451
+ /// Adds frames representing multiple attributes with the same sequence number.
452
+ /// </summary>
453
+ /// <typeparam name="T">The attribute value type.</typeparam>
454
+ /// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
455
+ /// <param name="attributes">A collection of key-value pairs representing attributes.</param>
456
+ public void AddMultipleAttributes < T > ( int sequence , IEnumerable < KeyValuePair < string , T > > attributes )
457
+ {
458
+ // NOTE: The IEnumerable<KeyValuePair<string, T>> is the simplest way to support a variety of
459
+ // different types like IReadOnlyDictionary<>, Dictionary<>, and IDictionary<>.
460
+ //
461
+ // None of those types are contravariant, and since we want to support attributes having a value
462
+ // of type object, the simplest thing to do is drop down to IEnumerable<KeyValuePair<>> which
463
+ // is contravariant. This also gives us things like List<KeyValuePair<>> and KeyValuePair<>[]
464
+ // for free even though we don't expect those types to be common.
465
+
466
+ // Calling this up-front just to make sure we validate before mutating anything.
467
+ AssertCanAddAttribute ( ) ;
468
+
469
+ if ( attributes != null )
470
+ {
471
+ foreach ( var attribute in attributes )
472
+ {
473
+ // This will call the AddAttribute(int, string, object) overload.
474
+ //
475
+ // This is fine because we try to make the object overload behave identically
476
+ // to the others.
477
+ AddAttribute ( sequence , attribute . Key , attribute . Value ) ;
478
+ }
479
+ }
480
+ }
481
+
428
482
/// <summary>
429
483
/// Appends a frame representing a child component.
430
484
/// </summary>
@@ -590,13 +644,42 @@ public ArrayRange<RenderTreeFrame> GetFrames() =>
590
644
591
645
private void Append ( in RenderTreeFrame frame )
592
646
{
647
+ var frameType = frame . FrameType ;
648
+ if ( frameType == RenderTreeFrameType . Attribute )
649
+ {
650
+ ClearAttributesWithName ( frame . AttributeName ) ;
651
+ }
652
+
593
653
_entries . Append ( frame ) ;
594
654
595
- var frameType = frame . FrameType ;
596
655
if ( frameType != RenderTreeFrameType . Attribute )
597
656
{
598
657
_lastNonAttributeFrameType = frame . FrameType ;
599
658
}
600
659
}
660
+
661
+ private void ClearAttributesWithName ( string name )
662
+ {
663
+ // When an AddAttribute or AddMultipleAttributes method is called, we need to clear
664
+ // any prior attributes that have the same attribute name for the current element
665
+ // or component.
666
+ //
667
+ // This is how we enforce the *last attribute wins* semantic.
668
+ Debug . Assert ( _openElementIndices . Count > 0 ) ;
669
+
670
+ // Start at the last open element/component and iterate forward until
671
+ // we find a duplicate.
672
+ //
673
+ // Since we prevent duplicates, we can always stop after finding one.
674
+ for ( var i = _openElementIndices . Peek ( ) ; i < _entries . Count ; i ++ )
675
+ {
676
+ if ( _entries . Buffer [ i ] . FrameType == RenderTreeFrameType . Attribute &&
677
+ string . Equals ( name , _entries . Buffer [ i ] . AttributeName , StringComparison . OrdinalIgnoreCase ) )
678
+ {
679
+ _entries . RemoveAt ( i ) ;
680
+ break ;
681
+ }
682
+ }
683
+ }
601
684
}
602
685
}
0 commit comments