Merge branch 'blazor-wasm' into blazor-wasm-preview5

This commit is contained in:
Pranav K 2020-04-10 21:49:10 -07:00 committed by GitHub
commit cc0407877f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 757 additions and 343 deletions

2
.github/CODEOWNERS vendored
View File

@ -9,7 +9,7 @@
/eng/common/ @dotnet-maestro-bot
/eng/Versions.props @dotnet-maestro-bot @dougbu
/eng/Version.Details.xml @dotnet-maestro-bot @dougbu
/src/Components/ @SteveSandersonMS
/src/Components/ @SteveSandersonMS @aspnet-blazor-eng
/src/DefaultBuilder/ @tratcher @anurse
/src/Hosting/ @tratcher @anurse
/src/Http/ @tratcher @jkotalik @anurse

View File

@ -9,13 +9,13 @@
-->
<Dependencies>
<ProductDependencies>
<Dependency Name="Microsoft.AspNetCore.Components.WebAssembly.Runtime" Version="3.2.0-preview4.20205.1">
<Dependency Name="Microsoft.AspNetCore.Components.WebAssembly.Runtime" Version="3.2.0-preview5.20210.1">
<Uri>https://github.com/dotnet/blazor</Uri>
<Sha>eef56fe506c48f78ecd88894aac74eacfe524f80</Sha>
<Sha>7659b5fda2406d854c72436bdc24f1ff958995f8</Sha>
</Dependency>
<Dependency Name="System.Net.Http.Json" Version="3.2.0-preview4.20177.1">
<Dependency Name="System.Net.Http.Json" Version="3.2.0-preview5.20210.3">
<Uri>https://github.com/dotnet/corefx</Uri>
<Sha>b3f1f8f7f3c6549bd7f632bb948835f785835548</Sha>
<Sha>557f293f3469cb64a4f3d06b6941b387e44df2d2</Sha>
</Dependency>
</ProductDependencies>
<ToolsetDependencies>

View File

@ -93,13 +93,13 @@
<SystemServiceProcessServiceControllerPackageVersion>4.7.0</SystemServiceProcessServiceControllerPackageVersion>
<SystemTextEncodingsWebPackageVersion>4.7.0</SystemTextEncodingsWebPackageVersion>
<SystemTextJsonPackageVersion>4.7.1</SystemTextJsonPackageVersion>
<SystemNetHttpJsonPackageVersion>3.2.0-preview4.20177.1</SystemNetHttpJsonPackageVersion>
<SystemNetHttpJsonPackageVersion>3.2.0-preview5.20210.3</SystemNetHttpJsonPackageVersion>
<SystemThreadingChannelsPackageVersion>4.7.0</SystemThreadingChannelsPackageVersion>
<SystemWindowsExtensionsPackageVersion>4.7.0</SystemWindowsExtensionsPackageVersion>
<!-- Only listed explicitly to workaround https://github.com/dotnet/cli/issues/10528 -->
<MicrosoftNETCorePlatformsPackageVersion>3.1.0</MicrosoftNETCorePlatformsPackageVersion>
<!-- Packages from aspnet/Blazor -->
<MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion>3.2.0-preview4.20205.1</MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion>
<MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion>3.2.0-preview5.20210.1</MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion>
<!-- Packages from aspnet/Extensions -->
<InternalAspNetCoreAnalyzersPackageVersion>3.1.3-servicing.20128.2</InternalAspNetCoreAnalyzersPackageVersion>
<MicrosoftAspNetCoreAnalyzerTestingPackageVersion>3.1.3-servicing.20128.2</MicrosoftAspNetCoreAnalyzerTestingPackageVersion>

View File

@ -22,7 +22,7 @@ namespace Microsoft.Extensions.DependencyInjection
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <param name="configure">A callback to configure the <see cref="RemoteAuthenticationOptions{MsalProviderOptions}"/>.</param>
/// <returns>The <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddMsalAuthentication(this IServiceCollection services, Action<RemoteAuthenticationOptions<MsalProviderOptions>> configure)
public static IRemoteAuthenticationBuilder<RemoteAuthenticationState, RemoteUserAccount> AddMsalAuthentication(this IServiceCollection services, Action<RemoteAuthenticationOptions<MsalProviderOptions>> configure)
{
return AddMsalAuthentication<RemoteAuthenticationState>(services, configure);
}
@ -34,11 +34,10 @@ namespace Microsoft.Extensions.DependencyInjection
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <param name="configure">A callback to configure the <see cref="RemoteAuthenticationOptions{MsalProviderOptions}"/>.</param>
/// <returns>The <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddMsalAuthentication<TRemoteAuthenticationState>(this IServiceCollection services, Action<RemoteAuthenticationOptions<MsalProviderOptions>> configure)
public static IRemoteAuthenticationBuilder<TRemoteAuthenticationState, RemoteUserAccount> AddMsalAuthentication<TRemoteAuthenticationState>(this IServiceCollection services, Action<RemoteAuthenticationOptions<MsalProviderOptions>> configure)
where TRemoteAuthenticationState : RemoteAuthenticationState, new()
{
AddMsalAuthentication<TRemoteAuthenticationState, RemoteUserAccount>(services, configure);
return services;
return AddMsalAuthentication<TRemoteAuthenticationState, RemoteUserAccount>(services, configure);
}
/// <summary>
@ -49,14 +48,24 @@ namespace Microsoft.Extensions.DependencyInjection
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <param name="configure">A callback to configure the <see cref="RemoteAuthenticationOptions{MsalProviderOptions}"/>.</param>
/// <returns>The <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddMsalAuthentication<TRemoteAuthenticationState, TAccount>(this IServiceCollection services, Action<RemoteAuthenticationOptions<MsalProviderOptions>> configure)
public static IRemoteAuthenticationBuilder<TRemoteAuthenticationState, TAccount> AddMsalAuthentication<TRemoteAuthenticationState, TAccount>(this IServiceCollection services, Action<RemoteAuthenticationOptions<MsalProviderOptions>> configure)
where TRemoteAuthenticationState : RemoteAuthenticationState, new()
where TAccount : RemoteUserAccount
{
services.AddRemoteAuthentication<RemoteAuthenticationState, TAccount, MsalProviderOptions>(configure);
services.AddRemoteAuthentication<TRemoteAuthenticationState, TAccount, MsalProviderOptions>(configure);
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<RemoteAuthenticationOptions<MsalProviderOptions>>, MsalDefaultOptionsConfiguration>());
return services;
return new MsalRemoteAuthenticationBuilder<TRemoteAuthenticationState, TAccount>(services);
}
}
internal class MsalRemoteAuthenticationBuilder<TRemoteAuthenticationState, TRemoteUserAccount> : IRemoteAuthenticationBuilder<TRemoteAuthenticationState, TRemoteUserAccount>
where TRemoteAuthenticationState : RemoteAuthenticationState, new()
where TRemoteUserAccount : RemoteUserAccount
{
public MsalRemoteAuthenticationBuilder(IServiceCollection services) => Services = services;
public IServiceCollection Services { get; }
}
}

