Xaml empty service provider (#736)
authorStephane Delcroix <stephane@delcroix.org>
Fri, 10 Feb 2017 10:07:00 +0000 (11:07 +0100)
committerGitHub <noreply@github.com>
Fri, 10 Feb 2017 10:07:00 +0000 (11:07 +0100)
* [Xaml] AcceptEmptyServiceProvider

* [XamlC] AcceptEmptyServiceProvider

* docs

Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs
Xamarin.Forms.Core/IMarkupExtension.cs
Xamarin.Forms.Xaml.UnitTests/AcceptEmptyServiceProvider.xaml [new file with mode: 0644]
Xamarin.Forms.Xaml.UnitTests/AcceptEmptyServiceProvider.xaml.cs [new file with mode: 0644]
Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj
Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs
docs/Xamarin.Forms.Core/Xamarin.Forms.Xaml/AcceptEmptyServiceProviderAttribute.xml [new file with mode: 0644]
docs/Xamarin.Forms.Core/index.xml

index 64bc758..8b2a25f 100644 (file)
@@ -279,6 +279,7 @@ namespace Xamarin.Forms.Build.Tasks
                        else if (vardefref.VariableDefinition.VariableType.ImplementsGenericInterface("Xamarin.Forms.Xaml.IMarkupExtension`1",
                                out markupExtension, out genericArguments))
                        {
+                               var acceptEmptyServiceProvider = vardefref.VariableDefinition.VariableType.GetCustomAttribute(module.ImportReference(typeof(AcceptEmptyServiceProviderAttribute))) != null;
                                if (vardefref.VariableDefinition.VariableType.FullName == "Xamarin.Forms.Xaml.BindingExtension")
                                        foreach (var instruction in CompileBindingPath(node, context, vardefref.VariableDefinition))
                                                yield return instruction;
@@ -291,26 +292,34 @@ namespace Xamarin.Forms.Build.Tasks
 
                                vardefref.VariableDefinition = new VariableDefinition(module.ImportReference(genericArguments.First()));
                                yield return Instruction.Create(OpCodes.Ldloc, context.Variables[node]);
-                               foreach (var instruction in node.PushServiceProvider(context, bpRef, propertyRef, propertyDeclaringTypeRef))
-                                       yield return instruction;
+                               if (acceptEmptyServiceProvider)
+                                       yield return Instruction.Create(OpCodes.Ldnull);
+                               else
+                                       foreach (var instruction in node.PushServiceProvider(context, bpRef, propertyRef, propertyDeclaringTypeRef))
+                                               yield return instruction;
                                yield return Instruction.Create(OpCodes.Callvirt, provideValue);
                                yield return Instruction.Create(OpCodes.Stloc, vardefref.VariableDefinition);
                        }
                        else if (context.Variables[node].VariableType.ImplementsInterface(module.ImportReference(typeof (IMarkupExtension))))
                        {
+                               var acceptEmptyServiceProvider = context.Variables[node].VariableType.GetCustomAttribute(module.ImportReference(typeof(AcceptEmptyServiceProviderAttribute))) != null;
                                var markExt = module.ImportReference(typeof (IMarkupExtension)).Resolve();
                                var provideValueInfo = markExt.Methods.First(md => md.Name == "ProvideValue");
                                var provideValue = module.ImportReference(provideValueInfo);
 
                                vardefref.VariableDefinition = new VariableDefinition(module.TypeSystem.Object);
                                yield return Instruction.Create(OpCodes.Ldloc, context.Variables[node]);
-                               foreach (var instruction in node.PushServiceProvider(context, bpRef, propertyRef, propertyDeclaringTypeRef))
-                                       yield return instruction;
+                               if (acceptEmptyServiceProvider)
+                                       yield return Instruction.Create(OpCodes.Ldnull);
+                               else
+                                       foreach (var instruction in node.PushServiceProvider(context, bpRef, propertyRef, propertyDeclaringTypeRef))
+                                               yield return instruction;
                                yield return Instruction.Create(OpCodes.Callvirt, provideValue);
                                yield return Instruction.Create(OpCodes.Stloc, vardefref.VariableDefinition);
                        }
                        else if (context.Variables[node].VariableType.ImplementsInterface(module.ImportReference(typeof (IValueProvider))))
                        {
+                               var acceptEmptyServiceProvider = context.Variables[node].VariableType.GetCustomAttribute(module.ImportReference(typeof(AcceptEmptyServiceProviderAttribute))) != null;
                                var valueProviderType = context.Variables[node].VariableType;
                                //If the IValueProvider has a ProvideCompiledAttribute that can be resolved, shortcut this
                                var compiledValueProviderName = valueProviderType?.GetCustomAttribute(module.ImportReference(typeof(ProvideCompiledAttribute)))?.ConstructorArguments?[0].Value as string;
@@ -334,8 +343,11 @@ namespace Xamarin.Forms.Build.Tasks
 
                                vardefref.VariableDefinition = new VariableDefinition(module.TypeSystem.Object);
                                yield return Instruction.Create(OpCodes.Ldloc, context.Variables[node]);
-                               foreach (var instruction in node.PushServiceProvider(context, bpRef, propertyRef, propertyDeclaringTypeRef))
-                                       yield return instruction;
+                               if (acceptEmptyServiceProvider)
+                                       yield return Instruction.Create(OpCodes.Ldnull);
+                               else
+                                       foreach (var instruction in node.PushServiceProvider(context, bpRef, propertyRef, propertyDeclaringTypeRef))
+                                               yield return instruction;
                                yield return Instruction.Create(OpCodes.Callvirt, provideValue);
                                yield return Instruction.Create(OpCodes.Stloc, vardefref.VariableDefinition);
                        }
index 24a435f..2b32f38 100644 (file)
@@ -11,4 +11,9 @@ namespace Xamarin.Forms.Xaml
        {
                object ProvideValue(IServiceProvider serviceProvider);
        }
+
+       [AttributeUsage(AttributeTargets.Class, Inherited = false)]
+       public sealed class AcceptEmptyServiceProviderAttribute : Attribute
+       {
+       }
 }
