-
Notifications
You must be signed in to change notification settings - Fork 10k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Blazor API Review: JS Interop #12082
Comments
Some of my thoughts about this.... I'm going to stick to the .NET side because I'm not entirely certain what to optimize for in the TS apis. DotNetDispatcherI'd like to choose a name or namespace for dedicated interop types. Previously .NET has given the guidance to use specific names. This kind of thing is valuable because it helps organization and works with tooling like analyzers (can teach analyzers to ignore that namespace). We're also planning to build an analyzer that will warn on usage of unsupported APIs (for ASP.NET Core as a whole). This is something we could leverage here as well - (don't call interop APIs from .NET). I'm thinking about that because I look at some of these signatures, and I think they would break if we added more features. For instance, what about if we added support for generics? That would require adding another new API since we'd have no way to pass a type in there. Adding a parameter object would help. Rather than trying to polish what's here to a mirror sheen, I think it's a better idea to declare this area as "implementation detail only".
JSInvokableAttribute
DotNetObjectRefWe can write a custom converter to get rid of We need to lock on the name of this. I'm not a big fan of seeing Some suggestions:
I don't want to drill into this too hard, if there isn't another suggestion that we really like, then it should stay as is. IJSRuntime and related typesWe should consider using I'm not totally certain why we have separate interfaces and abstract classes. My main concern is that we can't easily add members to the interfaces, so I'm not sure what exactly they buy us vs just the classes.
I'm not sure that my concerns about the security of
|
Throwing in I wanted to consider removing the ProposalJSMarshalByRefObject<T> IJSRuntime.CreateMarshalByRefObject(T value) where T : object In addition to this, as part of the Dispatcher's argument binding, we should also consider binding |
Another follow: calls to
The only interesting behavior to add would for |
|
I caught up with @terrajobst to talk about the methods on
As an example of this last one, consider what happens when you need to pass a If we just do |
We also talked about using |
That's a fair, but if that's a concern we could probably provide an implementation for mocking that takes a delegate. I'd say it depends on how valuable you see the reduction of allocations for JS interop. |
@rynowak, I do not know if its too late to provide input here, but ill try anyway :) When building component libraries, we do not know if the user will be using them in a blazor client or blazor server scenario, and if the client will have prerenderinger enabled. That means we must guard against a disconnected state, inject For components, it looks as if we can skip the JS call if [Inject] private IComponentContext? Context { get; set; }
protected override Task OnAfterRenderAsync()
{
if (!Context.IsConnected)
return Task.CompletedTask;
else
return JsRuntime!.InvokeAsync<object>("jsFunc", ...);
} For services (scoped/singleton) it is a bit more tricky as far as I can tell, since the service only gets instantiated once (during first render). Thus, if we have a public class MyService : IDisposable
{
private readonly IJSRuntime _jsRuntime;
private readonly IComponentContext _componentContext;
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
public MyService(IJSRuntime jsRuntime, IComponentContext componentContext)
{
_jsRuntime = jsRuntime;
_componentContext = componentContext;
}
public async Task DoSomethingAsync()
{
var isConnected = await _componentContext.IsConnectedAsync(_cancellationTokenSource.Token);
if (isConnected)
{
await _jsRuntime.InvokeAsync<object>("jsFunc", Array.Empty<object>(), _cancellationTokenSource.Token);
}
}
public void Dispose()
{
_cancellationTokenSource.Cancel();
_cancellationTokenSource.Dispose();
_cancellationTokenSource = null;
}
} The above code is possible with this extension method: public static class ComponentContextExtensions
{
public static async Task<bool> IsConnectedAsync(this IComponentContext componentContext, CancellationToken token = default)
{
while (!componentContext.IsConnected && !token.IsCancellationRequested)
{
await Task.Delay(50, token);
}
return componentContext.IsConnected;
}
} The above pattern for services is probably not always needed. I have one case where I think it is though: I have a service that subscribes to particular DOM events (Page Visibility API) when the first component subscribes to it. That way, one or more components needing to know about that DOM event only results in one subscription on the DOM. However, if To sum up, I think the patterns I've shown above are OK, but it would be nice if there was an easier way, perhaps with a little more help from the JSRuntime/ComponentContext APIs, to deal with the connected/disconnected scenarios. |
Thanks for reaching out. The following is out of date:
I'd suggest passing this problem to your caller. Code that calls into JS interop is going to have to be prepared for exceptions coming from the JS side. It's not expected that JS interop is bulletproof - because the JS side is untrusted code, and the connection is not guaranteed to have 100% uptime. |
Is that in P8? I just did a quick
When the
I am not sure I agree with this. It seems like my service would be violating a bunch of SOLID principles, if all the users of my service have to deal with possible JS interop code. It seems better to me to have the service take responsibility and shield the users as much as possible, and in that sense, giving the service the possibility to detect when the JS connection is down is not a bad idea. Especially in scenarios as the one I tried to describe in the end of my previous post. Thus, I hope you will continue to provide a way for services to detect if JS interop is present and available, and at least provide some guidance on how and when to the connection can be dropped, when a service should give up on it entirely, and when it might come back. |
Hi guys, i still have the error "JSRuntime must be set up correctly and must be an instance of JSRuntimeBase to use DotNetObjectRef.'" when i try to inject component in js. I don't know if it still "normal" with preview 8 ?
thanks ! |
Ryan writes in the related issue that it will be solved when this is issue is resolved, so the answer is yes. |
This is preview 9. I just checked with an example similar to yours to make sure we got it right. Unfortunately the team is working a few weeks ahead of the public release right now. Preview 8 just shipped this week, but as of today we're done with preview 9 changes 😆 I realize that's confusing to follow along with.
I realize that may not be convenient but it's unavoidable. Any time you have a pattern like You also have to keep in mind that JS interop runs arbitrary code on the client. Your client might be running JS code that you didn't write. A hostile client might stub out some JS call to never return, or return bad results. Basically anywhere you're using JS interop, you're going to want to harden that, including tasks that get cancelled or connection failures.
Can you log a new issue and include some examples (including how you want to handle disconnected cases in your APIs)? I'm happy to continue a discussion on this topic, and do more work in the future to enable you. I suspect that |
@julienGrd - please log a new issue and don't piggyback on the this API review discussion 😆 |
Done. |
Thanks. I will see what is possible and what is not when P9/RC lands. Then, I might take you up on that offer. Thanks again. |
Cool, what you should expect is that these scenarios have simpler guidance:
The main reason we added The best course of action for us was to remove it for the 3.0 release, because we didn't really think it offered any value. |
OK @rynowak, let me share my current thoughts, maybe you can fill in a few gaps :) My scenario is primarily a component library/service library setting. Ideally, since Razor components leverages dependency injection, the users of my library doesn't need to know or should know whether or not the services or components in my library uses JSInterop. If they do, they would, like you say, have to be aware and handle of the various pitfalls with JSInterop. If I later remove a components or services dependency on JSInterop (you guys might release new APIs later that means I don't need it), then all my users who have been good Razor citizens and added proper error handling for JSInterop, they would likely need to clean up their code again. With the above in mind, I will as a library author always prefer to handle the expected JSInterop errors, and wrap the unexpected JSInterop errors in a custom exception type, that generally signal to users of my library that something unexpected happened. That way, the users of my libraries can have a more simplified error handling, or none at all, and at least one that is less likely to change. Known or expected errorsThis is where you can probably help -- what are the all the things that can go wrong in JSInterop world, and how to recognize each one?
There might be other considerations for client side blazor, and I cannot claim to have any experience with SingalR directly, so there might also be some protocol-level errors that can be handled gracefully, depending on scenario/context. Thoughts? |
Summary
Microsoft.JSInterop
is a library that supports bi-directional interop between .NET and JavaScript..NET code uses the
IJSRuntime
orIJSInProcessRuntime
interface to dispatch calls to JavaScript.JavaScript runtimes call into .NET through the
DotNetDispatcher
class..NET API
TS API
The text was updated successfully, but these errors were encountered: