diff --git a/samples/ClientServerApp/ClientServerApp.Client/CounterDisplay.cshtml b/samples/ClientServerApp/ClientServerApp.Client/CounterDisplay.cshtml index 74a28e4e..05da120f 100644 --- a/samples/ClientServerApp/ClientServerApp.Client/CounterDisplay.cshtml +++ b/samples/ClientServerApp/ClientServerApp.Client/CounterDisplay.cshtml @@ -6,14 +6,10 @@ @functions { - // It would be cleaner if we could mark properties with [FromAttribute] or similar, - // instead of having to extract them from a dictionary manually. - private int currentCount; - private Action resetCounterCallback; - protected override void ReceiveParameters(IDictionary parameters) - { - currentCount = (int)parameters["current-count"]; - resetCounterCallback = (Action)parameters["on-reset"]; - } + [FromAttribute("current-count")] + private int currentCount { get; set; } //works with properties + + [FromAttribute("on-reset")] + private Action resetCounterCallback; //and fields } \ No newline at end of file diff --git a/samples/ClientServerApp/ClientServerApp.Client/_ViewImports.cshtml b/samples/ClientServerApp/ClientServerApp.Client/_ViewImports.cshtml index 4cbafd5f..7e7d0147 100644 --- a/samples/ClientServerApp/ClientServerApp.Client/_ViewImports.cshtml +++ b/samples/ClientServerApp/ClientServerApp.Client/_ViewImports.cshtml @@ -4,5 +4,6 @@ @using Task = Blazor.Runtime.FakeBcl.Task; // Temporary until Mono supports Task @using HttpClient = Blazor.Runtime.FakeBcl.HttpClient; // Temporary until Mono supports HttpClient @using ClientServerApp.Client +@using Blazor.Runtime.Components @using ClientServerApp.Shared @using System.Net.Http diff --git a/src/Blazor.Runtime/Components/FromAttributeAttribute.cs b/src/Blazor.Runtime/Components/FromAttributeAttribute.cs new file mode 100644 index 00000000..e8ecc3f5 --- /dev/null +++ b/src/Blazor.Runtime/Components/FromAttributeAttribute.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Blazor.Runtime.Components +{ + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public class FromAttributeAttribute : Attribute + { + public string AttributeName {get; set;} + public bool IsOptional { get; set; } + + public FromAttributeAttribute(string attributeName) + { + AttributeName = attributeName; + } + + public FromAttributeAttribute(string attributeName, bool isOptional) + { + AttributeName = attributeName; + IsOptional = isOptional; + } + } +} diff --git a/src/Blazor.Runtime/Components/RazorComponent.cs b/src/Blazor.Runtime/Components/RazorComponent.cs index f22f32e4..c86f5d78 100644 --- a/src/Blazor.Runtime/Components/RazorComponent.cs +++ b/src/Blazor.Runtime/Components/RazorComponent.cs @@ -5,6 +5,7 @@ using System.Linq; using Blazor.Routing; using Blazor.Runtime.Components; +using System.Reflection; using Blazor.Runtime.Interop; namespace Blazor.Components @@ -56,9 +57,45 @@ protected override void RenderVirtualDom() // don't have to be marked abstract. } + private static Dictionary> ParameterMembersCache = new Dictionary>(); + private IEnumerable GetComponentParameterMembers() + { + if (ParameterMembersCache.ContainsKey(this.GetType())) + { + return ParameterMembersCache[this.GetType()]; + } + + var decoratedMembers = new List(); + + + decoratedMembers.AddRange(this.GetType().GetTypeInfo().DeclaredFields.Where(x => x.GetCustomAttribute(typeof(FromAttributeAttribute), true) != null)); + decoratedMembers.AddRange(this.GetType().GetTypeInfo().DeclaredProperties.Where(x => x.GetCustomAttribute(typeof(FromAttributeAttribute),true) != null)); + + ParameterMembersCache.Add(this.GetType(), decoratedMembers); + return decoratedMembers; + } + protected override void ReceiveParameters(IDictionary parameters) { - // Subclasses may optionally override this + var decoratedMembers = GetComponentParameterMembers(); + + foreach (var member in decoratedMembers) + { + + var attribute = (FromAttributeAttribute)member.GetCustomAttributes().First(x => x.GetType() == typeof(FromAttributeAttribute)); + if (!parameters.ContainsKey(attribute.AttributeName) && !attribute.IsOptional) + { + throw new Exception($"Parameter '{attribute.AttributeName}' is required"); + } + + var parameterValue = parameters[attribute.AttributeName]; + + + if(member is FieldInfo) + ((FieldInfo)member).SetValue(this, parameterValue); + else + ((PropertyInfo)member).SetValue(this, parameterValue); + } } private static Type GetTypeForCompiledRazorFile(string cshtmlFilename) @@ -102,7 +139,7 @@ protected VDomAttribute onclick(Action callback) }; } - protected VDomAttribute onclick(Action callback) + protected VDomAttribute onclick(Action callback) { return new VDomAttribute { @@ -120,7 +157,7 @@ protected VDomAttribute onclickAsync(Func callback) }; } - protected VDomAttribute onclickAsync(Func callback) + protected VDomAttribute onclickAsync(Func callback) { return new VDomAttribute { @@ -129,7 +166,7 @@ protected VDomAttribute onclickAsync(Func callback) }; } - protected VDomAttribute onchange(Action callback) + protected VDomAttribute onchange(Action callback) { return new VDomAttribute {