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...
Related
I use react-native to develop an app, and I need to connect to braintree (paypal value). The official provides 3 kinds of sdk, js, android, and ios. I try to connect to the native android library, but it doesn't seem to have any effect.After calling the native method in ReactNative, only "react-native-test" is output (No error is reported, and there is no change on the interface. It seems that it should jump to PayPal authorization to be normal). I'm not sure if it's my problem . I also tried to use js library in react-native, but after importing a certain method, my program doesn't start. Or can I only do it in webView? Has anyone connected with PayPal value? Can you give me some advice? Thanks.
Here is the documentation I refer to.
Below is my android code:
public class BraintreeValueModule extends ReactContextBaseJavaModule implements PayPalListener {
private static ReactApplicationContext reactContext;
private Callback successCallback;
private Callback errorCallback;
private BraintreeClient braintreeClient;
private PayPalClient payPalClient;
public BraintreeValueModule(ReactApplicationContext context) {
super(context);
reactContext = context;
}
#NonNull
#Override
public String getName() {
return "BraintreeValueModule";
}
#ReactMethod
public void test(){
System.out.println("react-native-test");
braintreeClient = new BraintreeClient(reactContext.getApplicationContext(), "sandbox_ykbznr4s_ctmssyj6wz2qcj2g");
FragmentActivity activity = (FragmentActivity) getCurrentActivity();
activity.runOnUiThread(new MyRunnable(activity,braintreeClient));
//The following writing method will report an error: Method addObserver must be called on the main thread
//if(activity != null){
// payPalClient = new PayPalClient(activity, braintreeClient);
// payPalClient.setListener(this);
//}
}
#Override
public void onPayPalSuccess(#NonNull PayPalAccountNonce payPalAccountNonce) {
successCallback.invoke(payPalAccountNonce.toString());
}
#Override
public void onPayPalFailure(#NonNull Exception error) {
if (error instanceof UserCanceledException) {
// user canceled
errorCallback.invoke("use canceled");
} else {
// handle error
errorCallback.invoke("error");
}
}
}
public class MyRunnable implements Runnable, PayPalListener {
private BraintreeClient braintreeClient;
private PayPalClient payPalClient;
private FragmentActivity activity;
MyRunnable(FragmentActivity activity,BraintreeClient braintreeClient){
this.activity = activity;
this.braintreeClient = braintreeClient;
}
#Override
public void run() {
if(activity != null){
payPalClient = new PayPalClient(activity, braintreeClient);
payPalClient.setListener(this);
}
}
#Override
public void onPayPalSuccess(#NonNull PayPalAccountNonce payPalAccountNonce) {
System.out.println(payPalAccountNonce.getString());
}
#Override
public void onPayPalFailure(#NonNull Exception error) {
if (error instanceof UserCanceledException) {
// user canceled
System.out.println("use canceled");
} else {
// handle error
System.out.println("error");
}
}
}
I believe you might have missed out the following few lines:
PayPalVaultRequest request = new PayPalVaultRequest();
request.setBillingAgreementDescription("Your agreement description");
payPalClient.tokenizePayPalAccount(getCurrentActivity(), request);
However, I believe it might still not work as I had similar problem when I was trying to integrate the drop-in.
I'm afraid that you'll need to initialise your clients (in your case, BraintreeClient and PaypalClient) in the onCreate method of your MainActivity.
And then try to call a reference to the client (either by SharedPreference or static variable) in your module to launch the drop in.
Pretty sure it's similar issue to your case.
This only applies to the v4 library.
Read more on this thread:
https://github.com/braintree/braintree-android-drop-in/issues/374#issuecomment-1345929549
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);
I have a logic to apply in case the request received is a BadRequest, to do this I have created a filter:
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
// Apply logic
}
}
}
In Startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options => { options.Filters.Add<ValidateModelAttribute>(); });
}
Controller:
[Route("api/[controller]")]
[ApiController]
public class VerifyController : ControllerBase
{
[Route("test")]
[HttpPost]
[ValidateModel]
public ActionResult<Guid> validationTest(PersonalInfo personalInfo)
{
return null;
}
}
Model:
public class PersonalInfo
{
public string FirstName { get; set; }
[RegularExpression("\\d{4}-?\\d{2}-?\\d{2}", ErrorMessage = "Date must be properly formatted according to ISO 8601")]
public string BirthDate { get; set; }
}
The thing is when I put a break point on the line:
if (!context.ModelState.IsValid)
execution reaches this line only if the request I send is valid. Why it is not passing the filter if I send a bad request?
The [ApiController] attribute that you've applied to your controller adds Automatic HTTP 400 Responses to the MVC pipeline, which means that your custom filter and action aren't executed if ModelState is invalid.
I see a few options for affecting how this works:
Remove the [ApiController] attribute
Although you can just remove the [ApiController] attribute, this would also cause the loss of some of the other features it provides, such as Binding source parameter inference.
Disable only the Automatic HTTP 400 Responses
Here's an example from the docs that shows how to disable just this feature:
services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
// ...
options.SuppressModelStateInvalidFilter = true;
// ...
}
This code goes inside of your Startup's ConfigureServices method.
Customise the automatic response that gets generated
If you just want to provide a custom response to the caller, you can customise what gets returned. I've already described how this works in another answer, here.
An example of intersection for logging is describe in Log automatic 400 responses
Add configuration in Startup.ConfigureServices.
services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
// To preserve the default behavior, capture the original delegate to call later.
var builtInFactory = options.InvalidModelStateResponseFactory;
options.InvalidModelStateResponseFactory = context =>
{
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<Startup>>();
// Perform logging here.
//E.g. logger.LogError($”{context.ModelState}”);
logger.LogWarning(context.ModelState.ModelStateErrorsToString());
// Invoke the default behavior, which produces a ValidationProblemDetails response.
// To produce a custom response, return a different implementation of IActionResult instead.
return builtInFactory(context);
};
});
public static String ModelStateErrorsToString(this ModelStateDictionary modelState)
{
IEnumerable<ModelError> allErrors = modelState.Values.SelectMany(v => v.Errors);
StringBuilder sb = new StringBuilder();
foreach (ModelError error in allErrors)
{
sb.AppendLine($"error {error.ErrorMessage} {error.Exception}");
}
return sb.ToString();
}
As the attribute filter in the life cycle of the .Net Core you can’t handle it. The filter layer with ModelState will run after the model binding.
You can handle it with .Net Core middleware as the following https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-2.1&tabs=aspnetcore2x
If you want to SuppressModelStateInvalidFilter on individual action, consider to use custom attribute suggested on https://learn.microsoft.com/en-us/answers/questions/297568/how-to-suppress-suppressmodelstateinvalidfilter-at.html. (And similar answer https://github.com/aspnet/Mvc/issues/8575)
public class SuppressModelStateInvalidFilterAttribute : Attribute, IActionModelConvention
{
private const string FilterTypeName = "ModelStateInvalidFilterFactory";
public void Apply(ActionModel action)
{
for (var i = 0; i < action.Filters.Count; i++)
{
//if (action.Filters[i] is ModelStateInvalidFilter)
if (action.Filters[i].GetType().Name == FilterTypeName)
{
action.Filters.RemoveAt(i);
break;
}
}
}
}
Example of use
[ApiController]
public class PersonController
{
[SuppressModelStateInvalidFilter]
public ActionResult<Person> Get() => new Person();
}
I am having one Application based on XamarinForms.
One background service I have created in Android project and that service would like to send data to ContentPage(which is in PCL) which is displayed to user.
How could I pass data to ContentPage(From xx.Droid project to PCL)?
One solution is:
To Create class in PCL with static variable(e.g. var TEMP_VAR), which will be accessed from xxx.Droid project.
Update value of that static variable(TEMP_VAR) from the service class from the xxx.Droid project.
Need to create Notifier on that static variable(TEMP_VAR)
Update the content page using MessageCenter Mechanism if require.
If there is any better solution, could you please provide me?
This can be achieved using the concept of C#
Dependency service
Event
Need to have 4 classes for such an implementation:
Interface in PCL(e.g. CurrentLocationService.cs) with event handlers defined in it.
namespace NAMESPACE
{
public interface CurrentLocationService
{
void start();
event EventHandler<PositionEventArgs> positionChanged;
}
}
Implementation of interface of PCL in xxx.Droid project (e.g. CurrentLocationService_Android.cs) using Dependency service
class CurrentLocationService_Android : CurrentLocationService
{
public static CurrentLocationService_Android mySelf;
public event EventHandler<PositionEventArgs> positionChanged;
public void start()
{
mySelf = this;
Forms.Context.StartService(new Intent(Forms.Context, typeof(MyService)));
}
public void receivedNewPosition(CustomPosition pos)
{
positionChanged(this, new PositionEventArgs(pos));
}
}
ContentPage in PCL - which will have object of implementation of interface.
Object can be obtained by
public CurrentLocationService LocationService
{
get
{
if(currentLocationService == null)
{
currentLocationService = DependencyService.Get<CurrentLocationService>();
currentLocationService.positionChanged += OnPositionChange;
}
return currentLocationService;
}
}
private void OnPositionChange(object sender, PositionEventArgs e)
{
Debug.WriteLine("Got the update in ContentPage from service ");
}
Background service in xxx.Droid project. This service will have reference of implementation of dependency service CurrentLocationService.cs
[Service]
public class MyService : Service
{
public string TAG = "MyService";
public override IBinder OnBind(Intent intent)
{
throw new NotImplementedException();
}
public override StartCommandResult OnStartCommand(Android.Content.Intent intent, StartCommandFlags flags, int startId)
{
Log.Debug(TAG, TAG + " started");
doWork();
return StartCommandResult.Sticky;
}
public void doWork()
{
var t = new Thread(
() =>
{
Log.Debug(TAG, "Doing work");
Thread.Sleep(10000);
Log.Debug(TAG, "Work completed");
if(CurrentLocationService_Android.mySelf != null)
{
CustomPosition pos = new CustomPosition();
pos.update = "Finally value is updated";
CurrentLocationService_Android.mySelf.receivedNewPosition(pos);
}
StopSelf();
});
t.Start();
}
}
Note : PositionEventArgs class need to be created as per usage to pass on data between service and ContentPage.
This works for me like charm.
Hope so this would be helpful to you.
I am using NServiceBus v4.3, MVC4, RavenDB 2.5 and StructureMap 2.6.4 in our solution.
I am having a similar issue under StructureMap to that described in this question's responses where I require different lifecycles for the MVC Controller and NServiceBus Handler use of RavenDB's IDocumentSession in my Web project.
Specifically in my case what happens is that if I use the HybridHttpOrThreadLocalScoped (as the above answer suggests for Windsor) lifecycle the sessions are not properly disposed of and I soon hit the 30 transaction limit error. If I use the HttpContext lifecycle my NSB event Handlers in the Web project do not get called.
In my Controllers the session is wrapped in a unit of work applied via an MVC ActionFilter. I also use the UoW within the Handlers as my Registry is wired up to retrieve the session from the UoW. The code is as such:
RavenDbWebRegistry.cs
public sealed class RavenDbWebRegistry : Registry
{
public RavenDbWebRegistry()
{
// register RavenDB document store
ForSingletonOf<IDocumentStore>().Use(() =>
{
var documentStore = new DocumentStore
{
ConnectionStringName = "RavenDB",
Conventions =
{
IdentityPartsSeparator = "-",
JsonContractResolver = new PrivatePropertySetterResolver(),
},
};
documentStore.Initialize();
return documentStore;
});
For<IDocumentSession>().HybridHttpOrThreadLocalScoped().Add(ctx =>
{
var uow = (IRavenDbUnitOfWork)ctx.GetInstance<IUnitOfWork>();
return uow.DocumentSession;
});
For<IUnitOfWork>().HybridHttpOrThreadLocalScoped().Use<WebRavenDbUnitOfWork>();
}
}
Example of Web project Handler:
public class SiteCreatedEventHandler : IHandleMessages<ISiteCreatedEvent>
{
public IBus Bus { get; set; }
public IUnitOfWork Uow { get; set; }
public IDocumentSession DocumentSession { get; set; }
public void Handle(ISiteCreatedEvent message)
{
try
{
Debug.Print(#"{0}{1}", message, Environment.NewLine);
Uow.Begin();
var site = DocumentSession.Load<Site>(message.SiteId);
Uow.Commit();
//invoke Hub and push update to screen
var context = GlobalHost.ConnectionManager.GetHubContext<AlarmAndNotifyHub>();
//TODO make sure this SignalR function is correct
context.Clients.All.displayNewSite(site, message.CommandId);
context.Clients.All.refreshSiteList();
}
catch (Exception ex)
{
Uow.Rollback();
}
}
}
Usage of ActionFilter:
[RavenDbUnitOfWork]
public ViewResult CreateNew(int? id)
{
if (!id.HasValue || id.Value <= 0)
return View(new SiteViewModel { Guid = Guid.NewGuid() });
var targetSiteVm = MapSiteToSiteViewModel(SiteList(false)).FirstOrDefault(s => s.SiteId == id.Value);
return View(targetSiteVm);
}
WebRegistry (that sets up NSB in my MVC project)
public sealed class WebRegistry : Registry
{
public WebRegistry()
{
Scan(x =>
{
x.TheCallingAssembly();
x.Assembly("IS.CommonLibrary.ApplicationServices");
x.LookForRegistries();
});
IncludeRegistry<RavenDbWebRegistry>();
FillAllPropertiesOfType<IUnitOfWork>();
FillAllPropertiesOfType<IDocumentSession>();
FillAllPropertiesOfType<StatusConversionService>();
FillAllPropertiesOfType<IStateRepository<TieState>>();
FillAllPropertiesOfType<IStateRepository<DedState>>();
FillAllPropertiesOfType<ITieService>();
FillAllPropertiesOfType<IDedService>();
FillAllPropertiesOfType<IHbwdService>();
//NServiceBus
ForSingletonOf<IBus>().Use(
NServiceBus.Configure.With()
.StructureMapBuilder()
.DefiningCommandsAs(t => t.Namespace != null && t.Namespace.EndsWith("Command"))
.DefiningEventsAs(t => t.Namespace != null && t.Namespace.EndsWith("Event"))
.DefiningMessagesAs(t => t.Namespace == "Messages")
.RavenPersistence("RavenDB")
.UseTransport<ActiveMQ>()
.DefineEndpointName("IS.Argus.Web")
.PurgeOnStartup(true)
.UnicastBus()
.CreateBus()
.Start(() => NServiceBus.Configure.Instance
.ForInstallationOn<Windows>()
.Install())
);
//Web
For<HttpContextBase>().Use(() => HttpContext.Current == null ? null : new HttpContextWrapper(HttpContext.Current));
For<ModelBinderMappingDictionary>().Use(GetModelBinders());
For<IModelBinderProvider>().Use<StructureMapModelBinderProvider>();
For<IFilterProvider>().Use<StructureMapFilterProvider>();
For<StatusConversionService>().Use<StatusConversionService>();
For<ITieService>().Use<TieService>();
For<IDedService>().Use<DedService>();
For<IHbwdService>().Use<HbwdService>();
For<ISiteService>().Use<SiteService>();
IncludeRegistry<RedisRegistry>();
}
I have tried configuring my Registry using every possible combination I can think of to no avail.
Given that the StructureMap hybrid lifecycle does not work as I would expect, what must I do to achieve the correct behaviour?
Is the UoW necessary/beneficial with RavenDB? I like it (having adapted it from my earlier NHibernate UoW ActionFilter) because of the way it manages the lifecycle of my sessions within Controller Actions, but am open to other approaches.
What I would ideally like is a way to - within the Web project - assign entirely different IDocumentSessions to Controllers and Handlers, but have been unable to work out any way to do so.
Firstly, RavenDB already implements unit of work by the wrapping IDocumentSession, so no need for it. Opening a session, calling SaveChanges() and disposing has completed the unit of work
Secondly, Sessions can be implemented in a few ways for controllers.
The general guidance is to set up the store in the Global.asax.cs. Since there is only 1 framework that implements IDocumentSession - RavenDB, you might as well instantiate it from the Global. If it was NHibernate or Entity Framework behind a repository, I'd understand. But IDocumentSession is RavenDB specific, so go with a direct initialization in the Application_Start.
public class Global : HttpApplication
{
public void Application_Start(object sender, EventArgs e)
{
// Usual MVC stuff
// This is your Registry equivalent, so insert it into your Registry file
ObjectFactory.Initialize(x=>
{
x.For<IDocumentStore>()
.Singleton()
.Use(new DocumentStore { /* params here */ }.Initialize());
}
public void Application_End(object sender, EventArgs e)
{
var store = ObjectFactory.GetInstance<IDocumentStore>();
if(store!=null)
store.Dispose();
}
}
In the Controllers, add a base class and then it can open and close the sessions for you. Again IDocumentSession is specific to RavenDB, so dependency injection doesn't actually help you here.
public abstract class ControllerBase : Controller
{
protected IDocumentSession Session { get; private set; }
protected override void OnActionExecuting(ActionExecutingContext context)
{
Session = ObjectFactory.GetInstance<IDocumentStore>().OpenSession();
}
protected override void OnActionExecuted(ActionExecutedContext context)
{
if(this.IsChildAction)
return;
if(content.Exception != null && Session != null)
using(context)
Session.SaveChanges();
}
}
Then from there, inherit from the base controller and do your work from there:
public class CustomerController : ControllerBase
{
public ActionResult Get(string id)
{
var customer = Session.Load<Customer>(id);
return View(customer);
}
public ActionResult Edit(Customer c)
{
Session.Store(c);
return RedirectToAction("Get", c.Id);
}
}
Finally, I can see you're using StructureMap, so it only takes a few basic calls to get the Session from the DI framework:
public class SiteCreatedEventHandler : IHandleMessages<ISiteCreatedEvent>
{
public IBus Bus { get; set; }
public IUnitOfWork Uow { get; set; }
public IDocumentSession DocumentSession { get; set; }
public SiteCreatedEventHandler()
{
this.DocumentSession = ObjectFactory.GetInstance<IDocumentStore>().OpenSession();
}
public void Handle(ISiteCreatedEvent message)
{
using(DocumentSession)
{
try
{
Debug.Print(#"{0}{1}", message, Environment.NewLine);
///// Uow.Begin(); // Not needed for Load<T>
var site = DocumentSession.Load<Site>(message.SiteId);
//// Uow.Commit(); // Not needed for Load<T>
// invoke Hub and push update to screen
var context = GlobalHost.ConnectionManager.GetHubContext<AlarmAndNotifyHub>();
// TODO make sure this SignalR function is correct
context.Clients.All.displayNewSite(site, message.CommandId);
context.Clients.All.refreshSiteList();
}
catch (Exception ex)
{
//// Uow.Rollback(); // Not needed for Load<T>
}
}
}