View File

@ -21,6 +21,7 @@
<!-- Dependencies of ws-proxy sources -->
<Reference Include="Newtonsoft.Json" />
<Reference Include="Mono.Cecil" />
<Reference Include="Microsoft.CodeAnalysis.CSharp" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,256 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using System.Threading;
using System.IO;
using System.Collections.Generic;
using System.Net;
using Microsoft.Extensions.Logging;
namespace WebAssembly.Net.Debugging {
internal struct SessionId {
public readonly string sessionId;
public SessionId (string sessionId)
{
this.sessionId = sessionId;
}
public override int GetHashCode ()
=> sessionId?.GetHashCode () ?? 0;
public override bool Equals (object obj)
=> (obj is SessionId) ? ((SessionId) obj).sessionId == sessionId : false;
public override string ToString ()
=> $"session-{sessionId}";
}
internal struct MessageId {
public readonly string sessionId;
public readonly int id;
public MessageId (string sessionId, int id)
{
this.sessionId = sessionId;
this.id = id;
}
public static implicit operator SessionId (MessageId id)
=> new SessionId (id.sessionId);
public override string ToString ()
=> $"msg-{sessionId}:::{id}";
public override int GetHashCode ()
=> (sessionId?.GetHashCode () ?? 0) ^ id.GetHashCode ();
public override bool Equals (object obj)
=> (obj is MessageId) ? ((MessageId) obj).sessionId == sessionId && ((MessageId) obj).id == id : false;
}
internal struct Result {
public JObject Value { get; private set; }
public JObject Error { get; private set; }
public bool IsOk => Value != null;
public bool IsErr => Error != null;
Result (JObject result, JObject error)
{
if (result != null && error != null)
throw new ArgumentException ($"Both {nameof(result)} and {nameof(error)} arguments cannot be non-null.");
bool resultHasError = String.Compare ((result? ["result"] as JObject)? ["subtype"]?. Value<string> (), "error") == 0;
if (result != null && resultHasError) {
this.Value = null;
this.Error = result;
} else {
this.Value = result;
this.Error = error;
}
}
public static Result FromJson (JObject obj)
{
//Log ("protocol", $"from result: {obj}");
return new Result (obj ["result"] as JObject, obj ["error"] as JObject);
}
public static Result Ok (JObject ok)
=> new Result (ok, null);
public static Result OkFromObject (object ok)
=> Ok (JObject.FromObject(ok));
public static Result Err (JObject err)
=> new Result (null, err);
public static Result Exception (Exception e)
=> new Result (null, JObject.FromObject (new { message = e.Message }));
public JObject ToJObject (MessageId target) {
if (IsOk) {
return JObject.FromObject (new {
target.id,
target.sessionId,
result = Value
});
} else {
return JObject.FromObject (new {
target.id,
target.sessionId,
error = Error
});
}
}
public override string ToString ()
{
return $"[Result: IsOk: {IsOk}, IsErr: {IsErr}, Value: {Value?.ToString ()}, Error: {Error?.ToString ()} ]";
}
}
internal class MonoCommands {
public string expression { get; set; }
public string objectGroup { get; set; } = "mono-debugger";
public bool includeCommandLineAPI { get; set; } = false;
public bool silent { get; set; } = false;
public bool returnByValue { get; set; } = true;
public MonoCommands (string expression)
=> this.expression = expression;
public static MonoCommands GetCallStack ()
=> new MonoCommands ("MONO.mono_wasm_get_call_stack()");
public static MonoCommands IsRuntimeReady ()
=> new MonoCommands ("MONO.mono_wasm_runtime_is_ready");
public static MonoCommands StartSingleStepping (StepKind kind)
=> new MonoCommands ($"MONO.mono_wasm_start_single_stepping ({(int)kind})");
public static MonoCommands GetLoadedFiles ()
=> new MonoCommands ("MONO.mono_wasm_get_loaded_files()");
public static MonoCommands ClearAllBreakpoints ()
=> new MonoCommands ("MONO.mono_wasm_clear_all_breakpoints()");
public static MonoCommands GetObjectProperties (int objectId, bool expandValueTypes)
=> new MonoCommands ($"MONO.mono_wasm_get_object_properties({objectId}, { (expandValueTypes ? "true" : "false") })");
public static MonoCommands GetArrayValues (int objectId)
=> new MonoCommands ($"MONO.mono_wasm_get_array_values({objectId})");
public static MonoCommands GetArrayValueExpanded (int objectId, int idx)
=> new MonoCommands ($"MONO.mono_wasm_get_array_value_expanded({objectId}, {idx})");
public static MonoCommands GetScopeVariables (int scopeId, params int[] vars)
=> new MonoCommands ($"MONO.mono_wasm_get_variables({scopeId}, [ {string.Join (",", vars)} ])");
public static MonoCommands SetBreakpoint (string assemblyName, int methodToken, int ilOffset)
=> new MonoCommands ($"MONO.mono_wasm_set_breakpoint (\"{assemblyName}\", {methodToken}, {ilOffset})");
public static MonoCommands RemoveBreakpoint (int breakpointId)
=> new MonoCommands ($"MONO.mono_wasm_remove_breakpoint({breakpointId})");
}
internal enum MonoErrorCodes {
BpNotFound = 100000,
}
internal class MonoConstants {
public const string RUNTIME_IS_READY = "mono_wasm_runtime_ready";
}
class Frame {
public Frame (MethodInfo method, SourceLocation location, int id)
{
this.Method = method;
this.Location = location;
this.Id = id;
}
public MethodInfo Method { get; private set; }
public SourceLocation Location { get; private set; }
public int Id { get; private set; }
}
class Breakpoint {
public SourceLocation Location { get; private set; }
public int RemoteId { get; set; }
public BreakpointState State { get; set; }
public string StackId { get; private set; }
public static bool TryParseId (string stackId, out int id)
{
id = -1;
if (stackId?.StartsWith ("dotnet:", StringComparison.Ordinal) != true)
return false;
return int.TryParse (stackId.Substring ("dotnet:".Length), out id);
}
public Breakpoint (string stackId, SourceLocation loc, BreakpointState state)
{
this.StackId = stackId;
this.Location = loc;
this.State = state;
}
}
enum BreakpointState {
Active,
Disabled,
Pending
}
enum StepKind {
Into,
Out,
Over
}
internal class ExecutionContext {
public string DebuggerId { get; set; }
public Dictionary<string,BreakpointRequest> BreakpointRequests { get; } = new Dictionary<string,BreakpointRequest> ();
public TaskCompletionSource<DebugStore> ready = null;
public bool IsRuntimeReady => ready != null && ready.Task.IsCompleted;
public int Id { get; set; }
public object AuxData { get; set; }
public List<Frame> CallStack { get; set; }
internal DebugStore store;
public TaskCompletionSource<DebugStore> Source { get; } = new TaskCompletionSource<DebugStore> ();
int nextValueTypeId = 0;
public Dictionary<string, JToken> ValueTypesCache = new Dictionary<string, JToken> ();
public Dictionary<string, JToken> LocalsCache = new Dictionary<string, JToken> ();
public DebugStore Store {
get {
if (store == null || !Source.Task.IsCompleted)
return null;
return store;
}
}
public void ClearState ()
{
CallStack = null;
ValueTypesCache.Clear ();
LocalsCache.Clear ();
nextValueTypeId = 0;
}
public int NextValueTypeId () => Interlocked.Increment (ref nextValueTypeId);
}
}