\ No newline at end of file
diff --git a/Xamarin.Forms.Xaml.UnitTests/AcceptEmptyServiceProvider.xaml b/Xamarin.Forms.Xaml.UnitTests/AcceptEmptyServiceProvider.xaml
new file mode 100644 (file)
index 0000000..b39bd28
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
+       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+       xmlns:local="clr-namespace:Xamarin.Forms.Xaml.UnitTests"
+       x:Class="Xamarin.Forms.Xaml.UnitTests.AcceptEmptyServiceProvider" ServiceProvider="{local:Foo}">
+</ContentPage>
\ No newline at end of file
diff --git a/Xamarin.Forms.Xaml.UnitTests/AcceptEmptyServiceProvider.xaml.cs b/Xamarin.Forms.Xaml.UnitTests/AcceptEmptyServiceProvider.xaml.cs
new file mode 100644 (file)
index 0000000..611cb36
--- /dev/null
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using Xamarin.Forms;
+using Xamarin.Forms.Core.UnitTests;
+
+namespace Xamarin.Forms.Xaml.UnitTests
+{
+       [AcceptEmptyServiceProvider]
+       public class FooExtension : IMarkupExtension<IServiceProvider>
+       {
+               public IServiceProvider ProvideValue(IServiceProvider serviceProvider)
+               {
+                       return serviceProvider;
+               }
+
+               object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
+               {
+                       return (this as IMarkupExtension<IServiceProvider>).ProvideValue(serviceProvider);
+               }
+       }
+
+       public partial class AcceptEmptyServiceProvider : ContentPage
+       {
+               public AcceptEmptyServiceProvider()
+               {
+                       InitializeComponent();
+               }
+
+               public AcceptEmptyServiceProvider(bool useCompiledXaml)
+               {
+                       //this stub will be replaced at compile time
+               }
+
+               public IServiceProvider ServiceProvider { get; set; }
+
+               [TestFixture]
+               class Tests
+               {
+                       [SetUp]
+                       public void Setup()
+                       {
+                               Device.PlatformServices = new MockPlatformServices();
+                       }
+
+                       [TearDown]
+                       public void TearDown()
+                       {
+                               Device.PlatformServices = null;
+                       }
+
+                       [TestCase(true)]
+                       [TestCase(false)]
+                       public void ServiceProviderIsNullOnAttributedExtensions(bool useCompiledXaml)
+                       {
+                               var p = new AcceptEmptyServiceProvider(useCompiledXaml);
+                               Assert.IsNull(p.ServiceProvider);
+                       }
+               }
+       }
+}
index 4b3e5c8..90bab28 100644 (file)
     </Compile>
     <Compile Include="Issues\Bz44216.xaml.cs">
       <DependentUpon>Bz44216.xaml</DependentUpon>
