Use injected service from JS code to static Blazor method - c#

I have an app that uses a jQuery lib to show 'confirmation' dialogs.
When a user clicks 'OK' or 'Cancel' on the dialog, then first a callback is made to my Javascript code. From there I do a call to a Blazor method, based on the decision the user made.
This all looks something like this:
My js code:
$('.alert-yesno).on('click', function() {
// For simplicity I immediately call the Blazor method
DotNet.invokeMethodAsync('[Assembly name]', 'DoSomething')
.then(data => {
// do something...
});
});
Blazor page:
#inject MyService MyService
<button class="alert-yesno">Show dialog</button>
#code
{
[JSInvokable]
public static async Task DoSomething()
{
// How to use the non static MyService here...?
}
}
This all works fine. The Javascript part calls my Blazor method. But how can I use MyService from there? This is injected into the page. Which is good, because it makes use of dependency injection.
I don't want to create a new instance everytime from within the static method, because that way I loose all the auto injected dependencies.
How can I solve this issue?

See https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interop?view=aspnetcore-3.0#instance-method-call for a more complete example, but you can pass a reference to the the .net instance to the javascript code by calling
DotNetObjectReference.Create(myInstance);
Then the javascript code can call the instance method on that object. Since the component (and thus the service which is injected into it) has a potentially different lifespan than the jquery click handler, this might cause some other issues, but the link above should be a good starting point.

Related

Blazor JSInterop static vs instance C# methods

Could somebody please let me know how static methods work in regard to calling C# methods from JavaScript using JS interop?
If I have a static method in the code section of a component that references objects in that component, and there are multiple instances of that component on a page, will all instances be affected by the method call?
Or even worse, will this act as a Singleton on my server causing updates to every single client instance of that component?
I'm currently developing an audio recording system that cycles through phrases marked in text, and whenever JavaScript detects 5 frames of silence in the media stream, I want to call a C# method that will highlight the next section of text by changing its CSS class.
If you want to change some styles in your component, you definitely not want to use a static method. Because a static method could not interact with the instance of the component - as you cannot use the this keyword in a static method.
What I think you want to do is first, in C# side, create a classic method in your component C# code. For example create:
public void ChangeStyle()
{
this.ClassToDisplay = DoSomeManipulation();
}
Then, JS will need the instance of the the component. So you can do something like that: (I use the OnAfterRenderAsync because it is shown in Microsoft documentation, but the goal is to do it once)
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
DotNetObjectReference<MyComponent>? dotNetHelper = DotNetObjectReference.Create(this);
await JS.InvokeVoidAsync("JSFile.setDotNetHelper", dotNetHelper);
}
}
Then in JS side, you will need the setDotNetHelper method, with the following content:
class JSFile
{
static dotNetHelper;
static setDotNetHelper(value)
{
JSFile.dotNetHelper = value;
}
}
Now you are ready, you can call whenever you want the ChangeStyle method of your component. To do so, use this instruction in JS:
await dotNetHelper.invokeMethodAsync('ChangeStyle');
Et voilĂ . I am available for clarifications or questions, have a good one!
// Dylan

How do I inject NavigationManager in a Quartz.NET job?

I have a Blazor server-side application that uses Quartz.NET to run a scheduled job. The job merely calls an injected helper class...
public class DownloadEmailsJob : IJob {
private readonly EmailDownloadHelper _edh;
public DownloadEmailsJob(EmailDownloadHelper edh) =>
_edh = edh;
public async Task Execute(IJobExecutionContext context) =>
await _edh.DownloadEmails();
}
EmailDownloadHelper uses injection to get various services, such as the DbContext, etc. That part is all working fine.
I now have a need to call a SignalR hub method from inside the helper class. Following the code in Microsoft's documentation, in order to create the hub connection, I need to do something like this...
_hubConnection = new HubConnectionBuilder()
.WithUrl(_navigationManager.ToAbsoluteUri(EmailNotifierHub.Endpoint))
.Build();
...where EmailNotifierHub contains the following line...
public const string Endpoint = "/EmailHub";
This means that I need an instance of NavigationManager in my job. I tried injecting it in the constructor (as I do with the other dependencies in this class), but when it tries to create the hub connection, I get an exception...
System.InvalidOperationException' in Microsoft.AspNetCore.Components.dll
'RemoteNavigationManager' has not been initialized
The instance of NavigationManager is injected fine, meaning it's not null, and the exception only comes when it tries to call ToAbsoluteUri
I've done this sort of thing before in injected classes, but not in a Quartz.NET job, which is where I suspect the problem lies.
Anyone able to explain what's going wrong, and how I fix it? Thanks
Your main issue is that NavigationManager does not work properly outside a Circuit, and Quartz is (presumably) set up once for the application. The DI, therefore, does not have a scope in which it can provide a NavigationManager.
In similar situations, I usually resign to setting the base url of the site in appsettings.json, and use that to construct the absolute urls I need.

What causes if/else expressions to be re-evaluated?

