Resource loading (#815)
authorStephane Delcroix <stephane@delcroix.org>
Thu, 16 Mar 2017 19:09:22 +0000 (20:09 +0100)
committerSamantha Houts <samantha@teamredwall.com>
Thu, 16 Mar 2017 19:09:22 +0000 (12:09 -0700)
* Resource Loader

* Replace XamlLoader API by ResourceLoader for the Previewer

* instruct generated IL to use the resourceLoader

* [docs] update docs

* oops

* [docs] fix docs

15 files changed:
Xamarin.Forms.Build.Tasks/XamlCTask.cs
Xamarin.Forms.Core/Internals/ResourceLoader.cs [new file with mode: 0644]
Xamarin.Forms.Core/Xamarin.Forms.Core.csproj
Xamarin.Forms.Xaml.UnitTests/DefaultCtorRouting2.xaml.cs
Xamarin.Forms.Xaml.UnitTests/XamlLoaderGetXamlForTypeTests.xaml.cs
Xamarin.Forms.Xaml.Xamlc/Xamarin.Forms.Xaml.Xamlc.csproj
Xamarin.Forms.Xaml.Xamlc/packages.config [new file with mode: 0644]
Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs
Xamarin.Forms.Xaml/HydratationContext.cs
Xamarin.Forms.Xaml/XamlFilePathAttribute.cs
Xamarin.Forms.Xaml/XamlLoader.cs
docs/Xamarin.Forms.Core/Xamarin.Forms.Internals/ResourceLoader.xml [new file with mode: 0644]
docs/Xamarin.Forms.Core/index.xml
docs/Xamarin.Forms.Xaml/Xamarin.Forms.Xaml.Internals/XamlLoader.xml
docs/Xamarin.Forms.Xaml/Xamarin.Forms.Xaml/XamlFilePathAttribute.xml

index c41ddd9..8bbb0b2 100644 (file)
@@ -176,7 +176,7 @@ namespace Xamarin.Forms.Build.Tasks
 
                                                Logger.LogString(2, "   Replacing {0}.InitializeComponent ()... ", typeDef.Name);
                                                Exception e;
-                                               if (!TryCoreCompile(initComp, initCompRuntime, rootnode, out e)) {
+                                               if (!TryCoreCompile(initComp, initCompRuntime, rootnode, resource.Name, out e)) {
                                                        success = false;
                                                        Logger.LogLine(2, "failed.");
                                                        (thrownExceptions = thrownExceptions ?? new List<Exception>()).Add(e);
@@ -195,6 +195,8 @@ namespace Xamarin.Forms.Build.Tasks
                                                        Logger.LogLine(2, "done");
                                                }
 
+                                               Logger.LogLine(2, "");
+
                                                if (outputGeneratedILAsCode)
                                                        Logger.LogLine(2, "   Decompiling option has been removed. Use a 3rd party decompiler to admire the beauty of the IL generated");
 
@@ -238,45 +240,59 @@ namespace Xamarin.Forms.Build.Tasks
                        return success;
                }
 
-               bool TryCoreCompile(MethodDefinition initComp, MethodDefinition initCompRuntime, ILRootNode rootnode, out Exception exception)
+               bool TryCoreCompile(MethodDefinition initComp, MethodDefinition initCompRuntime, ILRootNode rootnode, string resourceId, out Exception exception)
                {
                        try {
                                var body = new MethodBody(initComp);
+                               var module = body.Method.Module;
                                var il = body.GetILProcessor();
                                il.Emit(OpCodes.Nop);
 
                                if (initCompRuntime != null) {
                                        // Generating branching code for the Previewer
-                                       //      IL_0007:  call class [mscorlib]System.Func`2<class [mscorlib]System.Type,string> class [Xamarin.Forms.Xaml.Internals]Xamarin.Forms.Xaml.XamlLoader::get_XamlFileProvider()
-                                       //  IL_000c:  brfalse IL_0031
-                                       //  IL_0011:  call class [mscorlib]System.Func`2<class [mscorlib]System.Type,string> class [Xamarin.Forms.Xaml.Internals]Xamarin.Forms.Xaml.XamlLoader::get_XamlFileProvider()
-                                       //  IL_0016:  ldarg.0 
-                                       //  IL_0017:  call instance class [mscorlib]System.Type object::GetType()
-                                       //  IL_001c:  callvirt instance !1 class [mscorlib]System.Func`2<class [mscorlib]System.Type, string>::Invoke(!0)
-                                       //  IL_0021:  brfalse IL_0031
-                                       //  IL_0026:  ldarg.0 
-                                       //  IL_0027:  call instance void class Xamarin.Forms.Xaml.UnitTests.XamlLoaderGetXamlForTypeTests::__InitComponentRuntime()
-                                       //  IL_002c:  ret
-                                       //  IL_0031:  nop
 
+                                       //First using the ResourceLoader
                                        var nop = Instruction.Create(OpCodes.Nop);
+                                       var getResourceProvider = module.ImportReference(module.ImportReference(typeof(Internals.ResourceLoader))
+                                                        .Resolve()
+                                                        .Properties.FirstOrDefault(pd => pd.Name == "ResourceProvider")
+                                                        .GetMethod);
+                                       il.Emit(OpCodes.Call, getResourceProvider);
+                                       il.Emit(OpCodes.Brfalse, nop);
+                                       il.Emit(OpCodes.Call, getResourceProvider);
+                                       il.Emit(OpCodes.Ldstr, resourceId);
+                                       var func = module.ImportReference(module.ImportReference(typeof(Func<string, string>))
+                                                        .Resolve()
+                                                        .Methods.FirstOrDefault(md => md.Name == "Invoke"));
+                                       func = func.ResolveGenericParameters(module.ImportReference(typeof(Func<string, string>)), module);
+                                       il.Emit(OpCodes.Callvirt, func);
+                                       il.Emit(OpCodes.Brfalse, nop);
+                                       il.Emit(OpCodes.Ldarg_0);
+                                       il.Emit(OpCodes.Call, initCompRuntime);
+                                       il.Emit(OpCodes.Ret);
+                                       il.Append(nop);
 
-                                       var getXamlFileProvider = body.Method.Module.ImportReference(body.Method.Module.ImportReference(typeof(Xamarin.Forms.Xaml.Internals.XamlLoader))
+                                       //Or using the deprecated XamlLoader
+                                       nop = Instruction.Create(OpCodes.Nop);
+#pragma warning disable 0618
+                                       var getXamlFileProvider = module.ImportReference(module.ImportReference(typeof(Xaml.Internals.XamlLoader))
                                                        .Resolve()
                                                        .Properties.FirstOrDefault(pd => pd.Name == "XamlFileProvider")
                                                        .GetMethod);
+#pragma warning restore 0618
+
                                        il.Emit(OpCodes.Call, getXamlFileProvider);
                                        il.Emit(OpCodes.Brfalse, nop);
                                        il.Emit(OpCodes.Call, getXamlFileProvider);
                                        il.Emit(OpCodes.Ldarg_0);
-                                       var getType = body.Method.Module.ImportReference(body.Method.Module.ImportReference(typeof(object))
+                                       var getType = module.ImportReference(module.ImportReference(typeof(object))
                                                                          .Resolve()
                                                                          .Methods.FirstOrDefault(md => md.Name == "GetType"));
                                        il.Emit(OpCodes.Call, getType);
-                                       var func = body.Method.Module.ImportReference(body.Method.Module.ImportReference(typeof(Func<Type, string>))
+                                       func = module.ImportReference(module.ImportReference(typeof(Func<Type, string>))
                                                         .Resolve()
                                                         .Methods.FirstOrDefault(md => md.Name == "Invoke"));
-                                       func = func.ResolveGenericParameters(body.Method.Module.ImportReference(typeof(Func<Type, string>)), body.Method.Module);
+                                       func = func.ResolveGenericParameters(module.ImportReference(typeof(Func<Type, string>)), module);
                                        il.Emit(OpCodes.Callvirt, func);
                                        il.Emit(OpCodes.Brfalse, nop);
                                        il.Emit(OpCodes.Ldarg_0);
@@ -285,7 +301,7 @@ namespace Xamarin.Forms.Build.Tasks
                                        il.Append(nop);
                                }
 
-                               var visitorContext = new ILContext(il, body, body.Method.Module);
+                               var visitorContext = new ILContext(il, body, module);
 
                                rootnode.Accept(new XamlNodeVisitor((node, parent) => node.Parent = parent), null);
                                rootnode.Accept(new ExpandMarkupsVisitor(visitorContext), null);
diff --git a/Xamarin.Forms.Core/Internals/ResourceLoader.cs b/Xamarin.Forms.Core/Internals/ResourceLoader.cs
new file mode 100644 (file)
index 0000000..e7b1be1
--- /dev/null
@@ -0,0 +1,9 @@
+using System;
+namespace Xamarin.Forms.Internals
+{
+       public static class ResourceLoader
+       {
+               public static Func<string, string> ResourceProvider { get; internal set; }
+               internal static Action<Exception> ExceptionHandler { get; set; }
+       }
+}
\ No newline at end of file
index 5e001cd..a052352 100644 (file)
     <Compile Include="PlatformConfiguration\AndroidSpecific\ListView.cs" />
     <Compile Include="ITextElement.cs" />
     <Compile Include="TextElement.cs" />
+    <Compile Include="Internals\ResourceLoader.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
   <ItemGroup>
   </PropertyGroup>
   <ItemGroup />
   <ItemGroup />
-</Project>
\ No newline at end of file
+</Project>
index 0525477..faa9fed 100644 (file)
@@ -26,7 +26,10 @@ namespace Xamarin.Forms.Xaml.UnitTests
                        public void TearDown()
                        {
                                Device.PlatformServices = null;
+#pragma warning disable 0618
                                Internals.XamlLoader.XamlFileProvider = null;
+#pragma warning restore 0618
+
                        }
 
                        [Test]
@@ -39,7 +42,9 @@ namespace Xamarin.Forms.Xaml.UnitTests
                        [Test]
                        public void ShouldntBeCompiled()
                        {
+#pragma warning disable 0618
                                Internals.XamlLoader.XamlFileProvider = (t) => {
+#pragma warning restore 0618
                                        if (t == typeof(DefaultCtorRouting2))
                                                return @"<?xml version=""1.0"" encoding=""UTF-8""?>
 <ContentPage xmlns=""http://xamarin.com/schemas/2014/forms""
index 10c3f2e..dcffb3c 100644 (file)
@@ -25,7 +25,10 @@ namespace Xamarin.Forms.Xaml.UnitTests
                        [SetUp]
                        public void SetUp()
                        {
+#pragma warning disable 0618
                                Xamarin.Forms.Xaml.Internals.XamlLoader.XamlFileProvider = null;
+#pragma warning restore 0618
+
                        }
 
                        [TestCase(false)]
@@ -35,7 +38,9 @@ namespace Xamarin.Forms.Xaml.UnitTests
                                var layout = new XamlLoaderGetXamlForTypeTests(useCompiledXaml);
                                Assert.That(layout.Content, Is.TypeOf<Button>());
 
+#pragma warning disable 0618
                                Xamarin.Forms.Xaml.Internals.XamlLoader.XamlFileProvider = (t) => {
+#pragma warning restore 0618
                                        if (t == typeof(XamlLoaderGetXamlForTypeTests))
                                                return @"
        <ContentPage xmlns=""http://xamarin.com/schemas/2014/forms""
index 4430103..093e18d 100644 (file)
     <Reference Include="System" />
     <Reference Include="Microsoft.Build.Utilities.v4.0" />
     <Reference Include="Microsoft.Build.Framework" />
+    <Reference Include="Mono.Cecil">
+      <HintPath>..\packages\Mono.Cecil.0.10.0-beta4\lib\net40\Mono.Cecil.dll</HintPath>
+    </Reference>
+    <Reference Include="Mono.Cecil.Mdb">
+      <HintPath>..\packages\Mono.Cecil.0.10.0-beta4\lib\net40\Mono.Cecil.Mdb.dll</HintPath>
+    </Reference>
+    <Reference Include="Mono.Cecil.Pdb">
+      <HintPath>..\packages\Mono.Cecil.0.10.0-beta4\lib\net40\Mono.Cecil.Pdb.dll</HintPath>
+    </Reference>
+    <Reference Include="Mono.Cecil.Rocks">
+      <HintPath>..\packages\Mono.Cecil.0.10.0-beta4\lib\net40\Mono.Cecil.Rocks.dll</HintPath>
+    </Reference>
   </ItemGroup>
   <ItemGroup>
     <Compile Include="..\Xamarin.Forms.Xaml.Xamlg\Mono.Options\Options.cs">
@@ -59,4 +71,7 @@
       <Name>Xamarin.Forms.Build.Tasks</Name>
     </ProjectReference>
   </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
 </Project>
diff --git a/Xamarin.Forms.Xaml.Xamlc/packages.config b/Xamarin.Forms.Xaml.Xamlc/packages.config
new file mode 100644 (file)
index 0000000..809273b
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Mono.Cecil" version="0.10.0-beta4" targetFramework="net451" />
+</packages>
\ No newline at end of file
index 5b8a840..337b2ac 100644 (file)
@@ -305,8 +305,8 @@ namespace Xamarin.Forms.Xaml
                                return;
 
                        xpe = xpe ?? new XamlParseException($"Cannot assign property \"{localName}\": Property does not exists, or is not assignable, or mismatching type between value and property", lineInfo);
-                       if (context.DoNotThrowOnExceptions)
-                               System.Diagnostics.Debug.WriteLine(xpe.Message);
+                       if (context.ExceptionHandler != null)
+                               context.ExceptionHandler(xpe);
                        else
                                throw xpe;
                }
index 7273a2c..172bce1 100644 (file)
@@ -3,7 +3,7 @@ using System.Collections.Generic;
 
 namespace Xamarin.Forms.Xaml
 {
-       internal class HydratationContext
+       class HydratationContext
        {
                public HydratationContext()
                {
@@ -17,8 +17,8 @@ namespace Xamarin.Forms.Xaml
 
                public HydratationContext ParentContext { get; set; }
 
-               public bool DoNotThrowOnExceptions { get; set; }
+               public Action<Exception> ExceptionHandler { get; set; }
 
                public object RootElement { get; set; }
        }
-}
+}
\ No newline at end of file
index 615f290..7a6d665 100644 (file)
@@ -3,7 +3,7 @@ using System.Runtime.CompilerServices;
 
 namespace Xamarin.Forms.Xaml
 {
-       [AttributeUsage(AttributeTargets.Class, Inherited = false)]
+       [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
        public sealed class XamlFilePathAttribute : Attribute
        {
                public XamlFilePathAttribute([CallerFilePath] string filePath = "")
index 7fee40d..cdf73bd 100644 (file)
@@ -32,9 +32,11 @@ using System.IO;
 using System.Reflection;
 using System.Text.RegularExpressions;
 using System.Xml;
+using Xamarin.Forms.Internals;
 
 namespace Xamarin.Forms.Xaml.Internals
 {
+       [Obsolete ("Replaced by ResourceLoader")]
        public static class XamlLoader
        {
                public static Func<Type, string> XamlFileProvider { get; internal set; }
@@ -44,7 +46,7 @@ namespace Xamarin.Forms.Xaml.Internals
 
 namespace Xamarin.Forms.Xaml
 {
-       internal static class XamlLoader
+       static class XamlLoader
        {
                static readonly Dictionary<Type, string> XamlResources = new Dictionary<Type, string>();
 
@@ -75,7 +77,9 @@ namespace Xamarin.Forms.Xaml
                                        XamlParser.ParseXaml (rootnode, reader);
                                        Visit (rootnode, new HydratationContext {
                                                RootElement = view,
-                                               DoNotThrowOnExceptions = Xamarin.Forms.Xaml.Internals.XamlLoader.DoNotThrowOnExceptions
+#pragma warning disable 0618
+                                               ExceptionHandler = ResourceLoader.ExceptionHandler ?? (Internals.XamlLoader.DoNotThrowOnExceptions ? e => { }: (Action<Exception>)null)
+#pragma warning restore 0618
                                        });
                                        break;
                                }
@@ -99,7 +103,7 @@ namespace Xamarin.Forms.Xaml
                                        var rootnode = new RuntimeRootNode (new XmlType (reader.NamespaceURI, reader.Name, null), null, (IXmlNamespaceResolver)reader);
                                        XamlParser.ParseXaml (rootnode, reader);
                                        var visitorContext = new HydratationContext {
-                                               DoNotThrowOnExceptions = doNotThrow,
+                                               ExceptionHandler = doNotThrow ? e => { } : (Action<Exception>)null,
                                        };
                                        var cvv = new CreateValuesVisitor (visitorContext);
                                        cvv.Visit ((ElementNode)rootnode, null);
@@ -127,10 +131,14 @@ namespace Xamarin.Forms.Xaml
 
                static string GetXamlForType(Type type)
                {
-                       string xaml = null;
-
                        //the Previewer might want to provide it's own xaml for this... let them do that
-                       if (Xamarin.Forms.Xaml.Internals.XamlLoader.XamlFileProvider != null && (xaml = Xamarin.Forms.Xaml.Internals.XamlLoader.XamlFileProvider(type)) != null)
+                       //the check at the end is preferred (using ResourceLoader). keep this until all the previewers are updated
+
+#pragma warning disable 0618
+                       var xaml = Internals.XamlLoader.XamlFileProvider?.Invoke(type);
+#pragma warning restore 0618
+
+                       if (xaml != null && ResourceLoader.ResourceProvider == null)
                                return xaml;
 
                        var assembly = type.GetTypeInfo().Assembly;
@@ -189,7 +197,8 @@ namespace Xamarin.Forms.Xaml
                                return null;
 
                        XamlResources[type] = resourceName;
-                       return xaml;
+                       var alternateXaml = ResourceLoader.ResourceProvider?.Invoke(resourceName);
+                       return alternateXaml ?? xaml;
                }
 
                static bool ResourceMatchesFilename(Assembly assembly, string resource, string filename)
diff --git a/docs/Xamarin.Forms.Core/Xamarin.Forms.Internals/ResourceLoader.xml b/docs/Xamarin.Forms.Core/Xamarin.Forms.Internals/ResourceLoader.xml
new file mode 100644 (file)
index 0000000..58450ad
--- /dev/null
@@ -0,0 +1,34 @@
+<Type Name="ResourceLoader" FullName="Xamarin.Forms.Internals.ResourceLoader">
+  <TypeSignature Language="C#" Value="public static class ResourceLoader" />
+  <TypeSignature Language="ILAsm" Value=".class public auto ansi abstract sealed beforefieldinit ResourceLoader extends System.Object" />
+  <AssemblyInfo>
+    <AssemblyName>Xamarin.Forms.Core</AssemblyName>
+    <AssemblyVersion>2.0.0.0</AssemblyVersion>
+  </AssemblyInfo>
+  <Base>
+    <BaseTypeName>System.Object</BaseTypeName>
+  </Base>
+  <Interfaces />
+  <Docs>
+    <summary>To be added.</summary>
+    <remarks>To be added.</remarks>
+  </Docs>
+  <Members>
+    <Member MemberName="ResourceProvider">
+      <MemberSignature Language="C#" Value="public static Func&lt;string,string&gt; ResourceProvider { get; }" />
+      <MemberSignature Language="ILAsm" Value=".property class System.Func`2&lt;string, string&gt; ResourceProvider" />
+      <MemberType>Property</MemberType>
+      <AssemblyInfo>
+        <AssemblyVersion>2.0.0.0</AssemblyVersion>
+      </AssemblyInfo>
+      <ReturnValue>
+        <ReturnType>System.Func&lt;System.String,System.String&gt;</ReturnType>
+      </ReturnValue>
+      <Docs>
+        <summary>To be added.</summary>
+        <value>To be added.</value>
+        <remarks>To be added.</remarks>
+      </Docs>
+    </Member>
+  </Members>
+</Type>
index 32d7fe8..4d37119 100644 (file)
       <Type Name="ReflectionExtensions" Kind="Class" />
       <Type Name="Registrar" Kind="Class" />
       <Type Name="Registrar`1" DisplayName="Registrar&lt;TRegistrable&gt;" Kind="Class" />
+      <Type Name="ResourceLoader" Kind="Class" />
       <Type Name="ResourcesChangedEventArgs" Kind="Class" />
       <Type Name="SetValueFlags" Kind="Enumeration" />
       <Type Name="TableModel" Kind="Class" />
index 8c7c8cf..405f2cd 100644 (file)
@@ -9,6 +9,11 @@
     <BaseTypeName>System.Object</BaseTypeName>
   </Base>
   <Interfaces />
+  <Attributes>
+    <Attribute>
+      <AttributeName>System.Obsolete("Replaced by ResourceLoader")</AttributeName>
+    </Attribute>
+  </Attributes>
   <Docs>
     <summary>For internal use by the XAML platform.</summary>
     <remarks>To be added.</remarks>
index c0027df..ec83ddc 100644 (file)
@@ -11,7 +11,7 @@
   <Interfaces />
   <Attributes>
     <Attribute>
-      <AttributeName>System.AttributeUsage(System.AttributeTargets.Class, Inherited=false)</AttributeName>
+      <AttributeName>System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple=false, Inherited=false)</AttributeName>
     </Attribute>
   </Attributes>
   <Docs>