skip to Main Content

we are evaluating Blazor as tech stack for a large enterprise application and I’m having some questions.

Let’s say we have two text boxes and both can be edited by the user. When one edits the first one, a backend code is executed which might end in a code where the second text box (bind to property) is edited, one second after the first edit.
That can cause issues because the user might have typed in the second textbox in the meantime.

We might block user inputs while the backend code is running, however, I’d prefer to not block user inputs during server calculations.
Is there any good way of handling that?
The perfect solution would be that a popup appears because of concurrent edits, and the user can decide what to do. At least he is informed that his value might be overwritten.

Any ideas?
Thanks

3

Answers


  1. As pointed out in one of the replies, the concurrency issues are the same irrespective of the framework. However the implementation can differ drastically depending on whether you use Blazor Server or Blazor WebAssembly.

    1. Blazor Server: Since no code is executed at the client side, you can use Singleton DI to address concurrency issues (wherever needed). So a singleton service tracks the business object being modified against a user id (say ‘x’). All other users are informed that user ‘x’ is modifying the item – similar to what you see in Google docs/sheets – very easily near real time.
    2. Blazor WebAssembly: All code runs entirely in the browser. Unless explicitly informed via SignalR hub or otherwise, there is no way for two users to know what entity they are modifying.

    Finally, you can always resolve concurrency optimistically (like EF Core). But keep in mind the fact that with WA, the concurrency conflict might become known only at the point of commit – unless addressed explicitly.

    Login or Signup to reply.
  2. Blazor Optional Input Value

    Not sure this helps, but in Blazor Server you can do all kinds of creative combinations of what you describe.

    One way to do this is the following: Instead of binding the first input to the second you just call a method and run your await-async server process that returns a new modified value based on the first input. The user can continue filling out the second input (or any input) as the asynchronous server Task is processing and it returns the new modified value.

    When the await call to the server returns the new value, a JSInteropt call triggers a regular JavaScript window.confirm(x) popup with a question in the browser. They can choose to keep their value, clicking "ok", or accept the one from the second input clicking "cancel". Its not very sexy but it gives you an idea. Note: In this example below Im not modifying the first input from the server call, just passing the raw value to the second input.

    You can paste the code below into any Blazor Server component and test it:

    // Inject the JSInterop feature to use custom JavaScript in Blazor
    @inject IJSRuntime JS
    
    <input type="text" @onchange="MyMethod" /><br />
    <input type="text" value="@MyValue" />
    
    @code {
        private string? MyValue { get; set; } = "";
        private async Task MyMethod(ChangeEventArgs args)
        {
            // Add your await method call to the long-running
            // server process here.
            string newvalue = await GetNewServerValue(args);
    
            // Call a JavaScript window.confirm with a choice for the user
            string x = "Do you want to keep your own input?";
            bool result = await JS.InvokeAsync<bool>("confirm", x);
    
            // If the user does not want to keep what they typed,
            // they can click cancel in the confrim box and the first input
            // value replaces their own using the property below.
            if (!result) {
                //MyValue = newvalue;// use the servers modified value
                MyValue = args?.Value?.ToString() ?? string.Empty;
            }
        }
    
        private async Task<string> GetNewServerValue(ChangeEventArgs args)
        {
            // I added a 5 second wait to simulate time for a user to enter
            // some text in the second input while waiting for the
            // server's response.
            Thread.Sleep(5000);
            // Run your process and return your modified value...
            return "new value";
        }
    }
    

    I realize this is kinda sloppy. I’m not a fan of using JSInterop or JavaScript prompts like this. You run the danger of calling the JavaScript global window object outside of SignalR and dropping the WebSocket connection when you do these tricks or going over the Blazor Server SignalR MaximumReceiveMessageSize limit of 32k. But Blazor gives you that creative ability to add all kinds of goodies like this.

    A better way of using a custom popup would be to just build a new Blazor Component in HTML with CSS as a <div> and have that layer over your screen using z-index in CSS, then show and hide it based on the events above. Or this can be a tiny blazor component that slides into view beside the input with a choice they can quickly click.

    But at least this shows you one of the many ways in Blazor to handle these kinds of scenarios.

    Login or Signup to reply.
  3. The perfect solution would be that a popup appears because of
    concurrent edits, and the user can decide what to do. At least he is
    informed that his value might be overwritten.

    Any ideas?

    So do exactly what you have described, where’s the problem?

    <input @bind="Model.PropertyA" @bind:after="OnPropertyAChanged" />
    <input @bind="Model.PropertyB" />
    
    @code 
    {  
        async Task OnPropertyAChanged()
        {
             var propertyBBackup = Model.PropertyB;
             var propertyBNew = await _someService.CalculatePropertyBAsync(Model.PropertyA);
             if (propertyBNew != Model.PropertyB) //value changed by CalculateNewPropertyB
             {
                  if (propertyBBackup != Model.PropertyB) //value changed by user
                  {
                      var toast = await ToastService.Show(....);
                      toast.ClickedYes += 
                      {
                          Model.PropertyB = propertyBNew;                       
                      }
                  }
             }
        }
    
    }
    

    If you are familiar with Reactive Extensions, I would build an operator (or extension method to detect changes to particular field while you are waiting for the source observable (CalculatePropertyBAsync) to emit a value and make this reusable. Something like:

    _someService.CalculatePropertyBAsync(Model.PropertyA).ToObservable()
        .DetectConflict(() => Model.PropertyB, onConflict: LetUserDecideInDialog)
        .Subscribe(result => Model.PropertyB = result);
    

    the advantage is, that you can combine this with Switch/SelectMany (better known as SwitchMap in RxJS) or Throttle operators to handle concurency issues when PropertyA changes too ofter and so on.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search