I’m trying to write a Blazor component that uses google maps via JSInterop. I am trying to call the “DirectionsService” class and “route” method.DirectionsService Route Method
Here is my my local method:
public async Task<DirectionResponse> Route(DirectionsRequest request)
{
var json = await _jsObjectRef.InvokeAsync<string>(
"google.maps.DirectionsService.route",
request);
var directionResponse = JsonConvert.DeserializeObject<DirectionResponse>(json);
return directionResponse;
}
This method has two parameters. One is a request object. The second is a callback function.
Method Definition
How can I accomplish this using the “InvokeAsync” method?
How do I include the callback function in order to receive the response properly?
You can't include a callback that calls directly into you .NET code.
However, you can pass a callback to another javascript function.
The javascript function is then able to call back to .NET with the standard syntax:
DotNet.invokeMethodAsync('YourApp', 'YourMethodAsync')
So... This is basically the conclusion I came up with. The code is not final, specially the reject portion but it essentially makes the call to the DirectionsService synchronous using async/await.
Here is the code:
let promise = new Promise((resolve, reject) => {
directionsService.route(dirRequest, (result, status) => {
if (status == 'OK') {
resolve(result);
}
else
{
reject(status);
}
});
});
//Wait for promise
let result = await promise;
return result;
This essentially allows me respond back to C# function that invoked this javascript function. Now the only thing left is for me to serialize the response back to the C# function, with the bits that I need.
Thanks Postlagerkarte for helping me out.
Related
I have a Web API Controller with a POST method that I want to use to refresh my data and return it. My Crawler fetchs HTML, parses it, and emits a value to SourceObservable, contained the parsed data. It's working fine once Repository.Save(accreditationData) is actually saving. But returning this accreditationDataObservable isn't working, so my Action never ends the response and Postman looks like waiting indefinitely:
[HttpPost]
public async Task<AccreditationData<AllMantainedTableRow>> Post()
{
var savedSubject = new Subject<bool>();
AllMantainedCrawler.SourceObservable.Subscribe(accreditationData => {
Repository.Save(accreditationData);
savedSubject.OnNext(true);
});
var accreditationDataObservable = AllMantainedCrawler.SourceObservable.TakeUntil(savedSubject.AsObservable());
AllMantainedCrawler.SourceSubject.OnNext(new Uri("my URL here"));
return await accreditationDataObservable;
}
Also I tried Take(1) instead TakeUntil and also returning ToTask() instead of observable, but got same result. Any suggestions?
AllMantainedCrawler.SourceSubject.OnNext(new Uri("my URL here"));
This line forces the crawler to emit. Therefore the savedSubject will also emit.
var accreditationDataObservable = AllMantainedCrawler.SourceObservable.TakeUntil(savedSubject.AsObservable());
if the saveSubject emits the above line complete the accreditationDataObservable but in the last line you try to await an already completed accreditationDataObservable.
if your remove the OnNext line and let the crawler emit you can await the accreditationDataObservable as long as it completes.
Never ever call an OnNext inside a Subscribe. There is always a way to avoid it.
With your code it seems that this would be the equivalent without the OnNext:
public async Task<AccreditationData<AllMantainedTableRow>> Post()
{
var accreditationDataObservable = AllMantainedCrawler.SourceObservable.Take(1).Do(x => Repository.Save(x));
AllMantainedCrawler.SourceSubject.OnNext(new Uri("my URL here"));
return await accreditationDataObservable;
}
However, I can't understand what the AllMantainedCrawler.SourceSubject.OnNext(new Uri("my URL here")); is doing here as you haven't provided enough detail in the question. If you can provide complete code I think I can help you.
As mentioned, I don't know how the SourceSubject relates to the SourceObservable, but If I can assume that accreditationData is just the Uri then this will work:
public async Task<AccreditationData<AllMantainedTableRow>> Post()
{
return await Observable.Start(() => Repository.Save(new Uri("my URL here")));
}
In blazor i use NavigationManager.NavigateTo(url)in order to change window location, but how can I use it to open a new tab with a specified URL without having to invoke JS on OnAfterRenderAsync()
As of 1 of June 2022 there is no way of currently doing it directly with pure Blazor, you'll need to use JSInterop. Luckily this is easy enough to do. At the top of your .razor file add
#inject IJSRuntime JSRuntime;
And then use it like so
await JSRuntime.InvokeAsync<object>("open", url, "_blank");
Note that the IJSRuntime interface itself only provides a InvokeAsync<TValue> method, the JSRuntimeExtensions class provides an extension method for IJSRuntime to directly invoke a method without a return value: InvokeVoidAsync
await JSRuntime.InvokeVoidAsync("open", url, "_blank");
Formerly, this code worked.
await _jsRuntime.InvokeVoidAsync("open", new object[2] { url, "_blank" });
At present, it now results in an uncaught exception:
> "TypeError: Converting circular structure to JSON
I found this behavior explained here (https://github.com/dotnet/aspnetcore/issues/16632):
This is because window.open returns a WindowProxy object (see
https://developer.mozilla.org/en-US/docs/Web/API/Window/open).
WindowProxy is not JSON-serializable, so can't be used as a return
value to .NET code.
To fix this, don't call window.open directly, but instead call a JS
function of your own that either returns nothing or returns something
that is JSON-serializable.
Per the above recommendation, I added the following to index.html:
<script>
window.blazorOpen = (args) => {
window.open(args);
};
</script>
And modified my C# code-behind call to pass the window arguments:
await _jsRuntime.InvokeVoidAsync("blazorOpen", new object[2] { url, "_blank" });
Effectively we now avoid the issue by discarding the WindowProxy object returned by window.open, which was formerly returned to InvokeVoidAsync and .NET was attempting to (unsuccessfully) process.
You will get TypeError: Converting circular structure to JSON when using
await _jsRuntime.InvokeVoidAsync("open", new object[2] { url, "_blank" });
or
await _jsRuntime.InvokeAsync<object>("open", url, "_blank");
This is because window.open returns a WindowProxy object (see
https://developer.mozilla.org/en-US/docs/Web/API/Window/open).
WindowProxy is not JSON-serializable, so can't be used as a return value to .NET code.
Taken from see here.
To get around this w/o using a javascript function, I use the following
await JSRuntime.InvokeVoidAsync("eval", $"let _discard_ = open(`{url}`, `_blank`)");
Just use a regular link
#UrlDescription
Per the latest change in API here is the working solution where you can add any custom object attribute to the URL as shown below:
/// <summary>
/// Show Link
/// </summary>
/// <returns></returns>
private async Task ShowLink()
{
if (this.selectedItem != null)
{
string url = $"https://example.com/sites/" + this.selectedItem.Id + "/SitePages/Reports/somepage.aspx";
//NavigationManager.NavigateTo(url, false);//opens the new page on same browser tab
string[] values = { url, "_blank" };
CancellationToken token = new CancellationToken(false);
await JSRuntime.InvokeAsync<object>("open", token, values);//opens the link in new browser tab
}
}
The best way is to:
<NavLink class="MyCssClass"
target="_blank"
href="https://www.google.com">
Google in new window
</NavLink>
Trying to get a basic model of my functions working.
Frontend (Angular): the body data will be JSON of this class:
class BackendParams {
listValues: any;
constructor( netList: any ) {
this.listValues = netList;
}
}
Then a function creates the class object:
const params = new BackendParams(list);
then calls a (still in the front-end) Angular function to send it to the backend:
onClickTest(params: any) {
const A = 1;
const B = 2;
const NameString = 'test';
const formData = new FormData();
formData.append('NetworkList', JSON.stringify(params));
let url = `${this.url}/CalibrationModel/1/2/SampleTest/TestModel`;
this.http.post(url, formData).subscribe(
(data) => {
console.log(data);
});
}
BACKEND:
class BackendParams
{
List<Constituent> listNetworkConstituents;
}
The following is image of the source code so you can see the syntax red underlines
I don't think the two are related (or are they?) but referencing the body parameters is of course essential.
And, of course, let me know anything else you see that might be a problem.
Thanks for your help. I learn a lot from you guys.
Yogi
If your method were marked as async then returning a bool would work. So public async Task<bool>..., but that isn't the case. However, as #JohnD91 said, if you're not using await in your method, it doesn't need to be async and it also doesn't need to return a Task.
The other problem is that parmsJSON is misspelled, because it's defined in the method signature as paramsJSON. You're missing the other a.
I need to integrate a third party's Web API methods into a WCF service.
The WCF Service will be called by another external party, and they expect the call and return to be synchronous.
I am able to get results from the API with the usual RestClient.ExecuteAsync.
I put together the following for synchronous calls:
public static List<Books> GetSyncBooks(int companyId)
{
var response = GetSynchronousBooks(companyId);
var content = response.Result.Content;
List<Books> result = new List<Books>();
return Helpers.JSONSerialiser.Deserialize<BookList>(content);
}
async private static Task<IRestResponse> GetSynchronousBooks(int companyId)
{
var request = BuildGETRequest("Book", companyId);
var response = await RestSharpHelper.ExecuteSynchronousRequest(request);
return response;
}
public static Task<IRestResponse> ExecuteSynchronousRequest(RestRequest request)
{
var client = new RestClient(BaseUrl);
client.AddHandler("application/json", new RestSharpJsonDotNetDeserializers());
var tcs = new TaskCompletionSource<IRestResponse>(TaskCreationOptions.AttachedToParent);
client.ExecuteAsync(request, (restResponse, asyncHandle) =>
{
if (restResponse.ResponseStatus == ResponseStatus.Error)
tcs.SetException(restResponse.ErrorException);
else
tcs.SetResult(restResponse);
});
return tcs.Task;
// BREAKPOINT here shows TASK value as
// Id = 1, Status = WaitingForActivation, Method = "{null}", Result = "{Not yet computed}"
}
The problem, however, is that I never get a result using this. The response content is always null. What am I doing wrong?
EDIT: Thanks Stephen. I have seen your name on some of the questions here on this subject: your score seems to indicate you know your way around this. I have indeed implemented the wcf service calls as you indicated based on your answer at another question. Can I ask you a related follow-up question? How scalable are async WCF service calls like this example? Would it "just work" for multiple simultaneous calls in the range of 10 to 100 per second, ignoring the processing overhead downstream?
I assume that your WCF service is hosted in ASP.NET.
Your problem is here: response.Result. I explain this deadlock situation on my blog. In summary, await will capture the current "context" (in this case, an ASP.NET request context) and will use that to resume the async method. However, the ASP.NET request context only allows one thread at a time, so if the request thread is blocked (calling response.Result), then the async method can never continue and you get a deadlock.
The solution is to correct this misunderstanding:
The WCF Service will be called by another external party, and they expect the call and return to be synchronous.
Since you're dealing with a client/server scenario, you don't have to make it synchronous. The asynchrony of the client is completely independent from the asynchrony of the server.
So, just implement your WCF service asynchronously:
public static Task<List<Books>> GetBooksAsync(int companyId)
{
var response = await GetBooksAsync(companyId);
var content = response.Content;
List<Books> result = new List<Books>();
return Helpers.JSONSerialiser.Deserialize<BookList>(content);
}
The client can still call it synchronously if they wish to.
I was working with Asp MVC 3, and in my application was created Async controller with some methods like:
public void ActionAsync()
{
AsyncManager.OutstandingOperations.Increment();
AsyncManager.Parameters["someResult"] = GetSomeResult();
AsyncManager.OutstandingOperations.Decrement();
}
public JsonResult ActionCompleted(SometResultModel someResult)
{
return Json(someResult, JsonRequestBehavior.AllowGet);
}
And now, when I'm working with MVC4 and Web Api I need to create controller with async actions like in mvc 3. At current time it's looks like:
public Task<HttpResponseMessage> PostActionAsync()
{
return Task<HttpResponseMessage>.Factory.StartNew( () =>
{
var result = GetSomeResult();
return Request.CreateResponse(HttpStatusCode.Created, result);
});
}
Is it a good idea to make async actions in web api like this or there some better way is exist?
UPD. Also, if I will use
public async Task<HttpResponseMessage> ActionAsync()
{
var result = await GetSomeResult();
return Request.CreateResponse(HttpStatusCode.Created, result);
}
will this full action work in background thread? And how to make my GetSomeResult() function be awaitable? It's return that Task<HttpResponseMessage> is not awaitable.
There is a big difference with your original action in MVC 3 where you are basically releasing the client after the ActionAsync method is invoked (The client thread is released, and it has to call the ActionCompleted action afterward to get the results). If that's what you are looking for, you need to implement async code with tasks on the client side.
Your second version is to make the server code async but the client thread will still wait for a response synchronously. await GetResult will make the server thread to return to the ASP.NET thread pool until the GetResult method returns something, so that thread can be reused with another request. It does not have anything to do with background work. If you want to use a fire and forget approach, you need to use the Task.Factory.StartNew(() => your code) or ThreadPool.QueueUserWorkItem(() => your code)