Add compiler support for EventCallback (dotnet/aspnetcore-tooling#222)
* Add new APIs to ComponentShim
* Add new APIs to ComponentsApi
* Add EventCallback metadata
* Add discovery of EventCallback properties
* Errata for Runtime APIs
* Add ability to use EventCallback as parameter
* Add support for bind to component
* Use EventCallback for bind-... to elements
* Use EventCallback<T> for event handlers
\n\nCommit migrated from a0b6bc0e52
This commit is contained in:
parent
1243e07e56
commit
54e79153ec
|
|
@ -55,6 +55,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
|
|||
|
||||
public static readonly string DelegateSignatureKey = "Blazor.DelegateSignature";
|
||||
|
||||
public static readonly string EventCallbackKey = "Blazor.EventCallback";
|
||||
|
||||
public static readonly string WeaklyTypedKey = "Blazor.IsWeaklyTyped";
|
||||
|
||||
public static readonly string RuntimeName = "Blazor.IComponent";
|
||||
|
|
|
|||
|
|
@ -158,9 +158,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
|
|||
// there will be errors. In general the errors that come from C# in this case are good enough
|
||||
// to understand the problem.
|
||||
//
|
||||
// The BindMethods calls are required in this case because to give us a good experience. They
|
||||
// We also support and encourage the use of EventCallback<> with bind. So in the above example
|
||||
// the ValueChanged property could be an Action<> or an EventCallback<>.
|
||||
//
|
||||
// The BindMethods calls are required with Action<> because to give us a good experience. They
|
||||
// use overloading to ensure that can get an Action<object> that will convert and set an arbitrary
|
||||
// value.
|
||||
// value. We have a similar set of APIs to use with EventCallback<>.
|
||||
//
|
||||
// We also assume that the element will be treated as a component for now because
|
||||
// multiple passes handle 'special' tag helpers. We have another pass that translates
|
||||
|
|
@ -207,67 +210,28 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
|
|||
format = GetAttributeContent(formatNode);
|
||||
}
|
||||
|
||||
// Now rewrite the content of the value node to look like:
|
||||
//
|
||||
// BindMethods.GetValue(<code>) OR
|
||||
// BindMethods.GetValue(<code>, <format>)
|
||||
var valueExpressionTokens = new List<IntermediateToken>();
|
||||
valueExpressionTokens.Add(new IntermediateToken()
|
||||
var changeExpressionTokens = new List<IntermediateToken>();
|
||||
if (changeAttribute != null && changeAttribute.IsDelegateProperty())
|
||||
{
|
||||
Content = $"{ComponentsApi.BindMethods.GetValue}(",
|
||||
Kind = TokenKind.CSharp
|
||||
});
|
||||
valueExpressionTokens.Add(original);
|
||||
if (!string.IsNullOrEmpty(format?.Content))
|
||||
{
|
||||
valueExpressionTokens.Add(new IntermediateToken()
|
||||
{
|
||||
Content = ", ",
|
||||
Kind = TokenKind.CSharp,
|
||||
});
|
||||
valueExpressionTokens.Add(format);
|
||||
}
|
||||
valueExpressionTokens.Add(new IntermediateToken()
|
||||
{
|
||||
Content = ")",
|
||||
Kind = TokenKind.CSharp,
|
||||
});
|
||||
|
||||
// Now rewrite the content of the change-handler node. There are two cases we care about
|
||||
// here. If it's a component attribute, then don't use the 'BindMethods wrapper. We expect
|
||||
// component attributes to always 'match' on type.
|
||||
//
|
||||
// __value => <code> = __value
|
||||
//
|
||||
// For general DOM attributes, we need to be able to create a delegate that accepts UIEventArgs
|
||||
// so we use BindMethods.SetValueHandler
|
||||
//
|
||||
// BindMethods.SetValueHandler(__value => <code> = __value, <code>) OR
|
||||
// BindMethods.SetValueHandler(__value => <code> = __value, <code>, <format>)
|
||||
//
|
||||
// Note that the linemappings here are applied to the value attribute, not the change attribute.
|
||||
|
||||
string changeExpressionContent = null;
|
||||
if (changeAttribute == null && format == null)
|
||||
{
|
||||
changeExpressionContent = $"{ComponentsApi.BindMethods.SetValueHandler}(__value => {original.Content} = __value, {original.Content})";
|
||||
}
|
||||
else if (changeAttribute == null && format != null)
|
||||
{
|
||||
changeExpressionContent = $"{ComponentsApi.BindMethods.SetValueHandler}(__value => {original.Content} = __value, {original.Content}, {format.Content})";
|
||||
RewriteNodesForDelegateBind(
|
||||
original,
|
||||
format,
|
||||
valueAttribute,
|
||||
changeAttribute,
|
||||
valueExpressionTokens,
|
||||
changeExpressionTokens);
|
||||
}
|
||||
else
|
||||
{
|
||||
changeExpressionContent = $"__value => {original.Content} = __value";
|
||||
RewriteNodesForEventCallbackBind(
|
||||
original,
|
||||
format,
|
||||
valueAttribute,
|
||||
changeAttribute,
|
||||
valueExpressionTokens,
|
||||
changeExpressionTokens);
|
||||
}
|
||||
var changeExpressionTokens = new List<IntermediateToken>()
|
||||
{
|
||||
new IntermediateToken()
|
||||
{
|
||||
Content = changeExpressionContent,
|
||||
Kind = TokenKind.CSharp
|
||||
}
|
||||
};
|
||||
|
||||
if (parent is MarkupElementIntermediateNode)
|
||||
{
|
||||
|
|
@ -521,6 +485,151 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
|
|||
return false;
|
||||
}
|
||||
|
||||
private void RewriteNodesForDelegateBind(
|
||||
IntermediateToken original,
|
||||
IntermediateToken format,
|
||||
BoundAttributeDescriptor valueAttribute,
|
||||
BoundAttributeDescriptor changeAttribute,
|
||||
List<IntermediateToken> valueExpressionTokens,
|
||||
List<IntermediateToken> changeExpressionTokens)
|
||||
{
|
||||
// Now rewrite the content of the value node to look like:
|
||||
//
|
||||
// BindMethods.GetValue(<code>) OR
|
||||
// BindMethods.GetValue(<code>, <format>)
|
||||
valueExpressionTokens.Add(new IntermediateToken()
|
||||
{
|
||||
Content = $"{ComponentsApi.BindMethods.GetValue}(",
|
||||
Kind = TokenKind.CSharp
|
||||
});
|
||||
valueExpressionTokens.Add(original);
|
||||
if (!string.IsNullOrEmpty(format?.Content))
|
||||
{
|
||||
valueExpressionTokens.Add(new IntermediateToken()
|
||||
{
|
||||
Content = ", ",
|
||||
Kind = TokenKind.CSharp,
|
||||
});
|
||||
valueExpressionTokens.Add(format);
|
||||
}
|
||||
valueExpressionTokens.Add(new IntermediateToken()
|
||||
{
|
||||
Content = ")",
|
||||
Kind = TokenKind.CSharp,
|
||||
});
|
||||
|
||||
// Now rewrite the content of the change-handler node. There are two cases we care about
|
||||
// here. If it's a component attribute, then don't use the 'BindMethods' wrapper. We expect
|
||||
// component attributes to always 'match' on type.
|
||||
//
|
||||
// __value => <code> = __value
|
||||
//
|
||||
// For general DOM attributes, we need to be able to create a delegate that accepts UIEventArgs
|
||||
// so we use BindMethods.SetValueHandler
|
||||
//
|
||||
// BindMethods.SetValueHandler(__value => <code> = __value, <code>) OR
|
||||
// BindMethods.SetValueHandler(__value => <code> = __value, <code>, <format>)
|
||||
//
|
||||
// Note that the linemappings here are applied to the value attribute, not the change attribute.
|
||||
|
||||
string changeExpressionContent;
|
||||
if (changeAttribute == null && format == null)
|
||||
{
|
||||
// DOM
|
||||
changeExpressionContent = $"{ComponentsApi.BindMethods.SetValueHandler}(__value => {original.Content} = __value, {original.Content})";
|
||||
}
|
||||
else if (changeAttribute == null && format != null)
|
||||
{
|
||||
// DOM + format
|
||||
changeExpressionContent = $"{ComponentsApi.BindMethods.SetValueHandler}(__value => {original.Content} = __value, {original.Content}, {format.Content})";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Component
|
||||
changeExpressionContent = $"__value => {original.Content} = __value";
|
||||
}
|
||||
changeExpressionTokens.Add(new IntermediateToken()
|
||||
{
|
||||
Content = changeExpressionContent,
|
||||
Kind = TokenKind.CSharp
|
||||
});
|
||||
}
|
||||
|
||||
private void RewriteNodesForEventCallbackBind(
|
||||
IntermediateToken original,
|
||||
IntermediateToken format,
|
||||
BoundAttributeDescriptor valueAttribute,
|
||||
BoundAttributeDescriptor changeAttribute,
|
||||
List<IntermediateToken> valueExpressionTokens,
|
||||
List<IntermediateToken> changeExpressionTokens)
|
||||
{
|
||||
// Now rewrite the content of the value node to look like:
|
||||
//
|
||||
// BindMethods.GetValue(<code>) OR
|
||||
// BindMethods.GetValue(<code>, <format>)
|
||||
valueExpressionTokens.Add(new IntermediateToken()
|
||||
{
|
||||
Content = $"{ComponentsApi.BindMethods.GetValue}(",
|
||||
Kind = TokenKind.CSharp
|
||||
});
|
||||
valueExpressionTokens.Add(original);
|
||||
if (!string.IsNullOrEmpty(format?.Content))
|
||||
{
|
||||
valueExpressionTokens.Add(new IntermediateToken()
|
||||
{
|
||||
Content = ", ",
|
||||
Kind = TokenKind.CSharp,
|
||||
});
|
||||
valueExpressionTokens.Add(format);
|
||||
}
|
||||
valueExpressionTokens.Add(new IntermediateToken()
|
||||
{
|
||||
Content = ")",
|
||||
Kind = TokenKind.CSharp,
|
||||
});
|
||||
|
||||
// Now rewrite the content of the change-handler node. There are two cases we care about
|
||||
// here. If it's a component attribute, then don't use the 'CreateBinder' wrapper. We expect
|
||||
// component attributes to always 'match' on type.
|
||||
//
|
||||
// The really tricky part of this is that we CANNOT write the type name of of the EventCallback we
|
||||
// intend to create. Doing so would really complicate the story for how we deal with generic types,
|
||||
// since the generic type lowering pass runs after this. To keep this simple we're relying on
|
||||
// the compiler to resolve overloads for us.
|
||||
//
|
||||
// EventCallbackFactory.CreateInferred(this, __value => <code> = __value, <code>)
|
||||
//
|
||||
// For general DOM attributes, we need to be able to create a delegate that accepts UIEventArgs
|
||||
// so we use 'CreateBinder'
|
||||
//
|
||||
// EventCallbackFactory.CreateBinder(this, __value => <code> = __value, <code>) OR
|
||||
// EventCallbackFactory.CreateBinder(this, __value => <code> = __value, <code>, <format>)
|
||||
//
|
||||
// Note that the linemappings here are applied to the value attribute, not the change attribute.
|
||||
|
||||
string changeExpressionContent;
|
||||
if (changeAttribute == null && format == null)
|
||||
{
|
||||
// DOM
|
||||
changeExpressionContent = $"{ComponentsApi.EventCallback.FactoryAccessor}.{ComponentsApi.EventCallbackFactory.CreateBinderMethod}(this, __value => {original.Content} = __value, {original.Content})";
|
||||
}
|
||||
else if (changeAttribute == null && format != null)
|
||||
{
|
||||
// DOM + format
|
||||
changeExpressionContent = $"{ComponentsApi.EventCallback.FactoryAccessor}.{ComponentsApi.EventCallbackFactory.CreateBinderMethod}(this, __value => {original.Content} = __value, {original.Content}, {format.Content})";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Component
|
||||
changeExpressionContent = $"{ComponentsApi.EventCallback.FactoryAccessor}.{ComponentsApi.EventCallbackFactory.CreateInferredMethod}(this, __value => {original.Content} = __value, {original.Content})";
|
||||
}
|
||||
changeExpressionTokens.Add(new IntermediateToken()
|
||||
{
|
||||
Content = changeExpressionContent,
|
||||
Kind = TokenKind.CSharp
|
||||
});
|
||||
}
|
||||
|
||||
private static IntermediateToken GetAttributeContent(TagHelperPropertyIntermediateNode node)
|
||||
{
|
||||
var template = node.FindDescendantNodes<TemplateIntermediateNode>().FirstOrDefault();
|
||||
|
|
|
|||
|
|
@ -541,6 +541,55 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
|
|||
context.CodeWriter.Write(")");
|
||||
}
|
||||
}
|
||||
else if (node.BoundAttribute?.IsEventCallbackProperty() ?? false)
|
||||
{
|
||||
// This is the case where we are writing an EventCallback (a delegate with super-powers).
|
||||
//
|
||||
// An event callback can either be passed verbatim, or it can be created by the EventCallbackFactory.
|
||||
// Since we don't look at the code the user typed inside the attribute value, this is always
|
||||
// resolved via overloading.
|
||||
|
||||
if (canTypeCheck && NeedsTypeCheck(node))
|
||||
{
|
||||
context.CodeWriter.Write(ComponentsApi.RuntimeHelpers.TypeCheck);
|
||||
context.CodeWriter.Write("<");
|
||||
context.CodeWriter.Write(node.TypeName);
|
||||
context.CodeWriter.Write(">");
|
||||
context.CodeWriter.Write("(");
|
||||
}
|
||||
|
||||
// Microsoft.AspNetCore.Components.EventCallback.Factory.Create(this, ...) OR
|
||||
// Microsoft.AspNetCore.Components.EventCallback.Factory.Create<T>(this, ...)
|
||||
|
||||
context.CodeWriter.Write(ComponentsApi.EventCallback.FactoryAccessor);
|
||||
context.CodeWriter.Write(".");
|
||||
context.CodeWriter.Write(ComponentsApi.EventCallbackFactory.CreateMethod);
|
||||
|
||||
if (node.TryParseEventCallbackTypeArgument(out var argument))
|
||||
{
|
||||
context.CodeWriter.Write("<");
|
||||
context.CodeWriter.Write(argument);
|
||||
context.CodeWriter.Write(">");
|
||||
}
|
||||
|
||||
context.CodeWriter.Write("(");
|
||||
context.CodeWriter.Write("this");
|
||||
context.CodeWriter.Write(", ");
|
||||
|
||||
context.CodeWriter.WriteLine();
|
||||
|
||||
for (var i = 0; i < tokens.Count; i++)
|
||||
{
|
||||
WriteCSharpToken(context, tokens[i]);
|
||||
}
|
||||
|
||||
context.CodeWriter.Write(")");
|
||||
|
||||
if (canTypeCheck && NeedsTypeCheck(node))
|
||||
{
|
||||
context.CodeWriter.Write(")");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is the case when an attribute contains C# code
|
||||
|
|
|
|||
|
|
@ -123,16 +123,16 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
|
|||
|
||||
// Now rewrite the content of the value node to look like:
|
||||
//
|
||||
// BindMethods.GetEventHandlerValue<TDelegate>(<code>)
|
||||
// EventCallback.Factory.Create<T>(this, <code>)
|
||||
//
|
||||
// This method is overloaded on string and TDelegate, which means that it will put the code in the
|
||||
// This method is overloaded on string and T, which means that it will put the code in the
|
||||
// correct context for intellisense when typing in the attribute.
|
||||
var eventArgsType = node.TagHelper.GetEventArgsType();
|
||||
var tokens = new List<IntermediateToken>()
|
||||
{
|
||||
new IntermediateToken()
|
||||
{
|
||||
Content = $"{ComponentsApi.BindMethods.GetEventHandlerValue}<{eventArgsType}>(",
|
||||
Content = $"{ComponentsApi.EventCallback.FactoryAccessor}.{ComponentsApi.EventCallbackFactory.CreateMethod}<{eventArgsType}>(this, ",
|
||||
Kind = TokenKind.CSharp
|
||||
},
|
||||
new IntermediateToken()
|
||||
|
|
|
|||
|
|
@ -218,6 +218,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
|
|||
// This is a weakly typed delegate, treat it as Action<object>
|
||||
attribute.TypeName = "System.Action<System.Object>";
|
||||
}
|
||||
else if (attribute.TypeName == null && (attribute.BoundAttribute?.IsEventCallbackProperty() ?? false))
|
||||
{
|
||||
// This is a weakly typed event-callback, treat it as EventCallback (non-generic)
|
||||
attribute.TypeName = ComponentsApi.EventCallback.FullTypeName;
|
||||
}
|
||||
else if (attribute.TypeName == null)
|
||||
{
|
||||
// This is a weakly typed attribute, treat it as System.Object
|
||||
|
|
|
|||
|
|
@ -460,7 +460,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
|
|||
}
|
||||
else
|
||||
{
|
||||
// See comments in BlazorDesignTimeNodeWriter for a description of the cases that are possible.
|
||||
// See comments in ComponentDesignTimeNodeWriter for a description of the cases that are possible.
|
||||
var tokens = GetCSharpTokens(node);
|
||||
if ((node.BoundAttribute?.IsDelegateProperty() ?? false) ||
|
||||
(node.BoundAttribute?.IsChildContentProperty() ?? false))
|
||||
|
|
@ -482,6 +482,47 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
|
|||
context.CodeWriter.Write(")");
|
||||
}
|
||||
}
|
||||
else if (node.BoundAttribute?.IsEventCallbackProperty() ?? false)
|
||||
{
|
||||
if (canTypeCheck && NeedsTypeCheck(node))
|
||||
{
|
||||
context.CodeWriter.Write(ComponentsApi.RuntimeHelpers.TypeCheck);
|
||||
context.CodeWriter.Write("<");
|
||||
context.CodeWriter.Write(node.TypeName);
|
||||
context.CodeWriter.Write(">");
|
||||
context.CodeWriter.Write("(");
|
||||
}
|
||||
|
||||
// Microsoft.AspNetCore.Components.EventCallback.Factory.Create(this, ...) OR
|
||||
// Microsoft.AspNetCore.Components.EventCallback.Factory.Create<T>(this, ...)
|
||||
|
||||
context.CodeWriter.Write(ComponentsApi.EventCallback.FactoryAccessor);
|
||||
context.CodeWriter.Write(".");
|
||||
context.CodeWriter.Write(ComponentsApi.EventCallbackFactory.CreateMethod);
|
||||
|
||||
if (node.TryParseEventCallbackTypeArgument(out var argument))
|
||||
{
|
||||
context.CodeWriter.Write("<");
|
||||
context.CodeWriter.Write(argument);
|
||||
context.CodeWriter.Write(">");
|
||||
}
|
||||
|
||||
context.CodeWriter.Write("(");
|
||||
context.CodeWriter.Write("this");
|
||||
context.CodeWriter.Write(", ");
|
||||
|
||||
for (var i = 0; i < tokens.Count; i++)
|
||||
{
|
||||
context.CodeWriter.Write(tokens[i].Content);
|
||||
}
|
||||
|
||||
context.CodeWriter.Write(")");
|
||||
|
||||
if (canTypeCheck && NeedsTypeCheck(node))
|
||||
{
|
||||
context.CodeWriter.Write(")");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (canTypeCheck && NeedsTypeCheck(node))
|
||||
|
|
|
|||
|
|
@ -129,5 +129,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
|
|||
{
|
||||
public static readonly string FullTypeName = "Microsoft.AspNetCore.Components.ElementRef";
|
||||
}
|
||||
|
||||
public static class EventCallback
|
||||
{
|
||||
public static readonly string FullTypeName = "Microsoft.AspNetCore.Components.EventCallback";
|
||||
public static readonly string MetadataName = FullTypeName;
|
||||
|
||||
public static readonly string FactoryAccessor = FullTypeName + ".Factory";
|
||||
}
|
||||
|
||||
public static class EventCallbackOfT
|
||||
{
|
||||
public static readonly string MetadataName = "Microsoft.AspNetCore.Components.EventCallback`1";
|
||||
}
|
||||
|
||||
public static class EventCallbackFactory
|
||||
{
|
||||
public static readonly string CreateMethod = "Create";
|
||||
public static readonly string CreateInferredMethod = "CreateInferred";
|
||||
public static readonly string CreateBinderMethod = "CreateBinder";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
|
|||
string.Equals(value, bool.TrueString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the attribute is of type <c>EventCallback</c> or
|
||||
/// <c>EventCallback{T}</c>
|
||||
/// </summary>
|
||||
/// <param name="attribute">The <see cref="BoundAttributeDescriptor"/>.</param>
|
||||
/// <returns><c>true</c> if the attribute is an event callback, otherwise <c>false</c>.</returns>
|
||||
public static bool IsEventCallbackProperty(this BoundAttributeDescriptor attribute)
|
||||
{
|
||||
if (attribute == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attribute));
|
||||
}
|
||||
|
||||
var key = BlazorMetadata.Component.EventCallbackKey;
|
||||
return
|
||||
attribute.Metadata.TryGetValue(key, out var value) &&
|
||||
string.Equals(value, bool.TrueString);
|
||||
}
|
||||
|
||||
public static bool IsGenericTypedProperty(this BoundAttributeDescriptor attribute)
|
||||
{
|
||||
if (attribute == null)
|
||||
|
|
|
|||
|
|
@ -101,5 +101,42 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
|
|||
formatter.WriteProperty(nameof(TagHelper), TagHelper?.DisplayName);
|
||||
formatter.WriteProperty(nameof(TypeName), TypeName);
|
||||
}
|
||||
|
||||
public bool TryParseEventCallbackTypeArgument(out string argument)
|
||||
{
|
||||
// This is ugly and ad-hoc, but for various layering reasons we can't just use Roslyn APIs
|
||||
// to parse this. We need to parse this just before we write it out to the code generator,
|
||||
// so we can't compute it up front either.
|
||||
|
||||
if (BoundAttribute == null || !BoundAttribute.IsEventCallbackProperty())
|
||||
{
|
||||
throw new InvalidOperationException("This attribute is not an EventCallback attribute.");
|
||||
}
|
||||
|
||||
if (string.Equals(TypeName, ComponentsApi.EventCallback.FullTypeName, StringComparison.Ordinal))
|
||||
{
|
||||
// Non-Generic
|
||||
argument = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TypeName != null &&
|
||||
TypeName.Length > ComponentsApi.EventCallback.FullTypeName.Length + "<>".Length &&
|
||||
TypeName.StartsWith(ComponentsApi.EventCallback.FullTypeName, StringComparison.Ordinal) &&
|
||||
TypeName[ComponentsApi.EventCallback.FullTypeName.Length] == '<' &&
|
||||
TypeName[TypeName.Length - 1] == '>')
|
||||
{
|
||||
// OK this is promising.
|
||||
//
|
||||
// Chop off leading `...EventCallback<` and let the length so the ending `>` is cut off as well.
|
||||
argument = TypeName.Substring(ComponentsApi.EventCallback.FullTypeName.Length + 1, TypeName.Length - (ComponentsApi.EventCallback.FullTypeName.Length + "<>".Length));
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we get here this is a failure. This should only happen if someone manages to mangle the name with extensibility.
|
||||
// We don't really want to crash though.
|
||||
argument = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
// We generate:
|
||||
// <input type="text"
|
||||
// value="@BindMethods.GetValue(FirstName)"
|
||||
// onchange="@BindMethods.SetValue(__value => FirstName = __value, FirstName)"/>
|
||||
// onchange="@EventCallbackFactory.CreateBinder(this, __value => FirstName = __value, FirstName)"/>
|
||||
//
|
||||
// This isn't very different from code the user could write themselves - thus the pronouncement
|
||||
// that bind is very much like a macro.
|
||||
|
|
@ -356,7 +356,13 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
for (var i = 0; i < tagHelper.BoundAttributes.Count; i++)
|
||||
{
|
||||
var changeAttribute = tagHelper.BoundAttributes[i];
|
||||
if (!changeAttribute.Name.EndsWith("Changed") || !changeAttribute.IsDelegateProperty())
|
||||
if (!changeAttribute.Name.EndsWith("Changed") ||
|
||||
|
||||
// Allow the ValueChanged attribute to be a delegate or EventCallback<>.
|
||||
//
|
||||
// We assume that the Delegate or EventCallback<> has a matching type, and the C# compiler will help
|
||||
// you figure figure it out if you did it wrongly.
|
||||
(!changeAttribute.IsDelegateProperty() && !changeAttribute.IsEventCallbackProperty()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
|
@ -367,7 +373,7 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
var expressionAttributeName = valueAttributeName + "Expression";
|
||||
for (var j = 0; j < tagHelper.BoundAttributes.Count; j++)
|
||||
{
|
||||
if (tagHelper.BoundAttributes[j].Name == valueAttributeName && !tagHelper.BoundAttributes[j].IsDelegateProperty())
|
||||
if (tagHelper.BoundAttributes[j].Name == valueAttributeName)
|
||||
{
|
||||
valueAttribute = tagHelper.BoundAttributes[j];
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
// We need to see private members too
|
||||
compilation = WithMetadataImportOptionsAll(compilation);
|
||||
|
||||
var symbols = BlazorSymbols.Create(compilation);
|
||||
var symbols = ComponentSymbols.Create(compilation);
|
||||
|
||||
var types = new List<INamedTypeSymbol>();
|
||||
var visitor = new ComponentTypeVisitor(symbols, types);
|
||||
|
|
@ -81,7 +81,7 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
return compilation.WithOptions(newCompilationOptions);
|
||||
}
|
||||
|
||||
private TagHelperDescriptor CreateDescriptor(BlazorSymbols symbols, INamedTypeSymbol type)
|
||||
private TagHelperDescriptor CreateDescriptor(ComponentSymbols symbols, INamedTypeSymbol type)
|
||||
{
|
||||
var typeName = type.ToDisplayString(FullNameTypeDisplayFormat);
|
||||
var assemblyName = type.ContainingAssembly.Identity.Name;
|
||||
|
|
@ -157,6 +157,11 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
pb.Metadata.Add(BlazorMetadata.Component.ChildContentKey, bool.TrueString);
|
||||
}
|
||||
|
||||
if (kind == PropertyKind.EventCallback)
|
||||
{
|
||||
pb.Metadata.Add(BlazorMetadata.Component.EventCallbackKey, bool.TrueString);
|
||||
}
|
||||
|
||||
if (kind == PropertyKind.Delegate)
|
||||
{
|
||||
pb.Metadata.Add(BlazorMetadata.Component.DelegateSignatureKey, bool.TrueString);
|
||||
|
|
@ -230,7 +235,7 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
});
|
||||
}
|
||||
|
||||
private TagHelperDescriptor CreateChildContentDescriptor(BlazorSymbols symbols, TagHelperDescriptor component, BoundAttributeDescriptor attribute)
|
||||
private TagHelperDescriptor CreateChildContentDescriptor(ComponentSymbols symbols, TagHelperDescriptor component, BoundAttributeDescriptor attribute)
|
||||
{
|
||||
var typeName = component.GetTypeName() + "." + attribute.Name;
|
||||
var assemblyName = component.AssemblyName;
|
||||
|
|
@ -297,7 +302,7 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
// - have the [Parameter] attribute
|
||||
// - have a setter, even if private
|
||||
// - are not indexers
|
||||
private IEnumerable<(IPropertySymbol property, PropertyKind kind)> GetProperties(BlazorSymbols symbols, INamedTypeSymbol type)
|
||||
private IEnumerable<(IPropertySymbol property, PropertyKind kind)> GetProperties(ComponentSymbols symbols, INamedTypeSymbol type)
|
||||
{
|
||||
var properties = new Dictionary<string, (IPropertySymbol, PropertyKind)>(StringComparer.Ordinal);
|
||||
do
|
||||
|
|
@ -368,6 +373,19 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
kind = PropertyKind.ChildContent;
|
||||
}
|
||||
|
||||
if (kind == PropertyKind.Default && property.Type == symbols.EventCallback)
|
||||
{
|
||||
kind = PropertyKind.EventCallback;
|
||||
}
|
||||
|
||||
if (kind == PropertyKind.Default &&
|
||||
property.Type is INamedTypeSymbol namedType2 &&
|
||||
namedType2.IsGenericType &&
|
||||
namedType2.ConstructedFrom == symbols.EventCallbackOfT)
|
||||
{
|
||||
kind = PropertyKind.EventCallback;
|
||||
}
|
||||
|
||||
if (kind == PropertyKind.Default && property.Type.TypeKind == TypeKind.Delegate)
|
||||
{
|
||||
kind = PropertyKind.Delegate;
|
||||
|
|
@ -390,13 +408,18 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
Enum,
|
||||
ChildContent,
|
||||
Delegate,
|
||||
EventCallback,
|
||||
}
|
||||
|
||||
private class BlazorSymbols
|
||||
private class ComponentSymbols
|
||||
{
|
||||
public static BlazorSymbols Create(Compilation compilation)
|
||||
public static ComponentSymbols Create(Compilation compilation)
|
||||
{
|
||||
var symbols = new BlazorSymbols();
|
||||
// We find a bunch of important and fundamental types here that are needed to discover
|
||||
// components. If one of these isn't defined then we just bail, because the results will
|
||||
// be unpredictable.
|
||||
var symbols = new ComponentSymbols();
|
||||
|
||||
symbols.ComponentBase = compilation.GetTypeByMetadataName(ComponentsApi.ComponentBase.MetadataName);
|
||||
if (symbols.ComponentBase == null)
|
||||
{
|
||||
|
|
@ -422,18 +445,34 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
if (symbols.RenderFragment == null)
|
||||
{
|
||||
// No definition for RenderFragment, nothing to do.
|
||||
return null;
|
||||
}
|
||||
|
||||
symbols.RenderFragmentOfT = compilation.GetTypeByMetadataName(ComponentsApi.RenderFragmentOfT.MetadataName);
|
||||
if (symbols.RenderFragmentOfT == null)
|
||||
{
|
||||
// No definition for RenderFragment, nothing to do.
|
||||
// No definition for RenderFragment<T>, nothing to do.
|
||||
return null;
|
||||
}
|
||||
|
||||
symbols.EventCallback = compilation.GetTypeByMetadataName(ComponentsApi.EventCallback.MetadataName);
|
||||
if (symbols.EventCallback == null)
|
||||
{
|
||||
// No definition for EventCallback, nothing to do.
|
||||
return null;
|
||||
}
|
||||
|
||||
symbols.EventCallbackOfT = compilation.GetTypeByMetadataName(ComponentsApi.EventCallbackOfT.MetadataName);
|
||||
if (symbols.EventCallbackOfT == null)
|
||||
{
|
||||
// No definition for EventCallback<T>, nothing to do.
|
||||
return null;
|
||||
}
|
||||
|
||||
return symbols;
|
||||
}
|
||||
|
||||
private BlazorSymbols()
|
||||
private ComponentSymbols()
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -446,14 +485,18 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
public INamedTypeSymbol RenderFragment { get; private set; }
|
||||
|
||||
public INamedTypeSymbol RenderFragmentOfT { get; private set; }
|
||||
|
||||
public INamedTypeSymbol EventCallback { get; private set; }
|
||||
|
||||
public INamedTypeSymbol EventCallbackOfT { get; private set; }
|
||||
}
|
||||
|
||||
private class ComponentTypeVisitor : SymbolVisitor
|
||||
{
|
||||
private readonly BlazorSymbols _symbols;
|
||||
private readonly ComponentSymbols _symbols;
|
||||
private readonly List<INamedTypeSymbol> _results;
|
||||
|
||||
public ComponentTypeVisitor(BlazorSymbols symbols, List<INamedTypeSymbol> results)
|
||||
public ComponentTypeVisitor(ComponentSymbols symbols, List<INamedTypeSymbol> results)
|
||||
{
|
||||
_symbols = symbols;
|
||||
_results = results;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace Microsoft.CodeAnalysis.Razor
|
|||
public class BindTagHelperDescriptorProviderTest : BaseTagHelperDescriptorProviderTest
|
||||
{
|
||||
[Fact]
|
||||
public void Execute_FindsBindTagHelperOnComponentType_CreatesDescriptor()
|
||||
public void Execute_FindsBindTagHelperOnComponentType_Delegate_CreatesDescriptor()
|
||||
{
|
||||
// Arrange
|
||||
var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
|
||||
|
|
@ -130,6 +130,120 @@ namespace Test
|
|||
Assert.False(attribute.IsEnum);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_FindsBindTagHelperOnComponentType_EventCallback_CreatesDescriptor()
|
||||
{
|
||||
// Arrange
|
||||
var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : IComponent
|
||||
{
|
||||
public void Init(RenderHandle renderHandle) { }
|
||||
|
||||
public void SetParameters(ParameterCollection parameters) { }
|
||||
|
||||
[Parameter]
|
||||
string MyProperty { get; set; }
|
||||
|
||||
[Parameter]
|
||||
EventCallback<string> MyPropertyChanged { get; set; }
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
Assert.Empty(compilation.GetDiagnostics());
|
||||
|
||||
var context = TagHelperDescriptorProviderContext.Create();
|
||||
context.SetCompilation(compilation);
|
||||
|
||||
// We run after component discovery and depend on the results.
|
||||
var componentProvider = new ComponentTagHelperDescriptorProvider();
|
||||
componentProvider.Execute(context);
|
||||
|
||||
var provider = new BindTagHelperDescriptorProvider();
|
||||
|
||||
// Act
|
||||
provider.Execute(context);
|
||||
|
||||
// Assert
|
||||
var matches = GetBindTagHelpers(context);
|
||||
var bind = Assert.Single(matches);
|
||||
|
||||
// These are features Bind Tags Helpers don't use. Verifying them once here and
|
||||
// then ignoring them.
|
||||
Assert.Empty(bind.AllowedChildTags);
|
||||
Assert.Null(bind.TagOutputHint);
|
||||
|
||||
// These are features that are invariants of all Bind Tag Helpers. Verifying them once
|
||||
// here and then ignoring them.
|
||||
Assert.Empty(bind.Diagnostics);
|
||||
Assert.False(bind.HasErrors);
|
||||
Assert.Equal(BlazorMetadata.Bind.TagHelperKind, bind.Kind);
|
||||
Assert.Equal(BlazorMetadata.Bind.RuntimeName, bind.Metadata[TagHelperMetadata.Runtime.Name]);
|
||||
Assert.False(bind.IsDefaultKind());
|
||||
Assert.False(bind.KindUsesDefaultTagHelperRuntime());
|
||||
|
||||
Assert.Equal("MyProperty", bind.Metadata[BlazorMetadata.Bind.ValueAttribute]);
|
||||
Assert.Equal("MyPropertyChanged", bind.Metadata[BlazorMetadata.Bind.ChangeAttribute]);
|
||||
|
||||
Assert.Equal(
|
||||
"Binds the provided expression to the 'MyProperty' property and a change event " +
|
||||
"delegate to the 'MyPropertyChanged' property of the component.",
|
||||
bind.Documentation);
|
||||
|
||||
// These are all trivially derived from the assembly/namespace/type name
|
||||
Assert.Equal("TestAssembly", bind.AssemblyName);
|
||||
Assert.Equal("Test.MyComponent", bind.Name);
|
||||
Assert.Equal("Test.MyComponent", bind.DisplayName);
|
||||
Assert.Equal("Test.MyComponent", bind.GetTypeName());
|
||||
|
||||
var rule = Assert.Single(bind.TagMatchingRules);
|
||||
Assert.Empty(rule.Diagnostics);
|
||||
Assert.False(rule.HasErrors);
|
||||
Assert.Null(rule.ParentTag);
|
||||
Assert.Equal("MyComponent", rule.TagName);
|
||||
Assert.Equal(TagStructure.Unspecified, rule.TagStructure);
|
||||
|
||||
var requiredAttribute = Assert.Single(rule.Attributes);
|
||||
Assert.Empty(requiredAttribute.Diagnostics);
|
||||
Assert.Equal("bind-MyProperty", requiredAttribute.DisplayName);
|
||||
Assert.Equal("bind-MyProperty", requiredAttribute.Name);
|
||||
Assert.Equal(RequiredAttributeDescriptor.NameComparisonMode.FullMatch, requiredAttribute.NameComparison);
|
||||
Assert.Null(requiredAttribute.Value);
|
||||
Assert.Equal(RequiredAttributeDescriptor.ValueComparisonMode.None, requiredAttribute.ValueComparison);
|
||||
|
||||
var attribute = Assert.Single(bind.BoundAttributes);
|
||||
|
||||
// Invariants
|
||||
Assert.Empty(attribute.Diagnostics);
|
||||
Assert.False(attribute.HasErrors);
|
||||
Assert.Equal(BlazorMetadata.Bind.TagHelperKind, attribute.Kind);
|
||||
Assert.False(attribute.IsDefaultKind());
|
||||
Assert.False(attribute.HasIndexer);
|
||||
Assert.Null(attribute.IndexerNamePrefix);
|
||||
Assert.Null(attribute.IndexerTypeName);
|
||||
Assert.False(attribute.IsIndexerBooleanProperty);
|
||||
Assert.False(attribute.IsIndexerStringProperty);
|
||||
|
||||
Assert.Equal(
|
||||
"Binds the provided expression to the 'MyProperty' property and a change event " +
|
||||
"delegate to the 'MyPropertyChanged' property of the component.",
|
||||
attribute.Documentation);
|
||||
|
||||
Assert.Equal("bind-MyProperty", attribute.Name);
|
||||
Assert.Equal("MyProperty", attribute.GetPropertyName());
|
||||
Assert.Equal("string Test.MyComponent.MyProperty", attribute.DisplayName);
|
||||
|
||||
// Defined from the property type
|
||||
Assert.Equal("System.String", attribute.TypeName);
|
||||
Assert.True(attribute.IsStringProperty);
|
||||
Assert.False(attribute.IsBooleanProperty);
|
||||
Assert.False(attribute.IsEnum);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_NoMatchedPropertiesOnComponent_IgnoresComponent()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -577,6 +577,171 @@ namespace Test
|
|||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_EventCallbackProperty_CreatesDescriptor()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
EventCallback OnClick { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
"));
|
||||
|
||||
Assert.Empty(compilation.GetDiagnostics());
|
||||
|
||||
var context = TagHelperDescriptorProviderContext.Create();
|
||||
context.SetCompilation(compilation);
|
||||
|
||||
var provider = new ComponentTagHelperDescriptorProvider();
|
||||
|
||||
// Act
|
||||
provider.Execute(context);
|
||||
|
||||
// Assert
|
||||
var components = ExcludeBuiltInComponents(context);
|
||||
var component = Assert.Single(components);
|
||||
|
||||
Assert.Equal("TestAssembly", component.AssemblyName);
|
||||
Assert.Equal("Test.MyComponent", component.Name);
|
||||
|
||||
var attribute = Assert.Single(component.BoundAttributes);
|
||||
Assert.Equal("OnClick", attribute.Name);
|
||||
Assert.Equal("Microsoft.AspNetCore.Components.EventCallback", attribute.TypeName);
|
||||
|
||||
Assert.False(attribute.HasIndexer);
|
||||
Assert.False(attribute.IsBooleanProperty);
|
||||
Assert.False(attribute.IsEnum);
|
||||
Assert.False(attribute.IsStringProperty);
|
||||
Assert.True(attribute.IsEventCallbackProperty());
|
||||
Assert.False(attribute.IsDelegateProperty());
|
||||
Assert.False(attribute.IsChildContentProperty());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_EventCallbackProperty_CreatesDescriptor_ClosedGeneric()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
EventCallback<UIMouseEventArgs> OnClick { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
"));
|
||||
|
||||
Assert.Empty(compilation.GetDiagnostics());
|
||||
|
||||
var context = TagHelperDescriptorProviderContext.Create();
|
||||
context.SetCompilation(compilation);
|
||||
|
||||
var provider = new ComponentTagHelperDescriptorProvider();
|
||||
|
||||
// Act
|
||||
provider.Execute(context);
|
||||
|
||||
// Assert
|
||||
var components = ExcludeBuiltInComponents(context);
|
||||
var component = Assert.Single(components);
|
||||
|
||||
Assert.Equal("TestAssembly", component.AssemblyName);
|
||||
Assert.Equal("Test.MyComponent", component.Name);
|
||||
|
||||
Assert.Collection(
|
||||
component.BoundAttributes.OrderBy(a => a.Name),
|
||||
a =>
|
||||
{
|
||||
Assert.Equal("OnClick", a.Name);
|
||||
Assert.Equal("Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIMouseEventArgs>", a.TypeName);
|
||||
Assert.False(a.HasIndexer);
|
||||
Assert.False(a.IsBooleanProperty);
|
||||
Assert.False(a.IsEnum);
|
||||
Assert.False(a.IsStringProperty);
|
||||
Assert.True(a.IsEventCallbackProperty());
|
||||
Assert.False(a.IsDelegateProperty());
|
||||
Assert.False(a.IsChildContentProperty());
|
||||
Assert.False(a.IsGenericTypedProperty());
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_EventCallbackProperty_CreatesDescriptor_OpenGeneric()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
var compilation = BaseCompilation.AddSyntaxTrees(Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent<T> : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
EventCallback<T> OnClick { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
"));
|
||||
|
||||
Assert.Empty(compilation.GetDiagnostics());
|
||||
|
||||
var context = TagHelperDescriptorProviderContext.Create();
|
||||
context.SetCompilation(compilation);
|
||||
|
||||
var provider = new ComponentTagHelperDescriptorProvider();
|
||||
|
||||
// Act
|
||||
provider.Execute(context);
|
||||
|
||||
// Assert
|
||||
var components = ExcludeBuiltInComponents(context);
|
||||
var component = Assert.Single(components);
|
||||
|
||||
Assert.Equal("TestAssembly", component.AssemblyName);
|
||||
Assert.Equal("Test.MyComponent<T>", component.Name);
|
||||
|
||||
Assert.Collection(
|
||||
component.BoundAttributes.OrderBy(a => a.Name),
|
||||
a =>
|
||||
{
|
||||
Assert.Equal("OnClick", a.Name);
|
||||
Assert.Equal("Microsoft.AspNetCore.Components.EventCallback<T>", a.TypeName);
|
||||
Assert.False(a.HasIndexer);
|
||||
Assert.False(a.IsBooleanProperty);
|
||||
Assert.False(a.IsEnum);
|
||||
Assert.False(a.IsStringProperty);
|
||||
Assert.True(a.IsEventCallbackProperty());
|
||||
Assert.False(a.IsDelegateProperty());
|
||||
Assert.False(a.IsChildContentProperty());
|
||||
Assert.True(a.IsGenericTypedProperty());
|
||||
|
||||
},
|
||||
a =>
|
||||
{
|
||||
Assert.Equal("T", a.Name);
|
||||
Assert.Equal("T", a.GetPropertyName());
|
||||
Assert.Equal("T", a.DisplayName);
|
||||
Assert.Equal("System.Type", a.TypeName);
|
||||
Assert.True(a.IsTypeParameterProperty());
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_RenderFragmentProperty_CreatesDescriptors()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -69,6 +69,24 @@ namespace Microsoft.AspNetCore.Components
|
|||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Not intended to be used directly.
|
||||
/// </summary>
|
||||
public static EventCallback GetEventHandlerValue<T>(EventCallback value)
|
||||
where T : UIEventArgs
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Not intended to be used directly.
|
||||
/// </summary>
|
||||
public static EventCallback<T> GetEventHandlerValue<T>(EventCallback<T> value)
|
||||
where T : UIEventArgs
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Not intended to be used directly.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
public readonly struct EventCallback
|
||||
{
|
||||
public static readonly EventCallbackFactory Factory = new EventCallbackFactory();
|
||||
|
||||
internal readonly MulticastDelegate Delegate;
|
||||
internal readonly IHandleEvent Receiver;
|
||||
|
||||
public EventCallback(IHandleEvent receiver, MulticastDelegate @delegate)
|
||||
{
|
||||
Receiver = receiver;
|
||||
Delegate = @delegate;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct EventCallback<T>
|
||||
{
|
||||
internal readonly MulticastDelegate Delegate;
|
||||
internal readonly IHandleEvent Receiver;
|
||||
|
||||
public EventCallback(IHandleEvent receiver, MulticastDelegate @delegate)
|
||||
{
|
||||
Receiver = receiver;
|
||||
Delegate = @delegate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
public sealed class EventCallbackFactory
|
||||
{
|
||||
public EventCallback Create(object receiver, EventCallback callback) => default;
|
||||
|
||||
public EventCallback Create(object receiver, Action callback) => default;
|
||||
|
||||
public EventCallback Create(object receiver, Action<object> callback) => default;
|
||||
|
||||
public EventCallback Create(object receiver, Func<Task> callback) => default;
|
||||
|
||||
public EventCallback Create(object receiver, Func<object, Task> callback) => default;
|
||||
|
||||
public EventCallback<T> Create<T>(object receiver, string callback) => default;
|
||||
|
||||
public EventCallback<T> Create<T>(object receiver, EventCallback<T> callback) => default;
|
||||
|
||||
public EventCallback<T> Create<T>(object receiver, Action callback) => default;
|
||||
|
||||
public EventCallback<T> Create<T>(object receiver, Action<T> callback) => default;
|
||||
|
||||
public EventCallback<T> Create<T>(object receiver, Func<Task> callback) => default;
|
||||
|
||||
public EventCallback<T> Create<T>(object receiver, Func<T, Task> callback) => default;
|
||||
|
||||
public EventCallback<T> CreateInferred<T>(object receiver, Action<T> callback, T value)
|
||||
{
|
||||
return Create(receiver, callback);
|
||||
}
|
||||
|
||||
public EventCallback<T> CreateInferred<T>(object receiver, Func<T, Task> callback, T value)
|
||||
{
|
||||
return Create(receiver, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
public static class EventCallbackFactoryBinderExtensions
|
||||
{
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<string> setter,
|
||||
string existingValue) => default;
|
||||
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<bool> setter,
|
||||
bool existingValue) => default;
|
||||
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<bool?> setter,
|
||||
bool? existingValue) => default;
|
||||
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<int> setter,
|
||||
int existingValue) => default;
|
||||
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<int?> setter,
|
||||
int? existingValue) => default;
|
||||
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<long> setter,
|
||||
long existingValue) => default;
|
||||
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<long?> setter,
|
||||
long? existingValue) => default;
|
||||
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<float> setter,
|
||||
float existingValue) => default;
|
||||
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<float?> setter,
|
||||
float? existingValue) => default;
|
||||
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<double> setter,
|
||||
double existingValue) => default;
|
||||
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<double?> setter,
|
||||
double? existingValue) => default;
|
||||
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<decimal> setter,
|
||||
decimal existingValue) => default;
|
||||
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<decimal?> setter,
|
||||
decimal? existingValue) => default;
|
||||
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<DateTime> setter,
|
||||
DateTime existingValue) => default;
|
||||
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<DateTime> setter,
|
||||
DateTime existingValue,
|
||||
string format) => default;
|
||||
|
||||
public static EventCallback<UIChangeEventArgs> CreateBinder<T>(
|
||||
this EventCallbackFactory factory,
|
||||
object receiver,
|
||||
Action<T> setter,
|
||||
T existingValue) where T : Enum => default;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
public static class EventCallbackFactoryUIEventArgsExtensions
|
||||
{
|
||||
public static EventCallback<UIEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIEventArgs> callback) => default;
|
||||
|
||||
public static EventCallback<UIEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIEventArgs, Task> callback) => default;
|
||||
|
||||
public static EventCallback<UIChangeEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIChangeEventArgs> callback) => default;
|
||||
|
||||
public static EventCallback<UIChangeEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIChangeEventArgs, Task> callback) => default;
|
||||
|
||||
public static EventCallback<UIClipboardEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIClipboardEventArgs> callback) => default;
|
||||
|
||||
public static EventCallback<UIClipboardEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIClipboardEventArgs, Task> callback) => default;
|
||||
|
||||
public static EventCallback<UIDragEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIDragEventArgs> callback) => default;
|
||||
|
||||
public static EventCallback<UIDragEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIDragEventArgs, Task> callback) => default;
|
||||
|
||||
public static EventCallback<UIErrorEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIErrorEventArgs> callback) => default;
|
||||
|
||||
public static EventCallback<UIErrorEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIErrorEventArgs, Task> callback) => default;
|
||||
|
||||
public static EventCallback<UIFocusEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIFocusEventArgs> callback) => default;
|
||||
|
||||
public static EventCallback<UIFocusEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIFocusEventArgs, Task> callback) => default;
|
||||
|
||||
public static EventCallback<UIKeyboardEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIKeyboardEventArgs> callback) => default;
|
||||
|
||||
public static EventCallback<UIKeyboardEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIKeyboardEventArgs, Task> callback) => default;
|
||||
|
||||
public static EventCallback<UIMouseEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIMouseEventArgs> callback) => default;
|
||||
|
||||
public static EventCallback<UIMouseEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIMouseEventArgs, Task> callback) => default;
|
||||
|
||||
public static EventCallback<UIPointerEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIPointerEventArgs> callback) => default;
|
||||
|
||||
public static EventCallback<UIPointerEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIPointerEventArgs, Task> callback) => default;
|
||||
|
||||
public static EventCallback<UIProgressEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIProgressEventArgs> callback) => default;
|
||||
|
||||
public static EventCallback<UIProgressEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIProgressEventArgs, Task> callback) => default;
|
||||
|
||||
public static EventCallback<UITouchEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UITouchEventArgs> callback) => default;
|
||||
|
||||
public static EventCallback<UITouchEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UITouchEventArgs, Task> callback) => default;
|
||||
|
||||
public static EventCallback<UIWheelEventArgs> Create(this EventCallbackFactory factory, object receiver, Action<UIWheelEventArgs> callback) => default;
|
||||
|
||||
public static EventCallback<UIWheelEventArgs> Create(this EventCallbackFactory factory, object receiver, Func<UIWheelEventArgs, Task> callback) => default;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Components
|
||||
{
|
||||
public interface IHandleEvent
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -72,6 +72,14 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
{
|
||||
}
|
||||
|
||||
public void AddAttribute(int sequence, string name, EventCallback value)
|
||||
{
|
||||
}
|
||||
|
||||
public void AddAttribute<T>(int sequence, string name, EventCallback<T> value)
|
||||
{
|
||||
}
|
||||
|
||||
public void AddAttribute(int sequence, string name, object value)
|
||||
{
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue