Specifying entrypoint: Add tests and other stylistic tweaks

This commit is contained in:
Steve Sanderson 2018-02-19 14:22:03 +00:00
parent 1653e56b98
commit 608da4c78b
10 changed files with 53 additions and 54 deletions

View File

@ -5,15 +5,7 @@ using Microsoft.AspNetCore.Blazor.Browser.Rendering;
namespace StandaloneApp
{
public class ProgramX
{
public static void Main(string[] args)
{
new BrowserRenderer().AddComponent<Home>("app");
}
}
public class ProgramY
public class Program
{
public static void Main(string[] args)
{

View File

@ -6,7 +6,6 @@
<!-- Local alternative to <RunArguments>blazor serve</RunArguments> -->
<RunCommand>dotnet</RunCommand>
<RunArguments>run --project ../../src/Microsoft.AspNetCore.Blazor.DevHost serve</RunArguments>
<StartupObject>StandaloneApp.ProgramY</StartupObject>
</PropertyGroup>
<ItemGroup>

View File

@ -6,13 +6,9 @@ import './GlobalExports';
async function boot() {
// Read startup config from the <script> element that's importing this file
const allScriptElems = document.getElementsByTagName('script');
const thisScriptElem = document.currentScript || allScriptElems[allScriptElems.length - 1];
const entryPointDll = thisScriptElem.getAttribute('main');
if (!entryPointDll) {
throw new Error('Missing "main" attribute on Blazor script tag.');
}
// TODO: should method be a required, non-empty field or just an optional *hint*?
const entryPointMethod = thisScriptElem.getAttribute('entry-point');
const thisScriptElem = (document.currentScript || allScriptElems[allScriptElems.length - 1]) as HTMLScriptElement;
const entryPointDll = getRequiredBootScriptAttribute(thisScriptElem, 'main');
const entryPointMethod = getRequiredBootScriptAttribute(thisScriptElem, 'entrypoint');
const entryPointAssemblyName = getAssemblyNameFromUrl(entryPointDll);
const referenceAssembliesCommaSeparated = thisScriptElem.getAttribute('references') || '';
const referenceAssemblies = referenceAssembliesCommaSeparated
@ -35,4 +31,12 @@ async function boot() {
platform.callEntryPoint(entryPointAssemblyName, entryPointMethod, []);
}
function getRequiredBootScriptAttribute(elem: HTMLScriptElement, attributeName: string): string {
const result = elem.getAttribute(attributeName);
if (!result) {
throw new Error(`Missing "${attributeName}" attribute on Blazor script tag.`);
}
return result;
}
boot();

View File

@ -45,25 +45,21 @@ export const monoPlatform: Platform = {
return methodHandle;
},
callEntryPoint: function callEntryPoint(assemblyName: string, methodName: string | null, args: System_Object[]): void {
// TODO: There should be a proper way of running whatever counts as the entrypoint without
// having to specify what method it is, but I haven't found it. The code here assumes
// that the entry point is "<assemblyname>.Program.Main" (i.e., namespace == assembly name).
if (!methodName)
methodName = assemblyName + ".Program::Main";
callEntryPoint: function callEntryPoint(assemblyName: string, entrypointMethod: string, args: System_Object[]): void {
// Parse the entrypointMethod, which is of the form MyApp.MyNamespace.MyTypeName::MyMethodName
// Note that we don't support specifying a method overload, so it has to be unique
const entrypointSegments = entrypointMethod.split("::");
if (entrypointSegments.length != 2) {
throw new Error("malformed entry point method name; could not resolve class name and method name");
}
const typeFullName = entrypointSegments[0];
const methodName = entrypointSegments[1];
const lastDot = typeFullName.lastIndexOf('.');
const namespace = lastDot > -1 ? typeFullName.substring(0, lastDot) : '';
const typeShortName = lastDot > -1 ? typeFullName.substring(lastDot + 1) : typeFullName;
var classAndMethod = methodName.split("::");
if (classAndMethod.length != 2)
throw new Error("malformed entry point method name; could not resolve class name and method name");
methodName = classAndMethod[1];
const nsAndClass = classAndMethod[0];
const lastDot = nsAndClass.lastIndexOf(".");
// It's possible the entry point method has no namespace
const namespace = lastDot > -1 ? classAndMethod[0].substring(0, lastDot) : "";
const className = lastDot > -1 ? classAndMethod[0].substring(lastDot + 1) : classAndMethod[0];
const entryPointMethod = monoPlatform.findMethod(assemblyName, namespace, className, methodName);
monoPlatform.callMethod(entryPointMethod, null, args);
const entryPointMethodHandle = monoPlatform.findMethod(assemblyName, namespace, typeShortName, methodName);
monoPlatform.callMethod(entryPointMethodHandle, null, args);
},
callMethod: function callMethod(method: MethodHandle, target: System_Object, args: System_Object[]): System_Object {

View File

@ -1,7 +1,7 @@
export interface Platform {
start(loadAssemblyUrls: string[]): Promise<void>;
callEntryPoint(assemblyName: string, assemblyMethod: string | null, args: System_Object[]);
callEntryPoint(assemblyName: string, entrypointMethod: string, args: System_Object[]);
findMethod(assemblyName: string, namespace: string, className: string, methodName: string): MethodHandle;
callMethod(method: MethodHandle, target: System_Object | null, args: System_Object[]): System_Object;

View File

@ -4,6 +4,7 @@
using Microsoft.AspNetCore.Blazor.Internal.Common.FileProviders;
using Microsoft.Extensions.FileProviders;
using Mono.Cecil;
using System;
using System.Collections.Generic;
using System.IO;
@ -51,16 +52,8 @@ namespace Microsoft.AspNetCore.Blazor.Build.Core.FileSystem
{
var template = File.ReadAllText(path);
var assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
var assemblyEntryPoint = string.Empty;
var assemblyEntryPoint = GetAssemblyEntryPoint(assemblyPath);
var binFiles = frameworkFileProvider.GetDirectoryContents("/_bin");
using (var asmDef = AssemblyDefinition.ReadAssembly(assemblyPath))
{
var ep = asmDef.EntryPoint;
if (ep != null)
assemblyEntryPoint = $"{ep.DeclaringType.FullName}::{ep.Name}";
}
result = new IndexHtmlFileProvider(template, assemblyName, assemblyEntryPoint, binFiles);
return true;
}
@ -69,5 +62,19 @@ namespace Microsoft.AspNetCore.Blazor.Build.Core.FileSystem
result = null;
return false;
}
private static string GetAssemblyEntryPoint(string assemblyPath)
{
using (var assemblyDefinition = AssemblyDefinition.ReadAssembly(assemblyPath))
{
var entryPoint = assemblyDefinition.EntryPoint;
if (entryPoint == null)
{
throw new ArgumentException($"The assembly at {assemblyPath} has no specified entry point.");
}
return $"{entryPoint.DeclaringType.FullName}::{entryPoint.Name}";
}
}
}
}

View File

@ -149,7 +149,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Core.FileSystem
attributesDict.Remove("type");
attributesDict["src"] = "/_framework/blazor.js";
attributesDict["main"] = assemblyNameWithExtension;
attributesDict["entry-point"] = assemblyEntryPoint;
attributesDict["entrypoint"] = assemblyEntryPoint;
attributesDict["references"] = referencesAttribute;
resultBuilder.Append("<script");

View File

@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Blazor.Server.Test
{
// Arrange
var instance = new IndexHtmlFileProvider(
null, "fakeassembly", null, Enumerable.Empty<IFileInfo>());
null, "fakeassembly", "MyNamespace.MyType::MyMethod", Enumerable.Empty<IFileInfo>());
// Act
var file = instance.GetFileInfo("/index.html");
@ -33,7 +33,7 @@ namespace Microsoft.AspNetCore.Blazor.Server.Test
// Arrange
var htmlTemplate = "test";
var instance = new IndexHtmlFileProvider(
htmlTemplate, "fakeassembly", null, Enumerable.Empty<IFileInfo>());
htmlTemplate, "fakeassembly", "MyNamespace.MyType::MyMethod", Enumerable.Empty<IFileInfo>());
// Act
var file = instance.GetFileInfo("/index.html");
@ -52,7 +52,7 @@ namespace Microsoft.AspNetCore.Blazor.Server.Test
// Arrange
var htmlTemplate = "test";
var instance = new IndexHtmlFileProvider(
htmlTemplate, "fakeassembly", null, Enumerable.Empty<IFileInfo>());
htmlTemplate, "fakeassembly", "MyNamespace.MyType::MyMethod", Enumerable.Empty<IFileInfo>());
// Act
var directory = instance.GetDirectoryContents(string.Empty);
@ -86,7 +86,7 @@ namespace Microsoft.AspNetCore.Blazor.Server.Test
new TestFileInfo("MyApp.ClassLib.dll"),
};
var instance = new IndexHtmlFileProvider(
htmlTemplate, "MyApp.Entrypoint", null, dependencies);
htmlTemplate, "MyApp.Entrypoint", "MyNamespace.MyType::MyMethod", dependencies);
// Act
var file = instance.GetFileInfo("/index.html");
@ -103,6 +103,7 @@ namespace Microsoft.AspNetCore.Blazor.Server.Test
Assert.False(scriptElem.HasChildNodes);
Assert.Equal("/_framework/blazor.js", scriptElem.GetAttribute("src"));
Assert.Equal("MyApp.Entrypoint.dll", scriptElem.GetAttribute("main"));
Assert.Equal("MyNamespace.MyType::MyMethod", scriptElem.GetAttribute("entrypoint"));
Assert.Equal("System.Abc.dll,MyApp.ClassLib.dll", scriptElem.GetAttribute("references"));
Assert.False(scriptElem.HasAttribute("type"));
Assert.Equal(string.Empty, scriptElem.Attributes["custom1"].Value);
@ -120,7 +121,7 @@ namespace Microsoft.AspNetCore.Blazor.Server.Test
new TestFileInfo("MyApp.ClassLib.dll"),
};
var instance = new IndexHtmlFileProvider(
htmlTemplate, "MyApp.Entrypoint", null, dependencies);
htmlTemplate, "MyApp.Entrypoint", "MyNamespace.MyType::MyMethod", dependencies);
// Act
var file = instance.GetFileInfo("/index.html");

View File

@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Blazor.Server.Test
public void FindsReferencedAssemblyGraphRealistic()
{
// Arrange
var standaloneAppAssembly = typeof(StandaloneApp.ProgramY).Assembly;
var standaloneAppAssembly = typeof(StandaloneApp.Program).Assembly;
var provider = new ReferencedAssemblyFileProvider(
standaloneAppAssembly.GetName().Name,
new ReferencedAssemblyResolver(

View File

@ -11,9 +11,9 @@ using Xunit;
namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
{
public class StandaloneAppTest
: ServerTestBase<DevHostServerFixture<StandaloneApp.ProgramY>>
: ServerTestBase<DevHostServerFixture<StandaloneApp.Program>>
{
public StandaloneAppTest(BrowserFixture browserFixture, DevHostServerFixture<StandaloneApp.ProgramY> serverFixture)
public StandaloneAppTest(BrowserFixture browserFixture, DevHostServerFixture<StandaloneApp.Program> serverFixture)
: base(browserFixture, serverFixture)
{
Navigate("/", noReload: true);