I have a Language dropdown in Blazor layout that finally calls this method:
private async Task SetLanguage(string lang)
{
cultureChanger.ChangeCulture(lang);
await GetLanguageValues();
}
The GetLanguageValues() method should be called then, But the debugger does not step over cultureChanger.ChangeCulture(lang); and then goes out of the method to the caller. How do I solve this?
GetLanguageValues method:
string lang = "";
private async Task GetLanguageValues()
{
lang = textService.GetText(cultureChanger.Current, "lang");
ViewData.Language = lang;
StateHasChanged();
}
CultureChanger class:
public class CultureChanger
{
public CultureInfo Current { get; private set; } = new CultureInfo("en");
public event Action OnCultureChange;
public void ChangeCulture(string cultureName)
{
Current = new CultureInfo(cultureName);
OnCultureChange?.Invoke();
}
}
I am upgrading a preview 6 Blazor project to preview 9, Previously it was working.
Solved:
One of the components that subscribe to this event had StateHasChanged() in its method that I replaced with: base.InvokeAsync(StateHasChanged);
Related
I did a lot of Razor pages the past year, and a couple of weeks ago I started to transform all to a ViewModel for my Blazor Server App.
Now I thought it's time to make a new Blazor WebAssembly App.
But I struggle to build a POC with a ViewModel, based on the WeatherForecast example.
But whatever I do, I have errors. And so far I did not find a a good basic example.
Unhandled exception rendering component: Unable to resolve service for type 'fm2.Client.Models.IFetchDataModel' while attempting to activate 'fm2.Client.ViewModels.FetchDataViewModel'.
System.InvalidOperationException: Unable to resolve service for type 'fm2.Client.Models.IFetchDataModel' while attempting to activate 'fm2.Client.ViewModels.FetchDataViewModel'.
Example: https://github.com/rmoergeli/fm2
namespace fm2.Client.ViewModels
{
public interface IFetchDataViewModel
{
WeatherForecast[] WeatherForecasts { get; set; }
Task RetrieveForecastsAsync();
Task OnInitializedAsync();
}
public class FetchDataViewModel : IFetchDataViewModel
{
private WeatherForecast[] _weatherForecasts;
private IFetchDataModel _fetchDataModel;
public WeatherForecast[] WeatherForecasts
{
get => _weatherForecasts;
set => _weatherForecasts = value;
}
public FetchDataViewModel(IFetchDataModel fetchDataModel)
{
Console.WriteLine("FetchDataViewModel Constructor Executing");
_fetchDataModel = fetchDataModel;
}
public async Task RetrieveForecastsAsync()
{
_weatherForecasts = await _fetchDataModel.RetrieveForecastsAsync();
Console.WriteLine("FetchDataViewModel Forecasts Retrieved");
}
public async Task OnInitializedAsync()
{
_weatherForecasts = await _fetchDataModel.RetrieveForecastsAsync();
}
}
}
namespace fm2.Client
{
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddScoped<IFetchDataViewModel, FetchDataViewModel>();
await builder.Build().RunAsync();
}
}
}
Additional note:
Here how I did it previously for Blazor Server App: https://github.com/rmoergeli/fm2_server
Here I try the same for the Blazor WebAssembly App:
https://github.com/rmoergeli/fm2_wasm (Constructor is not initialized).
This POC is different comapred to the first link at the top. Here I tried to just do the same like I did for the Blazor Server App.
I pulled the latest code from Github. It looks like the wrong api was getting called.
When I changed from this:
WeatherForecast[] _weatherForecast = await _http.GetFromJsonAsync<WeatherForecast[]>("api/SampleData/WeatherForecasts");
to this:
WeatherForecast[] _weatherForecast = await _http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
in WeatherViewModel.cs
I could get the weather data to be displayed.
I have a Blazor app built on .NET Core 3.1 and I need to be able to access USB port resources. I keep getting the error:
JavaScript interop calls cannot be issued at this time. This is
because the component is being statically rendererd. When prerendering
is enabled, JavaScript interop calls can only be performed during the
OnAfterRenderAsync lifecycle method.
I have a pretty simple Blazor component wrapping the Blazor.Extensions.WebUSB library
public partial class Recordings : ComponentBase
{
[Inject] private IUSB _usb { get; set; }
[Inject] private ILogger<Recordings> _logger { get; set; }
[Inject] private IJSRuntime _runtime { get; set; }
private bool _initialized = false;
protected override Task OnAfterRenderAsync(bool firstRender)
{
if (!_initialized)
{
this._usb.OnConnect += OnConnect;
this._usb.OnDisconnect += OnDisconnect;
this._usb.Initialize();
this._initialized = true;
}
return Task.CompletedTask;
}
protected async Task GetDevices()
{
var devices = await this._usb.GetDevices();
if (devices != null && devices.Length > 0)
{
_logger.LogInformation("Device list received");
}
}
private void OnConnect(USBDevice device)
{
this._logger.LogInformation("Device connected");
}
private void OnDisconnect(USBDevice device)
{
this._logger.LogInformation("Device disconnected");
}
}
And even though I'm doing the JS interop in the OnAfterRenderAsync as suggested I still get the same error. I've tried delaying the call to _usb.Initialize until a button is pressed (meaning the component should definitely have finished rendering.
I've tried disabling prerendering by setting the render-mode attribute in _Host.cshtml to Server instead of ServerPrerendered but nothing changed.
Your code should be like this:
protected override Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
this._usb.OnConnect += OnConnect;
this._usb.OnDisconnect += OnDisconnect;
this._usb.Initialize();
this._initialized = true;
}
return Task.CompletedTask;
}
Note: When the firstRender variable is true, which occurs only once, you can use JSInterop. Before that you can't. This is the right time and place to initialize your JavaScript objects.
The OnAfterRender(Boolean) and OnAfterRenderAsync(Boolean) lifecycle methods are useful for performing interop, or interacting with values recieved from #ref. Use the firstRender parameter to ensure that initialization work is only performed once.
Hope this helps...
I have a Xamarin.forms application with an Azure Mobile Apps backend where I have tried to use Enterprise ans Social authentication following this tutorial https://adrianhall.github.io/develop-mobile-apps-with-csharp-and-azure/ .
I've been able to set up server side both of them and test that they are working. It can be tested in these pages
https://tasklistsync2.azurewebsites.net/.auth/login/google
https://MyMobileApps.b2clogin.com/MyMobileApps.onmicrosoft.com/oauth2/v2.0/authorize?p=B2C_1_emailPolicy&client_id=9ec9bcd1-ee5f-4abb-9908-8d63a24b6165&nonce=defaultNonce&redirect_uri=https%3A%2F%2Ftasklistsync2.azurewebsites.net%2F.auth%2Flogin%2Faad%2Fcallback&scope=openid&response_type=id_token&prompt=login
I've also included the code in my mobile app to use this authentication flow and it works till the point that I enter my credentials the page reloads but then it stays blank and never comes back to my mobile app as login successfull.
This is what happens when I click the login button.
async Task ExecuteLoginCommand()
{
if (IsBusy)
return;
IsBusy = true;
try
{
var cloudService = ServiceLocator.Instance.Resolve<ICloudService>();
//Login methods
await cloudService.LoginAsync(); //Social/enterprise
//await cloudService.LoginAsync(User); //Custom login
Application.Current.MainPage = new NavigationPage(new Pages.TaskList());
}
catch(Exception ex)
{
Debug.WriteLine($"[Login] Error = {ex.Message}");
}
finally
{
IsBusy = false;
}
}
It goes to cloudService.LoginAsync() that is implemented in my AzureCloudService class.
public Task LoginAsync()
{
var loginProvider = DependencyService.Get<ILoginProvider>();
return loginProvider.LoginAsync(client);
}
Also implemented in the android project.
public async Task LoginAsync(MobileServiceClient client)
{
await client.LoginAsync(context, MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, "tasklistsync2");
}
So all this process gets executed and I'm able to enter my credentials but then the app stays there, with the login page blank and the new NavigationPage(new Pages.TAskList()); line never gets exectued. If I close the login page, it goes to the finally block in the Try and Catch.
I think the missing piece is to finish the login process and come back to the mobile application but I'm not sure how can I make this happen (I believe it should be happening already).
The reply URL for the Authentication Processes set up in the Azure configuration as explained in the tutorial are
https://tasklistsync2.azurewebsites.net/.auth/login/google/callback
https://tasklistsync2.azurewebsites.net/.auth/login/aad/callback
You need to implement login interfaces in platform specific for iOS and Android.
public class iOSPlatform : IPlatform {
public UIViewController RootView => UIApplication.SharedApplication.KeyWindow.RootViewController;
public AccountStore AccountStore { get; private set; }
public iOSPlatform()
{
AccountStore = AccountStore.Create();
}
public async Task<MobileServiceUser> LoginAsync(MobileServiceClient client)
{
var accessToken = await LoginADALAsync();
var zumoPayload = new JObject();
zumoPayload["access_token"] = accessToken;
if (accessToken != null)
return await client.LoginAsync("aad", zumoPayload);
else
return null;
}
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
public async Task LogoutAsync()
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
{
// Do nothing
var authContext = new AuthenticationContext(Constants.authority);
if (authContext.TokenCache.ReadItems().Any())
{
authContext.TokenCache.Clear();
}
//In addition to clearing the token cache, you should also clear the cookies in the web view.
//Otherwise, the session cookies come into play and that is why you are seeing the web view come up and disappear immediately.
foreach (var cookie in NSHttpCookieStorage.SharedStorage.Cookies)
{
NSHttpCookieStorage.SharedStorage.DeleteCookie(cookie);
}
}
private async Task<string> LoginADALAsync()
{
var authContext = new AuthenticationContext(Constants.authority);
if (authContext.TokenCache.ReadItems().Any())
{
authContext = new AuthenticationContext(authContext.TokenCache.ReadItems().First().Authority);
}
var authResult = await authContext.AcquireTokenAsync(
Constants.resourceId,
Constants.clientId,
new Uri(Constants.redirectUri),
new PlatformParameters(RootView));
return authResult.AccessToken;
}
}
Similar for Android, except Android uses Context instead of UIViewController
public Context RootView { get; private set; }
public AccountStore AccountStore { get; private set; }
public void Init(Context context)
{
RootView = context;
AccountStore = AccountStore.Create(context);
}
My requirement is when the return type of the action is void or Task, I'd like to return my custom ApiResult instead. I tried the middleware mechanism, but the response I observed has null for both ContentLength and ContentType, while what I want is a json representation of an empty instance of ApiResult.
Where should I make this conversion then?
There are multiple filter in .net core, and you could try Result filters.
For void or Task, it will return EmptyResult in OnResultExecutionAsync.
Try to implement your own ResultFilter like
public class ResponseFilter : IAsyncResultFilter
{
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
// do something before the action executes
if (context.Result is EmptyResult)
{
context.Result = new JsonResult(new ApiResult());
}
var resultContext = await next();
// do something after the action executes; resultContext.Result will be set
}
}
public class ApiResult
{
public int Code { get; set; }
public object Result { get; set; }
}
And register it in Startup.cs
services.AddScoped<ResponseFilter>();
services.AddMvc(c =>
{
c.Filters.Add(typeof(ResponseFilter));
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
All you have to do is to check the return type and on the basis of the return you can perform whatever operations you want.
Here is the abstract demo:
You have a method:
public Action SomeActionMethod()
{
var obj = new object();
return (Action)obj;
}
Now in your code you can use the following code to get the name of the method:
MethodBase b = p.GetType().GetMethods().FirstOrDefault();
var methodName = ((b as MethodInfo).ReturnType.Name);
Where p in the above code is the class which contains the methods whose return type you want to know.
after having the methodname you can decide on variable methodName what to return.
Hope it helps.
I want to create a composite Windows Workflow Activity (under .NET 4) that contains a predefined ReceiveAndSendReply Activity. Some of the properties are predefined, but others (particularly ServiceContractName) need to be set in the designer.
I could implement this as an Activity Template (the same way ReceiveAndSendReply is implemented), but would rather not. If I later change the template, I'd have to update all previously created workflows manually. A template would also permit other developers to change properties that should be fixed.
Is there a way to do this from a Xaml Activity? I have not found a way to assign an Argument value to a property of an embedded Activity. If not, what technique would you suggest?
I haven't done this using a composite XAML activity and am getting some errors when I try but doing so through a NativeActivity is no problem. See the example code below.
public class MyReceiveAndSendReply : NativeActivity
{
private Receive _receive;
private SendReply _sendReply;
public string ServiceContractName { get; set; }
public string OperationName { get; set; }
protected override bool CanInduceIdle
{
get { return true; }
}
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
_receive = _receive ?? new Receive();
_sendReply = _sendReply ?? new SendReply();
_receive.CanCreateInstance = true;
metadata.AddImplementationChild(_receive);
metadata.AddImplementationChild(_sendReply);
_receive.ServiceContractName = ServiceContractName;
_receive.OperationName = OperationName;
var args = new ReceiveParametersContent();
args.Parameters["firstName"] = new OutArgument<string>();
_receive.Content = args;
_sendReply.Request = _receive;
var results = new SendParametersContent();
results.Parameters["greeting"] = new InArgument<string>("Hello there");
_sendReply.Content = results;
base.CacheMetadata(metadata);
}
protected override void Execute(NativeActivityContext context)
{
context.ScheduleActivity(_receive, ReceiveCompleted);
}
private void ReceiveCompleted(NativeActivityContext context, ActivityInstance completedInstance)
{
context.ScheduleActivity(_sendReply);
}
}