Working off the Blazor hosted template, in my MainLayout.cshtml, I have something like:
#inject UserInfo userInfo
#if(userInfo.Name == "bob")
{
<div class="login-panel">
<p>Hi Bob!</p>
</div>
}
else
{
<LoginPanel />
}
and then in a separate Login.cshtml:
#inject UserInfo userInfo
async void Login()
{
userInfo.Name = "bob";
}
But logging in doesn't cause a change to the 'login-panel' in MainLayout.cshtml. I was able to get this to work if everything is in a single file and I am only checking for whether a particular variable is null or not (see here), so I assume the Blazor framework has specific criteria which illicit a page to re-evaluate such blocks. Is this accurate? If so, what is the best way to force this re-evaluation?
Edit: I played around with it a bit more, eventually discovered the StateHasChanged protected member function, which I assume is called after Init has completed, hence why the previous example works. I think a working solution is to have my UserInfo object implement INotifyPropertyChanged, and then register a change handler inside MainLayout.cshtml to call StateHasChanged() for views that need to update when login status changes.
Yes. Blazor uses something called StateHasChanged just like INotifyPropertyChanged.
However the difference is, in XAML frameworks the XAML will only update those properties which has called INotifyPropertyChanged, but in Razor when StateHasChanged is called the UI framework will refresh the whole UI tree of the component (at least as of now) the element is residing.
Then who has called the StateHasChanged if its not done by you. When an element gets an input (You are clicking a button) the blazor will automatically set the StateHasChanged under the hood.
More reads"
https://github.com/aspnet/Blazor/issues/409
https://learn-blazor.com/pages/data-binding/
https://github.com/aspnet/Blazor/issues/359
https://github.com/aspnet/Blazor/issues/407

Any downfall (or better alternative) to run initialization code (HTTPHandler - URL association) using a static constructor?

Summary :
I have a DLL that hosts a class library. The library is used by an ASP.NET website. I need some code (initialization) to be run when the library is used. I have placed the code on the static constructor of one of the classes, which most likely will be used. It runs right now, but I was wondering
is there a better place to put this code? Some sort of DLL init
method?
are there any downfalls? If the class is never used, will the code
run anyways?
Details:
I have a DLL that hosts a class library that implements ECommerce to be used on ASP.NET websites. It contains controls and logic objects specific to my client. As part of it, it contains an HTTPhandler that handles AJAX calls to the library. The url that is associated with the Handler has to be registered. I have done this on the static constructor of one of the classes.
using System.Web.Routing;
class CMyClass {
static CMyClass() {
RouteTable.Routes.Insert(0, new Route("myapi/{*pathinfo}", new CMyHTTPHandlerRouter()));
}
}
This works right now. The site that uses the DLL does not have to register the route, which is very convenient. I was wondering, though:
is there a better place to register routes from a DLL? Or a better
way to associate a handler with a URL, directly from the DLL, so it
is always registered when the DLL is used.
are there any downfalls? If CMyClass is never used, will the code run anyways?
I can answer your second question: the static constructor will only run if you somehow interact with CMyClass. In other words, it's run on demand, not eagerly when you e.g. access the DLL.
Routes are to be construed as "application code". Meaning once it is "compiled" you cannot make changes to it. This is by design. Application_Start is the place where routes are normally registered.
I would normally abide by this convention. But my reusable logic (i.e. inside any publicly exposed method in the dll) should ensure that the routes are registered, else throw up an error. This is how the end developers know that they aren't using your component right. And if "it" knows the routes are registered it can safely go and execute the actual stuff.
I'd use a static boolean variable to accomplish that.
public class MyMvcSolution
{
public static bool Registered {get; set; }
static MyMvcSolution(){ Registered = false; }
public static void DoSomethingImportant()
{
if(Registered)
{
//do important stuff
}
else
throw new InvalidOperationException("Whoa, routes are not registered!");
}
//this should be called in the Application_Start
public static void Init()
{
RouteTable.Routes.Insert(0, new Route("myapi/{*pathinfo}", new CMyHTTPHandlerRouter()));
Registered = true;
}
}
I believe the above solution will kind of do.
There is an alternative strategy. We want to add routes "dynamically". This talks about forcing the BuildManager to register routes you mention is a .cs file. This file isn't "compiled" as part of the application; there will be a *.cs file in your application somewhere. You will make an assembly out of it on-the-fly, and from that force the buildmanager to register. There is also a mechanism to "edit" the routes once that file changes too. I'll leave it to you to explore this. Deep but interesting stuff.

Calling ASP.NET page non-static methods from static web methods

I am in need of calling a non-static method in the active(current) asp page that the user is on by using a static WebMethod. How can I do this?
Both of these methods are within the ASP page's cs file.
public void NormalMethod()
{
txtFindingNum.Text = "Ajax is da bomb";
}
[WebMethod]
public static void MyWebMethod()
{
// This is the part I need help with...
DoIt();
}
You can't do it. It doesn't even make sense.
The instance methods of the page are about a specific instance of the page. When you're in the static web method (page method), there is no instance of the page.
If you could call the instance method from the web method, that would mean that the instance method should be a static method. Can you just add static to that method and have it still work? If not, then it depends on the particular instance of the page, and you simply can't call it when there is no instance.
Note that a page instance exists only during the HTTP request that it is serving. By the time your client-side code is calling the web service, that HTTP request is already over, and the page instance is gone.
You can't do it, but the txtFindingNum.Text field is input, and you can change it in client side (it also keep the change in the server after postback), with js or jq, like this:
$("#<%=txtFindingNum.ClientID%>").val("Ajax is da bomb");

Categories

Resources