+      <DependentUpon>AcceptEmptyServiceProvider.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="AcceptEmptyServiceProvider.xaml.cs">
+      <DependentUpon>AcceptEmptyServiceProvider.xaml</DependentUpon>
     </Compile>
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
     <EmbeddedResource Include="Issues\Bz44216.xaml">
       <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
     </EmbeddedResource>
+    <EmbeddedResource Include="AcceptEmptyServiceProvider.xaml">
+      <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
+    </EmbeddedResource>
   </ItemGroup>
   <ItemGroup>
     <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
index 833d5d9..5b8a840 100644 (file)
@@ -91,12 +91,12 @@ namespace Xamarin.Forms.Xaml
                        var valueProvider = value as IValueProvider;
 
                        if (markupExtension != null) {
-                               var serviceProvider = new XamlServiceProvider(node, Context);
+                               var serviceProvider = value.GetType().GetTypeInfo().GetCustomAttribute<AcceptEmptyServiceProviderAttribute>() == null ? new XamlServiceProvider(node, Context) : null;
                                value = markupExtension.ProvideValue(serviceProvider);
                        }
 
                        if (valueProvider != null) {
-                               var serviceProvider = new XamlServiceProvider(node, Context);
+                               var serviceProvider = value.GetType().GetTypeInfo().GetCustomAttribute<AcceptEmptyServiceProviderAttribute>() == null ? new XamlServiceProvider(node, Context) : null;
                                value = valueProvider.ProvideValue(serviceProvider);
                        }
 
diff --git a/docs/Xamarin.Forms.Core/Xamarin.Forms.Xaml/AcceptEmptyServiceProviderAttribute.xml b/docs/Xamarin.Forms.Core/Xamarin.Forms.Xaml/AcceptEmptyServiceProviderAttribute.xml
new file mode 100644 (file)
index 0000000..d95dffa
--- /dev/null
@@ -0,0 +1,36 @@
+<Type Name="AcceptEmptyServiceProviderAttribute" FullName="Xamarin.Forms.Xaml.AcceptEmptyServiceProviderAttribute">
+  <TypeSignature Language="C#" Value="public sealed class AcceptEmptyServiceProviderAttribute : Attribute" />
+  <TypeSignature Language="ILAsm" Value=".class public auto ansi sealed beforefieldinit AcceptEmptyServiceProviderAttribute extends System.Attribute" />
+  <AssemblyInfo>
+    <AssemblyName>Xamarin.Forms.Core</AssemblyName>
+    <AssemblyVersion>2.0.0.0</AssemblyVersion>
+  </AssemblyInfo>
+  <Base>
+    <BaseTypeName>System.Attribute</BaseTypeName>
+  </Base>
+  <Interfaces />
+  <Attributes>
+    <Attribute>
+      <AttributeName>System.AttributeUsage(System.AttributeTargets.Class, Inherited=false)</AttributeName>
+    </Attribute>
+  </Attributes>
+  <Docs>
+    <summary>To be added.</summary>
+    <remarks>To be added.</remarks>
+  </Docs>
+  <Members>
+    <Member MemberName=".ctor">
+      <MemberSignature Language="C#" Value="public AcceptEmptyServiceProviderAttribute ();" />
+      <MemberSignature Language="ILAsm" Value=".method public hidebysig specialname rtspecialname instance void .ctor() cil managed" />
+      <MemberType>Constructor</MemberType>
+      <AssemblyInfo>
+        <AssemblyVersion>2.0.0.0</AssemblyVersion>
+      </AssemblyInfo>
+      <Parameters />
+      <Docs>
+        <summary>To be added.</summary>
+        <remarks>To be added.</remarks>
+      </Docs>
+    </Member>
+  </Members>
+</Type>
index 89af75c..1b1cf31 100644 (file)
       <Type Name="ToolbarPlacement" Kind="Enumeration" />
     </Namespace>
     <Namespace Name="Xamarin.Forms.Xaml">
+      <Type Name="AcceptEmptyServiceProviderAttribute" Kind="Class" />
       <Type Name="IMarkupExtension" Kind="Interface" />
       <Type Name="IMarkupExtension`1" DisplayName="IMarkupExtension&lt;T&gt;" Kind="Interface" />
       <Type Name="IProvideValueTarget" Kind="Interface" />