I want to pass a javascript object that is already existed in a javascript file as a parameter to InvokeVoidAsync method :
myJs.js :
var myObject= {a:1,b:2};
MyComponent.razor :
#inject IJSRuntime js
#code {
private async Task MyMethod()
{
await js.InvokeVoidAsync("someJsFunction", /*how to add myObject here? */);
}
}
How to pass myObject to the someJsFunction function?
(Note that the function may also should accept c# objects too)
I finally found the trick, simply use the eval function :
private async Task MyMethod()
{
object data = await js.InvokeAsync<Person>("eval","myObject");
await js.InvokeVoidAsync("someJsFunction", data );
}
in the example below, "someJsFunction" accepts an object of type Person :
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
in my javascript file
var myObject = { name: "Pat", age: 38 }; //a person
//this function provides access to myObject
window.getMyObject = () =>
{
return myObject;
};
window.someJsFunction = (person) =>
{
console.log(person);
}
In the component :
#page "/"
#inject IJSRuntime js
<div>
<button #onclick="MyMethod" >Click</button>
</div>
#code{
Person Person;
protected async override Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
//retrieve myOject and assign to the field Person
Person = await js.InvokeAsync<Person>("getMyObject");
}
}
private async Task MyMethod()
{
if(Person != null)
//pass Person to someJsFunction
await js.InvokeVoidAsync("someJsFunction", Person);
}
}
You could define a new JS function
someJsFunctionWithObj(){
someJsFunction(myObject);
}
Then call that instead...
await js.InvokeVoidAsync("someJsFunctionWithObj");
Or create a JS function that returns the object -> call that from C# to get it -> then pass it back, but that seems quite convoluted.
It sounds like probably you should rethink your design really... Maybe if you explained why you are actually trying to do this we can suggest better idea?
Related
I have a problem that I have had with blazor server side for a long time. I have an edit form for an EF model but I cannot figure out how to handle cancelation. This is my code:
#inject NavigationManager nav
<h3>Edit #_tmpCustomer.FullName</h3>
<EditForm Model=#_tmpCustomer>
<DataAnnotationsValidator/>
<ValidationSummary/>
<label>First Name</label>
<InputText #bind-Value=_tmpCustomer.FName/>
<br/>
<label>Last Name</label>
<InputText #bind-Value=_tmpCustomer.LName/>
<br/>
<label>Phone Number</label>
<InputText #bind-Value=_tmpCustomer.PhoneNumber/>
<br/>
<button class="btn btn-primary" #onclick=UpdateCustomer>Save</button>
<button class="btn btn-primary" #onclick=#(() => NavTo("/customers"))> Cancel</button>
</EditForm>
#code {
[Parameter]
public int customerId { get; set; }
private Customer _customer { get; set; }
private Customer _tmpCustomer { get; set; }
protected override async Task OnInitializedAsync()
{
await LoadCustomer(customerId);
}
private async Task LoadCustomer(int customerId)
{
_customer = await _customerRepo.GetCustomerFromId(customerId);
_tmpCustomer = (Customer)_customer.Clone();
}
private async Task UpdateCustomer()
{
_customer = _tmpCustomer;
await _customerRepo.Update(_customer);
await NavTo("/customers");
}
private async Task NavTo(string uri)
{
nav.NavigateTo(uri);
}
}
public class Customer
{
...
public virtual object Clone()
{
return this.MemberwiseClone
}
}
public class CustomerRepo
{
private ApplicationDbContext _context;
public CustomerRepo(ApplicationDbContext context)
{
_context = context;
}
public async Task<List<Customer>> GetAllCustomers()
{
return _context.Customers.ToList();
}
public async Task<Customer> GetCustomerFromId(int customerId)
{
return _context.Customers.FirstOrDefault(c => c.Id == customerId);
}
public async Task Create(Customer customer)
{
_context.Add(customer);
await _context.SaveChangesAsync();
}
public async Task Update(Customer customer)
{
_context.Update(customer);
await _context.SaveChangesAsync();
}
}
The problem is that I cannot have 2 instances of the same EF model tracked at the same time, I could detach it but I don't think that's the clean or right way to do this.
What would be the correct way to cancel an edit form in blazor server?
Thanks :)
The problem is that the same DbContext is used for the entire user session. This is explained in the Database Access section of the Blazor Server and EF Core docs. You'll have to manage the DbContext's lifetime yourself.
The documentation example shows registering a DbContextFactory with :
builder.Services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db"))
Which is similar to what you'd use with AddDbContext.
Then the page injects the DbContextFactory instead of the actual DbContext and creates a new DbContext instance in OnInitialized
#implements IDisposable
#inject IDbContextFactory<ContactContext> DbFactory
.....
protected override async Task OnInitializedAsync()
{
Busy = true;
try
{
Context = DbFactory.CreateDbContext();
if (Context is not null && Context.Contacts is not null)
{
var contact = await Context.Contacts.SingleOrDefaultAsync(c => c.Id == ContactId);
if (contact is not null)
{
Contact = contact;
}
}
}
finally
{
Busy = false;
}
await base.OnInitializedAsync();
}
The page has to implement IDisposable so it can dispoce the Context instance when you navigate away from the page:
public void Dispose()
{
Context?.Dispose();
}
Explanation
In web applications the Dependency Injection scope is the controller action, which means scoped services are created when a new action is executed and disposed when it completes. This works nicely with DbContext's Unit-of-Work semantics, as changes are cached only for a single action. "Cancelling" means simply not calling SaveChanges.
Blazor Server behaves like a desktop application though, and the scope is the entire user circuit (think of it as a user session). You'll have to create a new DbContext for each unit of work.
One way to do this is to just create a new DbContext explicitly, ie new ApplicationDbContext(). This works but requires hard-coding the configuration.
Another option is to inject a DbContextFactory instead of the ApplicationDbContext into your CustomerRepo or page, and create the DbContext instance as needed. Note that CustomerRepo itself is now alive for the entire user session, so you can't depend on it working as just a wrapper over DbContext.
What would be the correct way to cancel an edit form in blazor server?
I do the following:
All data classes are immutable records. You can't change them.
I create an edit class for the data class and read in the data from the record.
This class does validation and state management. For instance you can create a record from the edit class at any time and do equality check against the original to see if your record state is dirty.
I create a new record from the edit class to submit to EF to update the database.
I use the DBContext factory and apply "Unit of Work" principles.
Canceling the edit is then simple, you don't save!
First change over to using the DBContextFactory. The MSSQL one looks like this:
builder.Services.AddDbContextFactory<ApplicationDbContext>(options => options.UseSqlServer("ConnectionString"), ServiceLifetime.Singleton);
And update your Customer Repo to use this.
public class CustomerRepo
{
private IDbContextFactory<ApplicationDbContext> _dbContextFactory;
public CustomerRepo(IDbContextFactory<ApplicationDbContext> factory)
=> _dbContextFactory = factory;
And the Create method now becomes:
public async ValueTask Create(Customer customer)
{
using var context = _dbContextFactory.CreateDbContext();
_context.Add(customer);
// can check you get 1 back
await _context.SaveChangesAsync();
}
Change Customer to a record
public record Customer
{
public int Id { get; init; }
public string? Name { get; init; }
}
Create an edit class for Customer
public class DeoCustomer
{
private Customer _customer;
public int Id { get; set; }
public string? Name { get; set; }
public DeoCustomer(Customer customer)
{
this.Id = customer.Id;
this.Name = customer.Name;
_customer = customer;
}
public Customer Record =>
new Customer
{
Id = this.Id,
Name = this.Name
};
public bool IsDirty =>
_customer != this.Record;
}
And then your edit form can look something like this:
// EditForm using editRecord
#code {
private DeoCustomer? editRecord = default!;
private async Task LoadCustomer(int customerId)
{
var customer = await _customerRepo.GetCustomerFromId(customerId);
editRecord = new DeoCustomer(customer);
}
private async Task UpdateCustomer()
{
if (editRecord.IsDirty)
{
var customer = editRecord.Record;
await _customerRepo.Update(customer);
}
await NavTo("/customers");
}
}
I haven't actually run the code so there may be some typos!
I'm trying to update an object with another object asynchronously. I'm trying to get the CustomerId value from Statues and then use it to call a specific customer and pass those values into PreviousStatuses. Then update the StatusToAdd with PreviousStatuses. If I pass Statues to StatusToAdd the values update. However, it's the wrong customer id. That's why I'm using PreviousStatuses.
This is the error I get:
crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: Object reference not set to an instance of an object.
System.NullReferenceException: Object reference not set to an instance of an object.
at DSPDRewrite.Pages.Popups.AddStatusComponent.UpdateValues(String id)
at DSPDRewrite.Pages.Popups.AddStatusComponent.OnInitializedAsync()
at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)
[Parameter]
public int CustomerId { get; set; }
[CascadingParameter]
public Task<AuthenticationState> AuthState { get; set; }
public Status Statuses;
public Status PreviousStatuses;
//IList<Status> PreviousStatuses;
public Dspd1056Status StatusToAdd = new Dspd1056Status();
public Customer customer;
public int AccountStatusId = 0;
protected override async Task OnInitializedAsync()
{
Statuses = await dataService.Get1056StatusById(CustomerId);
//int id = Int32.Parse(Statuses.CustomerId);
// Statuses = await dataService.Get1056StatusById(id);
Console.WriteLine(Statuses.CustomerId);
await UpdateValues(Statuses.CustomerId);
}
async Task UpdateValues(string id)
{
PreviousStatuses = await dataService.Get1056StatusById(Int32.Parse((id)));
StatusToAdd.AccountCurrent = PreviousStatuses.AccountCurrent;
StatusToAdd.StartDate = PreviousStatuses.StartDate;
StatusToAdd.EndDate = PreviousStatuses.EndDate;
StatusToAdd.Units = PreviousStatuses.Units;
StatusToAdd.Ppc = PreviousStatuses.Ppc;
StatusToAdd.EndStatus = PreviousStatuses.EndStatus;
StatusToAdd.ContinuallyFunded = PreviousStatuses.ContinuallyFunded;
StatusToAdd.AnnualUnits = PreviousStatuses.AnnualUnits;
StatusToAdd.Elg = PreviousStatuses.Elg;
StatusToAdd.ReceiptDate = PreviousStatuses.ReceiptDate;
StatusToAdd.RahTripsFunded = PreviousStatuses.RahTripsFunded;
StatusToAdd.Rate = PreviousStatuses.Rate;
StatusToAdd.AccountTotal = PreviousStatuses.AccountTotal;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
if (CustomerId != 0)
{
customer = await dataService.GetCustomerById((int)CustomerId);
StateHasChanged();
}
}
}
A few things to consider:
OnInitializedAsync
Can your .Get1056StatusById() method return null (e.g. if CustomerId is not found)? If so that appears to be the cause of the error: your code calls
await UpdateValues(Statuses.CustomerId);
That suggests Statuses is null. I would add a null check to this code to handle that case. Not sure why the Console.WriteLine didn't throw the null reference first, I assume the sample code didn't have it when the error was thrown.
A few other points: fields and methods in Components should normally be private or protected. If you are going to expose outside the Component they should be properties. Methods are rarely called outside a component. My suggested changes would be:
[Parameter] public int CustomerId { get; set; }
[CascadingParameter] public Task<AuthenticationState> AuthState { get; set; }
Status Statuses;
Status PreviousStatuses;
Dspd1056Status StatusToAdd = new Dspd1056Status();
Customer customer;
int AccountStatusId = 0;
protected override async Task OnInitializedAsync()
{
Statuses = await dataService.Get1056StatusById(CustomerId);
if(Statuses != null)
{
Console.WriteLine(Statuses.CustomerId);
await UpdateValues(Statuses.CustomerId);
}
}
async Task UpdateValues(string id)
{
PreviousStatuses = await dataService.Get1056StatusById(Int32.Parse((id)));
// rest...
Your component HTML should obviously check for a null Statuses before it tries to render:
#if(Statuses != null)
{
<p>Customer is #Statuses.CustomerId</p>
}
This is such a frequently used pattern in Blazor apps I usually use a <NotNull> component:
https://gist.github.com/conficient/eab57ade2587104d71ac6f26ddfd4865
{
public class MyClass
{
// all the call to GetData() of apiHelper should pass through this method
public async Task<T> InitiateAPICallAsync<T>(Task<T> apiCall) where T : BaseResponse
{
var response = await apiCall;
// some common code work using response data
return response;
}
public async void MyFunc()
{
var helper = new APIHelper("1", "2");
//
var response1 = await InitiateAPICallAsync(helper.GetData<Response1>()); // correct way
var rewponse2 = await helper.GetData<Response1>(); // incorrect way, need to show warning
}
}
public class APIHelper
{
public APIHelper(string a, string b)
{
// some code
}
public async Task<T> GetData<T>()
{
await Task.Delay(1000); // network call
// other code
return default;
}
}
public class Response1 : BaseResponse { }
public class Response2 : BaseResponse { }
public class BaseResponse { }
}
in my application MyClass, there is a method named InitiateAPICallAsync(). All call to the GetData() method of APIHelper must be pass through this method. I need to showing warning, if GetAsync() method called directly without passing through InitiateAPICallAsync.
Note: It is a sample code snippet, where in my real time project the APIHelper represents a Connectivity library. and MyClass represents another library named service.
How to show warning for a method if it is called directly in c#
Using CallerMemberName attribute is core thread of the following solution, thanks for Fumeaux's comment, I tried place CallerMemberName attribute above GetData method directly to get the caller, but the result is MyFunc but not InitiateAPICallAsync. So I tried use delegate as the InitiateAPICallAsync parameter that could make sure GetData will called by InitiateAPICallAsync. The following code has been simplified.
public delegate Task<int> PrintCaller([CallerMemberName] string Caller = null);
public class MyClass
{
public async Task<string> InitiateAPICallAsync(PrintCaller apiCall)
{
var response = await apiCall();
return "Test";
}
public async void MyFunc()
{
var helper = new APIHelper();
var str1 = await InitiateAPICallAsync(new PrintCaller(helper.GetData));
var str2 = await helper.GetData();
}
}
public class APIHelper
{
public async Task<int> GetData([CallerMemberName] string Caller = null)
{
if (Caller == "InitiateAPICallAsync")
{
// do some thing
}
else
{
//Show Warning
var dialog = new MessageDialog("Waring!!! Please don't call it directly");
await dialog.ShowAsync();
}
return 0;
}
}
How can I add a delay to an event (OnInput) in Blazor ?For example, if a user is typing in the text field and you want to wait until the user has finished typing.
Blazor.Templates::3.0.0-preview8.19405.7
Code:
#page "/"
<input type="text" #bind="Data" #oninput="OnInputHandler"/>
<p>#Data</p>
#code {
public string Data { get; set; }
public void OnInputHandler(UIChangeEventArgs e)
{
Data = e.Value.ToString();
}
}
Solution:
There is no single solution to your question. The following code is just one approach. Take a look and adapt it to your requirements. The code resets a timer on each keyup, only last timer raises the OnUserFinish event.
Remember to dispose timer by implementing IDisposable
#using System.Timers;
#implements IDisposable;
<input type="text" #bind="Data" #bind:event="oninput"
#onkeyup="#ResetTimer"/>
<p >UI Data: #Data
<br>Backend Data: #DataFromBackend</p>
#code {
public string Data { get; set; } = string.Empty;
public string DataFromBackend { get; set; } = string.Empty;
private Timer aTimer = default!;
protected override void OnInitialized()
{
aTimer = new Timer(1000);
aTimer.Elapsed += OnUserFinish;
aTimer.AutoReset = false;
}
void ResetTimer(KeyboardEventArgs e)
{
aTimer.Stop();
aTimer.Start();
}
private async void OnUserFinish(Object? source, ElapsedEventArgs e)
{
// https://stackoverflow.com/a/19415703/842935
// Call backend
DataFromBackend = await Task.FromResult( Data + " from backend");
await InvokeAsync( StateHasChanged );
}
void IDisposable.Dispose()
=>
aTimer?.Dispose();
}
Use case:
One example of use case of this code is avoiding backend requests, because the request is not sent until user stops typing.
Running:
This answer is the middle ground between the previous answers, i.e. between DIY and using a full-blown reactive UI framework.
It utilizes the powerful Reactive.Extensions library (a.k.a. Rx), which in my opinion is the only reasonable way to solve such problems in normal scenarios.
The solution
After installing the NuGet package System.Reactive you can import the needed namespaces in your component:
#using System.Reactive.Subjects
#using System.Reactive.Linq
Create a Subject field on your component that will act as the glue between the input event and your Observable pipeline:
#code {
private Subject<ChangeEventArgs> searchTerm = new();
// ...
}
Connect the Subject with your input:
<input type="text" class="form-control" #oninput=#searchTerm.OnNext>
Finally, define the Observable pipeline:
#code {
// ...
private Thing[]? things;
protected override async Task OnInitializedAsync() {
searchTerm
.Throttle(TimeSpan.FromMilliseconds(200))
.Select(e => (string?)e.Value)
.Select(v => v?.Trim())
.DistinctUntilChanged()
.SelectMany(SearchThings)
.Subscribe(ts => {
things = ts;
StateHasChanged();
});
}
private Task<Thing[]> SearchThings(string? searchTerm = null)
=> HttpClient.GetFromJsonAsync<Thing[]>($"api/things?search={searchTerm}")
}
The example pipeline above will...
give the user 200 milliseconds to finish typing (a.k.a. debouncing or throttling the input),
select the typed value from the ChangeEventArgs,
trim it,
skip any value that is the same as the last one,
use all values that got this far to issue an HTTP GET request,
store the response data on the field things,
and finally tell the component that it needs to be re-rendered.
If you have something like the below in your markup, you will see it being updated when you type:
#foreach (var thing in things) {
<ThingDisplay Item=#thing #key=#thing.Id />
}
Additional notes
Don't forget to clean up
You should properly dispose the event subscription like so:
#implements IDisposable // top of your component
// markup
#code {
// ...
private IDisposable? subscription;
public void Dispose() => subscription?.Dispose();
protected override async Task OnInitializedAsync() {
subscription = searchTerm
.Throttle(TimeSpan.FromMilliseconds(200))
// ...
.Subscribe(/* ... */);
}
}
Subscribe() actually returns an IDisposable that you should store and dispose along with your component. But do not use using on it, because this would destroy the subscription prematurely.
Open questions
There are some things I haven't figured out yet:
Is it possible to avoid calling StateHasChanged()?
Is it possible to avoid calling Subscribe() and bind directly to the Observable inside the markup like you would do in Angular using the async pipe?
Is it possible to avoid creating a Subject? Rx supports creating Observables from C# Events, but how do I get the C# object for the oninput event?
I have created a set of Blazor components. One of which is Debounced inputs with multiple input types and much more features. Blazor.Components.Debounce.Input is available on NuGet.
You can try it out with the demo app.
Note: currently it is in Preview. Final version is coming with .NET 5. release
I think this is the better solution for me, I used it for searching.
Here's the code that I used.
private DateTime timer {
get;
set;
} = DateTime.MinValue;
private async Task SearchFire(ChangeEventArgs Args) {
if (timer == DateTime.MinValue) {
timer = DateTime.UtcNow;
} else {
_ = StartSearch(Args);
timer = DateTime.UtcNow;
}
}
private async Task StartSearch(ChangeEventArgs Args) { //2000 = 2 seconeds you can change it
await Task.Delay(2000);
var tot = TimeSpan.FromTicks((DateTime.UtcNow - timer).Ticks).TotalSeconds;
if (tot > 2) {
if (!string.IsNullOrEmpty(Args.Value.ToString())) { //Do anything after 2 seconds.
//reset timer after finished writhing
timer = DateTime.MinValue;
} else {}
} else {}
}
You can avoid bind the input. Just set #oninput
<Input id="theinput" #oninput="OnTextInput" />
#code {
public string SomeField { get; set; }
public void OnTextInput(ChangeEventArgs e)
{
SomeField = e.Value.ToString();
}
}
and set initial value in javascript (if there is).
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JSRuntime.InvokeVoidAsync("setInitialValueById", "theinput", SomeField);
}
}
The setInitialValueById method:
window.setInitialValueById = (elementId, value) => {
document.getElementById(elementId).value = value;}
This will resolve the known input lag issue in blazor. You can set a label value with delay if it's the case:
public async Task OnTextInput(ChangeEventArgs e)
{
var value = e.Value.ToString();
SomeField = value;
await JSRuntime.InvokeVoidAsync("setLabelValue", value);
}
The setLabelValue method:
let lastInput;
window.setLabelValue = (value) => {
lastInput = value;
setTimeout(() => {
let inputValue = value;
if (inputValue === lastInput) {
document.getElementById("theLabelId").innerHTML = inputValue;
}
}, 2000);
}
This question already has answers here:
Can constructors be async?
(15 answers)
Closed 3 years ago.
I'm needing to call a third party async method from a mvc app. The name of this async method is ForceClient.QueryAsync. It is from an open source project: https://github.com/developerforce/Force.com-Toolkit-for-NET/.
Below works fine, the model.Opportunity contains expected info when the process is in the View stage of the mvc:
public async Task<ActionResult> MyController(string Id) {
. . . .
MyModel model = new MyModel();
var client = new ForceClient(instanceUrl, accessToken, apiVersion);
var qry = await client.QueryAsync<MyModel.SFOpportunity>(
"SELECT Name, StageName FROM Opportunity where Id='" + Id + "'");
model.Opportunity = qry.Records.FirstOrDefault();
. . . .
return View(viewName, myModel);
}
But below does not work. The model.Opportunity is null when the process is in the View stage. I did some debugging and see that the flow goes like this:
1) Step1
2) Step2
3) In the View stage. At this point the model.Opportunity is null, which I need it to be populated.
4) Step3.
public async Task<ActionResult> MyController(string Id) {
. . . .
MyModel myModel = await Task.Run(() =>
{
var result = new MyModel(Id);
return result;
}); // =====> Step 1
. . . .
return View(viewName, myInfoView);
}
public class MyModel
{
public SFOpportunity Opportunity { get; set; }
public MyModel(string id)
{
setOpportunityAsync(id);
}
private async void setOpportunityAsync(string id)
{
. . .
var client = new ForceClient(instanceUrl, accessToken, apiVersion);
var qry = await client.QueryAsync<MyModel.SFOpportunity>(
"SELECT Name, StageName FROM Opportunity where Id='" + id + "'"); // ======> Step2
Opportunity = qry.Records.FirstOrDefault(); // =====> step3
}
So, my question is what do I need to do to get it to execute the steps in the following sequence:
1) Step1
2) Step2
3) Step3
4) In the View stage. At this point the model.Opportunity is should be populated.
You cannot have async constructors.
One alternative is to have async factory methods:
public class MyModel
{
public SFOpportunity Opportunity { get; set; }
private MyModel() { }
public static async Task<MyModel> CreateAsync(string id)
{
var result = new MyModel();
await result.setOpportunityAsync(id);
return result;
}
private async Task setOpportunityAsync(string id)
{
...
}
}
The constructor for MyModel does not (and can not) await setOpportunityAsync because the constructor itself isn't (and can't be) asynchronous. Otherwise you would be able to await the call to the constructor itself, but you can't. So the async method likely won't be finished executing right after the constructor is called. It will be finished... whenever it's finished.
Here's a smaller test class to illustrate the behavior:
public class HasConstructorWithAsyncCall
{
public HasConstructorWithAsyncCall()
{
MarkConstructorFinishedAsync();
}
public bool ConstructorHasFinished { get; private set; }
async void MarkConstructorFinishedAsync()
{
await Task.Delay(500);
ConstructorHasFinished = true;
}
}
What is the value of ConstructorHasFinished immediately after an instance is constructed? Here's a unit test:
[TestMethod]
public void TestWhenConstructorFinishes()
{
var subject = new HasConstructorWithAsyncCall();
Assert.IsFalse(subject.ConstructorHasFinished);
Thread.Sleep(600);
Assert.IsTrue(subject.ConstructorHasFinished);
}
The test passes. The constructor returns while MarkConstructorFinishedAsync hasn't completed, so ConstructorHasFinished is false. Half a second later it finishes, and the value is true.
You can't mark a constructor async so you can't await anything in the constructor.
In general we wouldn't put anything long-running like data retrieval in a constructor, including anything we would call asynchronously. If we do then we must either call it synchronously or know that the completion of the constructor doesn't mean that it's completely "constructed."