View File

@ -11,103 +11,6 @@ using System.Collections.Generic;
using Microsoft.Extensions.Logging;
namespace WebAssembly.Net.Debugging {
internal struct SessionId {
public readonly string sessionId;
public SessionId (string sessionId)
{
this.sessionId = sessionId;
}
public override int GetHashCode ()
=> sessionId?.GetHashCode () ?? 0;
public override bool Equals (object obj)
=> (obj is SessionId) ? ((SessionId) obj).sessionId == sessionId : false;
public override string ToString ()
=> $"session-{sessionId}";
}
internal struct MessageId {
public readonly string sessionId;
public readonly int id;
public MessageId (string sessionId, int id)
{
this.sessionId = sessionId;
this.id = id;
}
public static implicit operator SessionId (MessageId id)
=> new SessionId (id.sessionId);
public override string ToString ()
=> $"msg-{sessionId}:::{id}";
public override int GetHashCode ()
=> (sessionId?.GetHashCode () ?? 0) ^ id.GetHashCode ();
public override bool Equals (object obj)
=> (obj is MessageId) ? ((MessageId) obj).sessionId == sessionId && ((MessageId) obj).id == id : false;
}
internal struct Result {
public JObject Value { get; private set; }
public JObject Error { get; private set; }
public bool IsOk => Value != null;
public bool IsErr => Error != null;
Result (JObject result, JObject error)
{
if (result != null && error != null)
throw new ArgumentException ($"Both {nameof(result)} and {nameof(error)} arguments cannot be non-null.");
bool resultHasError = String.Compare ((result? ["result"] as JObject)? ["subtype"]?. Value<string> (), "error") == 0;
if (result != null && resultHasError) {
this.Value = null;
this.Error = result;
} else {
this.Value = result;
this.Error = error;
}
}
public static Result FromJson (JObject obj)
{
//Log ("protocol", $"from result: {obj}");
return new Result (obj ["result"] as JObject, obj ["error"] as JObject);
}
public static Result Ok (JObject ok)
=> new Result (ok, null);
public static Result OkFromObject (object ok)
=> Ok (JObject.FromObject(ok));
public static Result Err (JObject err)
=> new Result (null, err);
public static Result Exception (Exception e)
=> new Result (null, JObject.FromObject (new { message = e.Message }));
public JObject ToJObject (MessageId target) {
if (IsOk) {
return JObject.FromObject (new {
target.id,
target.sessionId,
result = Value
});
} else {
return JObject.FromObject (new {
target.id,
target.sessionId,
error = Error
});
}
}
}
class DevToolsQueue {
Task current_send;
@ -293,11 +196,12 @@ namespace WebAssembly.Net.Debugging {
int id = Interlocked.Increment (ref next_cmd_id);
var o = JObject.FromObject (new {
sessionId.sessionId,
id,
method,
@params = args
});
if (sessionId.sessionId != null)
o["sessionId"] = sessionId.sessionId;
var tcs = new TaskCompletionSource<Result> ();
var msgId = new MessageId (sessionId.sessionId, id);
@ -317,10 +221,11 @@ namespace WebAssembly.Net.Debugging {
void SendEventInternal (SessionId sessionId, string method, JObject args, CancellationToken token)
{
var o = JObject.FromObject (new {
sessionId.sessionId,
method,
@params = args
});
if (sessionId.sessionId != null)
o["sessionId"] = sessionId.sessionId;
Send (this.ide, o, token);
}

View File

@ -0,0 +1,182 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using System.Threading;
using System.IO;
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using System.Reflection;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace WebAssembly.Net.Debugging {
internal class EvaluateExpression {
class FindThisExpression : CSharpSyntaxWalker {
public List<string> thisExpressions = new List<string> ();
public SyntaxTree syntaxTree;
public FindThisExpression (SyntaxTree syntax)
{
syntaxTree = syntax;
}
public override void Visit (SyntaxNode node)
{
if (node is ThisExpressionSyntax) {
if (node.Parent is MemberAccessExpressionSyntax thisParent && thisParent.Name is IdentifierNameSyntax) {
IdentifierNameSyntax var = thisParent.Name as IdentifierNameSyntax;
thisExpressions.Add(var.Identifier.Text);
var newRoot = syntaxTree.GetRoot ().ReplaceNode (node.Parent, thisParent.Name);
syntaxTree = syntaxTree.WithRootAndOptions (newRoot, syntaxTree.Options);
this.Visit (GetExpressionFromSyntaxTree(syntaxTree));
}
}
else
base.Visit (node);
}
public async Task CheckIfIsProperty (MonoProxy proxy, MessageId msg_id, int scope_id, CancellationToken token)
{
foreach (var var in thisExpressions) {
JToken value = await proxy.TryGetVariableValue (msg_id, scope_id, var, true, token);
if (value == null)
throw new Exception ($"The property {var} does not exist in the current context");
}
}
}
class FindVariableNMethodCall : CSharpSyntaxWalker {
public List<IdentifierNameSyntax> variables = new List<IdentifierNameSyntax> ();
public List<ThisExpressionSyntax> thisList = new List<ThisExpressionSyntax> ();
public List<InvocationExpressionSyntax> methodCall = new List<InvocationExpressionSyntax> ();
public List<object> values = new List<Object> ();
public override void Visit (SyntaxNode node)
{
if (node is IdentifierNameSyntax identifier && !variables.Any (x => x.Identifier.Text == identifier.Identifier.Text))
variables.Add (identifier);
if (node is InvocationExpressionSyntax) {
methodCall.Add (node as InvocationExpressionSyntax);
throw new Exception ("Method Call is not implemented yet");
}
if (node is AssignmentExpressionSyntax)
throw new Exception ("Assignment is not implemented yet");
base.Visit (node);
}
public async Task<SyntaxTree> ReplaceVars (SyntaxTree syntaxTree, MonoProxy proxy, MessageId msg_id, int scope_id, CancellationToken token)
{
CompilationUnitSyntax root = syntaxTree.GetCompilationUnitRoot ();
foreach (var var in variables) {
ClassDeclarationSyntax classDeclaration = root.Members.ElementAt (0) as ClassDeclarationSyntax;
MethodDeclarationSyntax method = classDeclaration.Members.ElementAt (0) as MethodDeclarationSyntax;
JToken value = await proxy.TryGetVariableValue (msg_id, scope_id, var.Identifier.Text, false, token);
if (value == null)
throw new Exception ($"The name {var.Identifier.Text} does not exist in the current context");
values.Add (ConvertJSToCSharpType (value ["value"] ["value"].ToString (), value ["value"] ["type"].ToString ()));
var updatedMethod = method.AddParameterListParameters (
SyntaxFactory.Parameter (
SyntaxFactory.Identifier (var.Identifier.Text))
.WithType (SyntaxFactory.ParseTypeName (GetTypeFullName(value["value"]["type"].ToString()))));
root = root.ReplaceNode (method, updatedMethod);
}
syntaxTree = syntaxTree.WithRootAndOptions (root, syntaxTree.Options);
return syntaxTree;
}
private object ConvertJSToCSharpType (string v, string type)
{
switch (type) {
case "number":
return Convert.ChangeType (v, typeof (int));
case "string":
return v;
}
throw new Exception ($"Evaluate of this datatype {type} not implemented yet");
}
private string GetTypeFullName (string type)
{
switch (type) {
case "number":
return typeof (int).FullName;
case "string":
return typeof (string).FullName;
}
throw new Exception ($"Evaluate of this datatype {type} not implemented yet");
}
}
static SyntaxNode GetExpressionFromSyntaxTree (SyntaxTree syntaxTree)
{
CompilationUnitSyntax root = syntaxTree.GetCompilationUnitRoot ();
ClassDeclarationSyntax classDeclaration = root.Members.ElementAt (0) as ClassDeclarationSyntax;
MethodDeclarationSyntax methodDeclaration = classDeclaration.Members.ElementAt (0) as MethodDeclarationSyntax;
BlockSyntax blockValue = methodDeclaration.Body;
ReturnStatementSyntax returnValue = blockValue.Statements.ElementAt (0) as ReturnStatementSyntax;
InvocationExpressionSyntax expressionInvocation = returnValue.Expression as InvocationExpressionSyntax;
MemberAccessExpressionSyntax expressionMember = expressionInvocation.Expression as MemberAccessExpressionSyntax;
ParenthesizedExpressionSyntax expressionParenthesized = expressionMember.Expression as ParenthesizedExpressionSyntax;
return expressionParenthesized.Expression;
}
internal static async Task<string> CompileAndRunTheExpression (MonoProxy proxy, MessageId msg_id, int scope_id, string expression, CancellationToken token)
{
FindVariableNMethodCall findVarNMethodCall = new FindVariableNMethodCall ();
string retString;
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText (@"
using System;
public class CompileAndRunTheExpression
{
public string Evaluate()
{
return (" + expression + @").ToString();
}
}");
FindThisExpression findThisExpression = new FindThisExpression (syntaxTree);
var expressionTree = GetExpressionFromSyntaxTree(syntaxTree);
findThisExpression.Visit (expressionTree);
await findThisExpression.CheckIfIsProperty (proxy, msg_id, scope_id, token);
syntaxTree = findThisExpression.syntaxTree;
expressionTree = GetExpressionFromSyntaxTree (syntaxTree);
findVarNMethodCall.Visit (expressionTree);
syntaxTree = await findVarNMethodCall.ReplaceVars (syntaxTree, proxy, msg_id, scope_id, token);
MetadataReference [] references = new MetadataReference []
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location)
};
CSharpCompilation compilation = CSharpCompilation.Create (
"compileAndRunTheExpression",
syntaxTrees: new [] { syntaxTree },
references: references,
options: new CSharpCompilationOptions (OutputKind.DynamicallyLinkedLibrary));
using (var ms = new MemoryStream ()) {
EmitResult result = compilation.Emit (ms);
ms.Seek (0, SeekOrigin.Begin);
Assembly assembly = Assembly.Load (ms.ToArray ());
Type type = assembly.GetType ("CompileAndRunTheExpression");
object obj = Activator.CreateInstance (type);
var ret = type.InvokeMember ("Evaluate",
BindingFlags.Default | BindingFlags.InvokeMethod,
null,
obj,
//new object [] { 10 }
findVarNMethodCall.values.ToArray ());
retString = ret.ToString ();
}
return retString;
}
}
}

View File

@ -8,137 +8,17 @@ using System.IO;
using System.Collections.Generic;
using System.Net;
using Microsoft.Extensions.Logging;
using Microsoft.CodeAnalysis;
namespace WebAssembly.Net.Debugging {
internal class MonoCommands {
public string expression { get; set; }
public string objectGroup { get; set; } = "mono-debugger";
public bool includeCommandLineAPI { get; set; } = false;
public bool silent { get; set; } = false;
public bool returnByValue { get; set; } = true;
public MonoCommands (string expression)
=> this.expression = expression;
public static MonoCommands GetCallStack ()
=> new MonoCommands ("MONO.mono_wasm_get_call_stack()");
public static MonoCommands IsRuntimeReady ()
=> new MonoCommands ("MONO.mono_wasm_runtime_is_ready");
public static MonoCommands StartSingleStepping (StepKind kind)
=> new MonoCommands ($"MONO.mono_wasm_start_single_stepping ({(int)kind})");
public static MonoCommands GetLoadedFiles ()
=> new MonoCommands ("MONO.mono_wasm_get_loaded_files()");
public static MonoCommands ClearAllBreakpoints ()
=> new MonoCommands ("MONO.mono_wasm_clear_all_breakpoints()");
public static MonoCommands GetObjectProperties (int objectId)
=> new MonoCommands ($"MONO.mono_wasm_get_object_properties({objectId})");
public static MonoCommands GetArrayValues (int objectId)
=> new MonoCommands ($"MONO.mono_wasm_get_array_values({objectId})");
public static MonoCommands GetScopeVariables (int scopeId, params int[] vars)
=> new MonoCommands ($"MONO.mono_wasm_get_variables({scopeId}, [ {string.Join (",", vars)} ])");
public static MonoCommands SetBreakpoint (string assemblyName, int methodToken, int ilOffset)
=> new MonoCommands ($"MONO.mono_wasm_set_breakpoint (\"{assemblyName}\", {methodToken}, {ilOffset})");
public static MonoCommands RemoveBreakpoint (int breakpointId)
=> new MonoCommands ($"MONO.mono_wasm_remove_breakpoint({breakpointId})");
}
internal enum MonoErrorCodes {
BpNotFound = 100000,
}
internal class MonoConstants {
public const string RUNTIME_IS_READY = "mono_wasm_runtime_ready";
}
class Frame {
public Frame (MethodInfo method, SourceLocation location, int id)
{
this.Method = method;
this.Location = location;
this.Id = id;
}
public MethodInfo Method { get; private set; }
public SourceLocation Location { get; private set; }
public int Id { get; private set; }
}
class Breakpoint {
public SourceLocation Location { get; private set; }
public int RemoteId { get; set; }
public BreakpointState State { get; set; }
public string StackId { get; private set; }
public static bool TryParseId (string stackId, out int id)
{
id = -1;
if (stackId?.StartsWith ("dotnet:", StringComparison.Ordinal) != true)
return false;
return int.TryParse (stackId.Substring ("dotnet:".Length), out id);
}
public Breakpoint (string stackId, SourceLocation loc, BreakpointState state)
{
this.StackId = stackId;
this.Location = loc;
this.State = state;
}
}
enum BreakpointState {
Active,
Disabled,
Pending
}
enum StepKind {
Into,
Out,
Over
}
internal class ExecutionContext {
public string DebuggerId { get; set; }
public Dictionary<string,BreakpointRequest> BreakpointRequests { get; } = new Dictionary<string,BreakpointRequest> ();
public TaskCompletionSource<DebugStore> ready = null;
public bool IsRuntimeReady => ready != null && ready.Task.IsCompleted;
public int Id { get; set; }
public object AuxData { get; set; }
public List<Frame> CallStack { get; set; }
internal DebugStore store;
public TaskCompletionSource<DebugStore> Source { get; } = new TaskCompletionSource<DebugStore> ();
public DebugStore Store {
get {
if (store == null || !Source.Task.IsCompleted)
return null;
return store;
}
}
}
internal class MonoProxy : DevToolsProxy {
Dictionary<SessionId, ExecutionContext> contexts = new Dictionary<SessionId, ExecutionContext> ();
public MonoProxy (ILoggerFactory loggerFactory) : base(loggerFactory) { }
ExecutionContext GetContext (SessionId sessionId)
internal ExecutionContext GetContext (SessionId sessionId)
{
if (contexts.TryGetValue (sessionId, out var context))
return context;
@ -221,11 +101,14 @@ namespace WebAssembly.Net.Debugging {
protected override async Task<bool> AcceptCommand (MessageId id, string method, JObject args, CancellationToken token)
{
if (!contexts.TryGetValue (id, out var context))
return false;
switch (method) {
case "Debugger.enable": {
var resp = await SendCommand (id, method, args, token);
GetContext (id).DebuggerId = resp.Value ["debuggerId"]?.ToString ();
context.DebuggerId = resp.Value ["debuggerId"]?.ToString ();
if (await IsRuntimeAlreadyReadyAlready (id, token))
await RuntimeReady (id, token);
@ -270,7 +153,6 @@ namespace WebAssembly.Net.Debugging {
}
case "Debugger.setBreakpointByUrl": {
var context = GetContext (id);
var resp = await SendCommand (id, method, args, token);
if (!resp.IsOk) {
SendResponse (id, resp, token);
@ -312,6 +194,23 @@ namespace WebAssembly.Net.Debugging {
return await Step (id, StepKind.Over, token);
}
case "Debugger.evaluateOnCallFrame": {
var objId = args? ["callFrameId"]?.Value<string> ();
if (objId.StartsWith ("dotnet:", StringComparison.Ordinal)) {
var parts = objId.Split (new char [] { ':' });
if (parts.Length < 3)
return true;
switch (parts [1]) {
case "scope": {
await GetEvaluateOnCallFrame (id, int.Parse (parts [2]), args? ["expression"]?.Value<string> (), token);
break;
}
}
return true;
}
return false;
}
case "Runtime.getProperties": {
var objId = args? ["objectId"]?.Value<string> ();
if (objId.StartsWith ("dotnet:", StringComparison.Ordinal)) {
@ -324,14 +223,26 @@ namespace WebAssembly.Net.Debugging {
break;
}
case "object": {
await GetDetails (id, MonoCommands.GetObjectProperties (int.Parse (parts[2])), token);
await GetDetails (id, MonoCommands.GetObjectProperties (int.Parse (parts[2]), expandValueTypes: false), token);
break;
}
case "array": {
await GetDetails (id, MonoCommands.GetArrayValues (int.Parse (parts [2])), token);
await GetArrayDetails (id, objId, parts, token);
break;
}
case "valuetype": {
await GetDetailsForValueType (id, objId,
get_props_cmd_fn: () => {
if (parts.Length < 4)
return null;
var containerObjId = int.Parse (parts[2]);
return MonoCommands.GetObjectProperties (containerObjId, expandValueTypes: true);
}, token);
break;
}
}
return true;
}
break;
@ -341,6 +252,30 @@ namespace WebAssembly.Net.Debugging {
return false;
}
async Task GetArrayDetails (MessageId id, string objId, string[] objIdParts, CancellationToken token)
{
switch (objIdParts.Length)
{
case 3: {
await GetDetails (id, MonoCommands.GetArrayValues (int.Parse (objIdParts [2])), token);
break;
}
case 4: {
// This form of the id is being used only for valuetypes right now
await GetDetailsForValueType(id, objId,
get_props_cmd_fn: () => {
var arrayObjectId = int.Parse (objIdParts [2]);
var idx = int.Parse (objIdParts [3]);
return MonoCommands.GetArrayValueExpanded (arrayObjectId, idx);
}, token);
break;
}
default:
SendResponse (id, Result.Exception (new ArgumentException ($"Unknown objectId format for array: {objId}")), token);
break;
}
}
//static int frame_id=0;
async Task<bool> OnBreakpointHit (SessionId sessionId, JObject args, CancellationToken token)
{
@ -381,6 +316,7 @@ namespace WebAssembly.Net.Debugging {
var the_mono_frames = res.Value? ["result"]? ["value"]? ["frames"]?.Values<JObject> ();
foreach (var mono_frame in the_mono_frames) {
++frame_id;
var il_pos = mono_frame ["il_pos"].Value<int> ();
var method_token = mono_frame ["method_token"].Value<int> ();
var assembly_name = mono_frame ["assembly_name"].Value<string> ();
@ -411,11 +347,11 @@ namespace WebAssembly.Net.Debugging {
Log ("info", $"frame il offset: {il_pos} method token: {method_token} assembly name: {assembly_name}");
Log ("info", $"\tmethod {method.Name} location: {location}");
frames.Add (new Frame (method, location, frame_id));
frames.Add (new Frame (method, location, frame_id-1));
callFrames.Add (new {
functionName = method.Name,
callFrameId = $"dotnet:scope:{frame_id}",
callFrameId = $"dotnet:scope:{frame_id-1}",
functionLocation = method.StartLocation.AsLocation (),
location = location.AsLocation (),
@ -429,7 +365,7 @@ namespace WebAssembly.Net.Debugging {
@type = "object",
className = "Object",
description = "Object",
objectId = $"dotnet:scope:{frame_id}",
objectId = $"dotnet:scope:{frame_id-1}",
},
name = method.Name,
startLocation = method.StartLocation.AsLocation (),
@ -437,7 +373,6 @@ namespace WebAssembly.Net.Debugging {
}}
});
++frame_id;
context.CallStack = frames;
}
@ -477,7 +412,7 @@ namespace WebAssembly.Net.Debugging {
async Task OnResume (MessageId msd_id, CancellationToken token)
{
//discard managed frames
GetContext (msd_id).CallStack = null;
GetContext (msd_id).ClearState ();
await Task.CompletedTask;
}
@ -495,30 +430,20 @@ namespace WebAssembly.Net.Debugging {
var ret_code = res.Value? ["result"]? ["value"]?.Value<int> ();
if (ret_code.HasValue && ret_code.Value == 0) {
context.CallStack = null;
context.ClearState ();
await SendCommand (msg_id, "Debugger.stepOut", new JObject (), token);
return false;
}
}
SendResponse (msg_id, Result.Ok (new JObject ()), token);
context.CallStack = null;
context.ClearState ();
await SendCommand (msg_id, "Debugger.resume", new JObject (), token);
return true;
}
static string FormatFieldName (string name)
{
if (name.Contains("k__BackingField", StringComparison.Ordinal)) {
return name.Replace("k__BackingField", "", StringComparison.Ordinal)
.Replace("<", "", StringComparison.Ordinal)
.Replace(">", "", StringComparison.Ordinal);
}
return name;
}
async Task GetDetails(MessageId msg_id, MonoCommands cmd, CancellationToken token)
async Task GetDetails(MessageId msg_id, MonoCommands cmd, CancellationToken token, bool send_response = true)
{
var res = await SendMonoCommand(msg_id, cmd, token);
@ -529,43 +454,124 @@ namespace WebAssembly.Net.Debugging {
}
try {
var values = res.Value?["result"]?["value"]?.Values<JObject>().ToArray() ?? Array.Empty<JObject>();
var var_list = new List<JObject>();
var var_list = res.Value?["result"]?["value"]?.Values<JObject>().ToArray() ?? Array.Empty<JObject>();
if (var_list.Length > 0)
ExtractAndCacheValueTypes (GetContext (msg_id), var_list);
// Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously
// results in a "Memory access out of bounds", causing 'values' to be null,
// so skip returning variable values in that case.
for (int i = 0; i < values.Length; i+=2)
{
string fieldName = FormatFieldName ((string)values[i]["name"]);
var value = values [i + 1]? ["value"];
if (((string)value ["description"]) == null)
value ["description"] = value ["value"]?.ToString ();
if (!send_response)
return;
var_list.Add(JObject.FromObject(new {
name = fieldName,
value
}));
}
var response = JObject.FromObject(new
{
result = var_list
});
SendResponse(msg_id, Result.Ok(response), token);
} catch (Exception e) {
} catch (Exception e) when (send_response) {
Log ("verbose", $"failed to parse {res.Value} - {e.Message}");
SendResponse(msg_id, Result.Exception(e), token);
}
}
internal bool TryFindVariableValueInCache(ExecutionContext ctx, string expression, bool only_search_on_this, out JToken obj)
{
if (ctx.LocalsCache.TryGetValue (expression, out obj)) {
if (only_search_on_this && obj["fromThis"] == null)
return false;
return true;
}
return false;
}
internal async Task<JToken> TryGetVariableValue (MessageId msg_id, int scope_id, string expression, bool only_search_on_this, CancellationToken token)
{
JToken thisValue = null;
var context = GetContext (msg_id);
if (context.CallStack == null)
return null;
if (TryFindVariableValueInCache(context, expression, only_search_on_this, out JToken obj))
return obj;
var scope = context.CallStack.FirstOrDefault (s => s.Id == scope_id);
var vars = scope.Method.GetLiveVarsAt (scope.Location.CliLocation.Offset);
//get_this
int [] var_ids = { };
var res = await SendMonoCommand (msg_id, MonoCommands.GetScopeVariables (scope.Id, var_ids), token);
var values = res.Value? ["result"]? ["value"]?.Values<JObject> ().ToArray ();
thisValue = values.FirstOrDefault (v => v ["name"].Value<string> () == "this");
if (!only_search_on_this) {
if (thisValue != null && expression == "this") {
return thisValue;
}
//search in locals
var var_id = vars.SingleOrDefault (v => v.Name == expression);
if (var_id != null) {
res = await SendMonoCommand (msg_id, MonoCommands.GetScopeVariables (scope.Id, new int [] { var_id.Index }), token);
values = res.Value? ["result"]? ["value"]?.Values<JObject> ().ToArray ();
return values [0];
}
}
//search in scope
if (thisValue != null) {
var objectId = thisValue ["value"] ["objectId"].Value<string> ();
var parts = objectId.Split (new char [] { ':' });
res = await SendMonoCommand (msg_id, MonoCommands.GetObjectProperties (int.Parse (parts [2]), expandValueTypes: false), token);
values = res.Value? ["result"]? ["value"]?.Values<JObject> ().ToArray ();
var foundValue = values.FirstOrDefault (v => v ["name"].Value<string> () == expression);
if (foundValue != null) {
foundValue["fromThis"] = true;
context.LocalsCache[foundValue ["name"].Value<string> ()] = foundValue;
return foundValue;
}
}
return null;
}
async Task GetEvaluateOnCallFrame (MessageId msg_id, int scope_id, string expression, CancellationToken token)
{
try {
var context = GetContext (msg_id);
if (context.CallStack == null)
return;
var varValue = await TryGetVariableValue (msg_id, scope_id, expression, false, token);
if (varValue != null) {
varValue ["value"] ["description"] = varValue ["value"] ["className"];
SendResponse (msg_id, Result.OkFromObject (new {
result = varValue ["value"]
}), token);
return;
}
string retValue = await EvaluateExpression.CompileAndRunTheExpression (this, msg_id, scope_id, expression, token);
SendResponse (msg_id, Result.OkFromObject (new {
result = new {
value = retValue
}
}), token);
} catch (Exception e) {
logger.LogTrace (e.Message, expression);
SendResponse (msg_id, Result.OkFromObject (new {}), token);
}
}
async Task GetScopeProperties (MessageId msg_id, int scope_id, CancellationToken token)
{
try {
var scope = GetContext (msg_id).CallStack.FirstOrDefault (s => s.Id == scope_id);
var ctx = GetContext (msg_id);
var scope = ctx.CallStack.FirstOrDefault (s => s.Id == scope_id);
if (scope == null) {
SendResponse (msg_id,
Result.Err (JObject.FromObject (new { message = $"Could not find scope with id #{scope_id}" })),
token);
return;
}
var vars = scope.Method.GetLiveVarsAt (scope.Location.CliLocation.Offset);
var var_ids = vars.Select (v => v.Index).ToArray ();
@ -584,39 +590,17 @@ namespace WebAssembly.Net.Debugging {
return;
}
ExtractAndCacheValueTypes (ctx, values);
var var_list = new List<object> ();
int i = 0;
// Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously
// results in a "Memory access out of bounds", causing 'values' to be null,
// so skip returning variable values in that case.
while (i < vars.Length && i < values.Length) {
var value = values [i] ["value"];
if (((string)value ["description"]) == null)
value ["description"] = value ["value"]?.ToString ();
var_list.Add (new {
name = vars [i].Name,
value
});
i++;
for (; i < vars.Length && i < values.Length; i ++) {
ctx.LocalsCache[vars [i].Name] = values [i];
var_list.Add (new { name = vars [i].Name, value = values [i]["value"] });
}
//Async methods are special in the way that local variables can be lifted to generated class fields
//value of "this" comes here either
while (i < values.Length) {
String name = values [i] ["name"].ToString ();
if (name.IndexOf (">", StringComparison.Ordinal) > 0)
name = name.Substring (1, name.IndexOf (">", StringComparison.Ordinal) - 1);
var value = values [i + 1] ["value"];
if (((string)value ["description"]) == null)
value ["description"] = value ["value"]?.ToString ();
var_list.Add (new {
name,
value
});
i = i + 2;
for (; i < values.Length; i ++) {
ctx.LocalsCache[values [i]["name"].ToString()] = values [i]["value"];
var_list.Add (values [i]);
}
SendResponse (msg_id, Result.OkFromObject (new { result = var_list }), token);
@ -626,6 +610,68 @@ namespace WebAssembly.Net.Debugging {
}
}
IEnumerable<JObject> ExtractAndCacheValueTypes (ExecutionContext ctx, IEnumerable<JObject> var_list)
{
foreach (var jo in var_list) {
var value = jo["value"]?.Value<JObject> ();
if (value ["type"]?.Value<string> () != "object")
continue;
if (!(value ["isValueType"]?.Value<bool> () ?? false) || // not a valuetype
!(value ["expanded"]?.Value<bool> () ?? false)) // not expanded
continue;
// Expanded ValueType
var members = value ["members"]?.Values<JObject>().ToArray() ?? Array.Empty<JObject>();
var objectId = value ["objectId"]?.Value<string> () ?? $"dotnet:valuetype:{ctx.NextValueTypeId ()}";
value ["objectId"] = objectId;
ExtractAndCacheValueTypes (ctx, members);
ctx.ValueTypesCache [objectId] = JArray.FromObject (members);
value.Remove ("members");
}
return var_list;
}
async Task<bool> GetDetailsForValueType (MessageId msg_id, string object_id, Func<MonoCommands> get_props_cmd_fn, CancellationToken token)
{
var ctx = GetContext (msg_id);
if (!ctx.ValueTypesCache.ContainsKey (object_id)) {
var cmd = get_props_cmd_fn ();
if (cmd == null) {
SendResponse (msg_id, Result.Exception (new ArgumentException (
"Could not find a cached value for {object_id}, and cant' expand it.")),
token);
return false;
} else {
await GetDetails (msg_id, cmd, token, send_response: false);
}
}
if (ctx.ValueTypesCache.TryGetValue (object_id, out var var_list)) {
var response = JObject.FromObject(new
{
result = var_list
});
SendResponse(msg_id, Result.Ok(response), token);
return true;
} else {
var response = JObject.FromObject(new
{
result = $"Unable to get details for {object_id}"
});
SendResponse(msg_id, Result.Err(response), token);
return false;
}
}
async Task<Breakpoint> SetMonoBreakpoint (SessionId sessionId, BreakpointRequest req, SourceLocation location, CancellationToken token)
{
var bp = new Breakpoint (req.Id, location, BreakpointState.Pending);

View File

@ -332,8 +332,8 @@ export class AuthenticationService {
return AuthenticationService.instance.getUser();
}
public static getAccessToken() {
return AuthenticationService.instance.getAccessToken();
public static getAccessToken(options: AccessTokenRequestOptions) {
return AuthenticationService.instance.getAccessToken(options);
}
public static signIn(state: unknown) {

View File

@ -26,6 +26,7 @@ namespace Microsoft.Extensions.DependencyInjection
where TAccountClaimsPrincipalFactory : AccountClaimsPrincipalFactory<TAccount>
{
builder.Services.Replace(ServiceDescriptor.Scoped<AccountClaimsPrincipalFactory<TAccount>, TAccountClaimsPrincipalFactory>());
builder.Services.Replace(ServiceDescriptor.Scoped<AccountClaimsPrincipalFactory<TAccount>, TUserFactory>());
return builder;
}
@ -51,5 +52,6 @@ namespace Microsoft.Extensions.DependencyInjection
public static IRemoteAuthenticationBuilder<RemoteAuthenticationState, RemoteUserAccount> AddAccountClaimsPrincipalFactory<TAccountClaimsPrincipalFactory>(
this IRemoteAuthenticationBuilder<RemoteAuthenticationState, RemoteUserAccount> builder)
where TAccountClaimsPrincipalFactory : AccountClaimsPrincipalFactory<RemoteUserAccount> => builder.AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, RemoteUserAccount, TAccountClaimsPrincipalFactory>();
where TUserFactory : AccountClaimsPrincipalFactory<RemoteUserAccount> => builder.AddUserFactory<RemoteAuthenticationState, RemoteUserAccount, TUserFactory>();
}
}

View File

@ -177,7 +177,6 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication
await ProcessLogInCallback();
break;
case RemoteAuthenticationActions.LogInFailed:
_message = GetErrorMessage();
break;
case RemoteAuthenticationActions.Profile:
if (ApplicationPaths.RemoteProfilePath == null)
@ -209,7 +208,6 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication
await ProcessLogOutCallback();
break;
case RemoteAuthenticationActions.LogOutFailed:
_message = GetErrorMessage();
break;
case RemoteAuthenticationActions.LogOutSucceeded:
break;
@ -235,8 +233,8 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication
await NavigateToReturnUrl(GetReturnUrl(result.State, returnUrl));
break;
case RemoteAuthenticationStatus.Failure:
var uri = Navigation.ToAbsoluteUri($"{ApplicationPaths.LogInFailedPath}?message={Uri.EscapeDataString(result.ErrorMessage)}").ToString();
await NavigateToReturnUrl(uri);
_message = result.ErrorMessage;
Navigation.NavigateTo(ApplicationPaths.LogInFailedPath);
break;
case RemoteAuthenticationStatus.OperationCompleted:
default:
@ -297,8 +295,8 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication
case RemoteAuthenticationStatus.OperationCompleted:
break;
case RemoteAuthenticationStatus.Failure:
var uri = Navigation.ToAbsoluteUri($"{ApplicationPaths.LogOutFailedPath}?message={Uri.EscapeDataString(result.ErrorMessage)}").ToString();
await NavigateToReturnUrl(uri);
_message = result.ErrorMessage;
Navigation.NavigateTo(ApplicationPaths.LogOutFailedPath);
break;
default:
throw new InvalidOperationException($"Invalid authentication result status.");
@ -363,8 +361,6 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication
private ValueTask RedirectToProfile() => JS.InvokeVoidAsync("location.replace", Navigation.ToAbsoluteUri(ApplicationPaths.RemoteProfilePath).PathAndQuery);
private string GetErrorMessage() => QueryStringHelper.GetParameter(new Uri(Navigation.Uri).Query, "message");
private static void DefaultLogInFragment(RenderTreeBuilder builder)
{
builder.OpenElement(0, "p");

View File

@ -10,18 +10,21 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication
/// </summary>
public class AccessTokenResult
{
private AccessToken _token;
private readonly AccessToken _token;
private readonly NavigationManager _navigation;
/// <summary>
/// Initializes a new instance of <see cref="AccessTokenResult"/>.
/// </summary>
/// <param name="status">The status of the result.</param>
/// <param name="token">The <see cref="AccessToken"/> in case it was successful.</param>
/// <param name="navigation">The <see cref="NavigationManager"/> to perform redirects.</param>
/// <param name="redirectUrl">The redirect uri to go to for provisioning the token.</param>
public AccessTokenResult(AccessTokenResultStatus status, AccessToken token, string redirectUrl)
public AccessTokenResult(AccessTokenResultStatus status, AccessToken token, NavigationManager navigation, string redirectUrl)
{
Status = status;
_token = token;
_navigation = navigation;
RedirectUrl = redirectUrl;
}
@ -53,5 +56,27 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication
return false;
}
}
/// <summary>
/// Determines whether the token request was successful and makes the <see cref="AccessToken"/> available for use when it is.
/// </summary>
/// <param name="accessToken">The <see cref="AccessToken"/> if the request was successful.</param>
/// <param name="redirect">Whether or not to redirect automatically when failing to provision a token.</param>
/// <returns><c>true</c> when the token request is successful; <c>false</c> otherwise.</returns>
public bool TryGetToken(out AccessToken accessToken, bool redirect)
{
if (TryGetToken(out accessToken))
{
return true;
}
else
{
if (redirect)
{
_navigation.NavigateTo(RedirectUrl);
}
return false;
}
}
}
}

View File

@ -159,7 +159,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication
result.RedirectUrl = redirectUrl.ToString();
}
return new AccessTokenResult(parsedStatus, result.Token, result.RedirectUrl);
return new AccessTokenResult(parsedStatus, result.Token, Navigation, result.RedirectUrl);
}
/// <inheritdoc />
@ -184,7 +184,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication
result.RedirectUrl = redirectUrl.ToString();
}
return new AccessTokenResult(parsedStatus, result.Token, result.RedirectUrl);
return new AccessTokenResult(parsedStatus, result.Token, Navigation, result.RedirectUrl);
}
private Uri GetRedirectUrl(string customReturnUrl)

View File

@ -113,10 +113,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication
await renderer.Dispatcher.InvokeAsync<object>(() => remoteAuthenticator.SetParametersAsync(parameters));
// Assert
Assert.Equal(
"https://www.example.com/base/authentication/login-failed?message=There was an error trying to log in",
jsRuntime.LastInvocation.args[0]);
Assert.Equal("https://www.example.com/base/authentication/login-failed", remoteAuthenticator.Navigation.Uri.ToString());
}
[Fact]
@ -368,9 +365,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication
await renderer.Dispatcher.InvokeAsync<object>(() => remoteAuthenticator.SetParametersAsync(parameters));
// Assert
Assert.Equal(
"https://www.example.com/base/authentication/logout-failed?message=There was an error trying to log out",
jsRuntime.LastInvocation.args[0]);
Assert.Equal("https://www.example.com/base/authentication/logout-failed", remoteAuthenticator.Navigation.Uri.ToString());
}
[Fact]
@ -603,7 +598,8 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication
{
public TestNavigationManager(string baseUrl, string currentUrl) => Initialize(baseUrl, currentUrl);
protected override void NavigateToCore(string uri, bool forceLoad) => Uri = uri;
protected override void NavigateToCore(string uri, bool forceLoad)
=> Uri = System.Uri.IsWellFormedUriString(uri, UriKind.Absolute) ? uri : new Uri(new Uri(BaseUri), uri).ToString();
}
private class TestSignOutSessionStateManager : SignOutSessionStateManager

View File

@ -47,14 +47,10 @@ else
var tokenResult = await AuthenticationService.RequestAccessToken();
if (tokenResult.TryGetToken(out var token))
if (tokenResult.TryGetToken(out var token, redirect: true))
{
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {token.Value}");
forecasts = await httpClient.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
}
else
{
Navigation.NavigateTo(tokenResult.RedirectUrl);
}
}
}

View File

@ -6,6 +6,9 @@
<!--#if PWA -->
<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
<!--#endif -->
<!--#if Hosted -->
<AssemblyName Condition="'$(name)' != '$(name{-VALUE-FORMS-}safe_namespace)'">`$(AssemblyName.Replace(' ', '_'))</AssemblyName>
<!--#endif -->
</PropertyGroup>
<ItemGroup>

View File

@ -2,11 +2,12 @@
<PropertyGroup>
<TargetFramework>${DefaultNetCoreTargetFramework}</TargetFramework>
<UserSecretsId Condition="'$(IndividualAuth)' == 'True' OR '$(OrganizationalAuth)' == 'True'">BlazorWasm-CSharp.Server-53bc9b9d-9d6a-45d4-8429-2a2761773502</UserSecretsId>
<UserSecretsId Condition="'$(IndividualAuth)' == 'True' OR '$(OrganizationalAuth)' == 'True'">ComponentsWebAssembly-CSharp.Server-53bc9b9d-9d6a-45d4-8429-2a2761773502</UserSecretsId>
<WebProject_DirectoryAccessLevelKey Condition="'$(OrganizationalAuth)' == 'True' AND '$(OrgReadAccess)' != 'True'">0</WebProject_DirectoryAccessLevelKey>
<WebProject_DirectoryAccessLevelKey Condition="'$(OrganizationalAuth)' == 'True' AND '$(OrgReadAccess)' == 'True'">1</WebProject_DirectoryAccessLevelKey>
<NoDefaultLaunchSettingsFile Condition="'$(ExcludeLaunchSettings)' == 'True'">True</NoDefaultLaunchSettingsFile>
<RootNamespace Condition="'$(name)' != '$(name{-VALUE-FORMS-}safe_namespace)'">BlazorWasm-CSharp.Server</RootNamespace>
<RootNamespace Condition="'$(name)' != '$(name{-VALUE-FORMS-}safe_namespace)'">ComponentsWebAssembly-CSharp.Server</RootNamespace>
<AssemblyName Condition="'$(name)' != '$(name{-VALUE-FORMS-}safe_namespace)'">`$(AssemblyName.Replace(' ', '_'))</AssemblyName>
</PropertyGroup>
<ItemGroup>

View File

@ -1,4 +1,4 @@
@page "/fetchdata"
@page "/fetchdata"
@*#if (!NoAuth && Hosted)
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@ -60,15 +60,11 @@ else
var tokenResult = await AuthenticationService.RequestAccessToken();
if (tokenResult.TryGetToken(out var token))
if (tokenResult.TryGetToken(out var token, redirect: true))
{
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {token.Value}");
forecasts = await httpClient.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
}
else
{
Navigation.NavigateTo(tokenResult.RedirectUrl);
}
#else
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");