Blazor (Server) not updating image using StateHasChanged(); - c#

I'm creating a blazor server App that should read the Webcam and show the Image, and while the Image generates fine (the base64 is completely valid) it's not updating the image on the Website even when using InvokeAsync(StateHasChanged);
Index.razor:
#page "/"
<PageTitle>Index</PageTitle>
<div style="width: 100%; height: 500px;border: solid green 1px">
<img src="#ImageSource" style="height: 100%; width: 100%; margin: auto; border: solid red 1px;"
/>
</div>
#code
{
public string? ImageSource { get; set; }
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
Console.WriteLine("init");
Webcam.Instance?.Init(async bufferScope =>
{
byte[] image = bufferScope.Buffer.ExtractImage();
var ms = new MemoryStream(image);
ImageSource = ToBase64Image(Bitmap.FromStream(ms));
_forceRerender = true;
await InvokeAsync(StateHasChanged);
Console.WriteLine("running again");
}, true);
}
public static string ToBase64Image(Image bmp)
{
var data = GetPng(bmp);
return "data:image/png;base64," + Convert.ToBase64String(data.ToArray());
}
public static byte[] GetPng(Image bitmap)
{
using (var stream = new MemoryStream())
{
bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
return stream.ToArray();
}
}
bool _forceRerender = false;
protected override bool ShouldRender()
{
if (_forceRerender)
{
_forceRerender = false;
return true;
}
return base.ShouldRender();
}
}
If that helps here is my (rudimentary) webcam class (yes i know the way i have it isn't best practise but i wan't it to at least run):
using FlashCap;
namespace CameraServer.Data
{
public class Webcam
{
public static Webcam? Instance { get; private set; }
private CaptureDeviceDescriptor DeviceDescriptor { get; set; }
public CaptureDevice Device { get; private set; }
public Webcam(CaptureDeviceDescriptor deviceDescriptor)
{
DeviceDescriptor = deviceDescriptor;
Instance = this;
}
public async Task<Webcam?> Init(PixelBufferArrivedDelegate func, bool start = false)
{
Device = await DeviceDescriptor.OpenAsync(DeviceDescriptor.Characteristics[0], func);
if (start)
await Device.StartAsync();
return Instance;
}
//signal webcam to start
public void Start() => Device?.StartAsync();
//stop webcam (videoSource.WaitForStop(); to check)
public void Stop() => Device?.StopAsync();
}
}

If someone has a similar problem: The Issue was solved by changing the render-mode in _Host.cshtml from "ServerPrerendered" to "Server".
Thanks everyone who tried to help!

Related

OnInitializedAsync() Issue with Blazor Telerik

Im using Telerik Blazor controls, I am using mt API to return data which works fine when I check it from an API perspective. My web app has issues as it errors as I believe there is no data. To my knowledge this is due to OnInitializedAsync(). Below is my code:
<TelerikScheduler Data="#HolidayPlanners" #bind-Date="#StartDate" #bind-View="#selectedView" Height="100%" Class="Scheduler" OnCreate="#AddAppointment"
OnUpdate="#UpdateAppointment" OnDelete="#DeleteAppointment"
AllowCreate="true" AllowDelete="true" AllowUpdate="true"
IdField="#(nameof(UvwHolidayPlanner.Pk))"
StartField="#(nameof(UvwHolidayPlanner.StartDate))"
EndField="#(nameof(UvwHolidayPlanner.EndDate))"
TitleField="#(nameof(UvwHolidayPlanner.Title))"
DescriptionField="#(nameof(UvwHolidayPlanner.Description))"
IsAllDayField="#(nameof(UvwHolidayPlanner.IsAllDay))"
RecurrenceRuleField="#(nameof(UvwHolidayPlanner.RecurrenceRule))"
RecurrenceExceptionsField="#(nameof(UvwHolidayPlanner.RecurrenceExceptions))"
RecurrenceIdField="#(nameof(UvwHolidayPlanner.RecurrenceFk))">
<SchedulerViews>
<SchedulerMonthView></SchedulerMonthView>
</SchedulerViews>
#code {
public string _URL = String.Empty;
IEnumerable<UvwHolidayPlanner> HolidayPlanners { get; set; }
DateTime StartDate = DateTime.Now;
SchedulerView selectedView { get; set; } = SchedulerView.Month;
protected override async Task OnInitializedAsync()
{
_URL = settingsAccessor.AllClientSettings().BaseServiceURI;
HolidayPlanners = (await http.CreateClient("ClientSettings").GetFromJsonAsync<List<UvwHolidayPlanner>>($"{_URL}/lookup/HolidayPlanner"))
.OrderBy(t => t.Title)
.ToList();
StateHasChanged();
}
//private void LoadData()
//{
//Appointments = appointmentService.GetAppointments();
//}
void UpdateAppointment(SchedulerUpdateEventArgs args)
{
//appointmentService.UpdateAppointment((AppointmentDto)args.Item);
//LoadData();
}
void AddAppointment(SchedulerCreateEventArgs args)
{
//appointmentService.CreateAppointment((AppointmentDto)args.Item);
//LoadData();
}
void DeleteAppointment(SchedulerDeleteEventArgs args)
{
//appointmentService.DeleteAppointment((AppointmentDto)args.Item);
//LoadData();
}
}
My HolidayPlanners property seems to return null & it errors. Unsure if this is because the UI Components have already initialized.
Below is a message within the 404 error unsure if thats an issue:
ProjectManager.Pages.Pages__Host.<ExecuteAsync>b__15_1() in _Host.cshtml
<component type="typeof(App)" render-mode="ServerPrerendered" />
I have tried render-mode="Server" & "Static"

Teams Tookit Blazor - Unable to use Resize event across multiple tabs

I am currently trying to re-register an event I am using upon switching tabs.
The JS is:
window.resizeListener = function (dotnethelper) {
$(window).resize(() => {
let browserWidth = $(window).innerWidth();
let browserHeight = $(window).innerHeight();
dotnethelper.invokeMethodAsync('SetBrowserDimensions', browserWidth, browserHeight).then(() => {
// success, do nothing
}).catch(error => {
console.log("Error during browser resize: " + error);
});
});
}
window.getWindowDimensions = function () {
return {
width: window.innerWidth,
height: window.innerHeight
};
};`
I created the following Class:
using Microsoft.JSInterop;
namespace DevBlazor.Data
{
public class BrowserService
{
private IJSRuntime JS = null;
public event EventHandler<GetDimensions> Resize;
public async void Init(IJSRuntime js)
{
// enforce single invocation
if (JS == null)
{
this.JS = js;
_ = await JS.InvokeAsync<string>("resizeListener", DotNetObjectReference.Create(this));
}
}
[JSInvokable]
public void SetBrowserDimensions(int jsBrowserWidth, int jsBrowserHeight)
{
// For simplicity, we're just using the new width
this.Resize?.Invoke(this, new GetDimensions() { Width = jsBrowserWidth, Height = jsBrowserHeight });
}
}
public class GetDimensions
{
public int Width { get; set; }
public int Height { get; set; }
}
}
added as singleton in program.cs: builder.Services.AddSingleton<BrowserService>();
Then I use it in each tab:
private static Boolean bigWindowSize = true;
protected void UpdatedBrowserWidth(object sender, GetDimensions getDimensions)
{
wd = getDimensions;
Console.WriteLine("Width: " + wd.Width + Environment.NewLine + "Height: " + wd.Height);
if (wd.Width >= wd.Height)
{
bigWindowSize = true;
base.StateHasChanged();
}
else
{
bigWindowSize = false;
base.StateHasChanged();
}
}
async Task CreateSizeDimensions()
{
//just used to create an initial non-null starting value
wd = await JsRuntime.InvokeAsync<GetDimensions>("getWindowDimensions");
Console.WriteLine("Init Width: " + wd.Width + Environment.NewLine + "Init Height: " + wd.Height);
if (wd.Width >= wd.Height)
bigWindowSize = true;
else
bigWindowSize = false;
Browser.Init(JsRuntime);
Browser.Resize += UpdatedBrowserWidth;
}
Standard Init:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
isInTeams = await MicrosoftTeams.IsInTeams();
if (isInTeams)
{
await CreateSizeDimensions();
}
StateHasChanged();
}
}
I have tried:
Adding an IDisposable
Can't find a way to make it register more globally via the TabConfig.razor file, couldn't find ways to call the variables.
Make the main variables static on Welcome.razor and use them on another tab.
Make all main variables connected to this in each tab privately static since I can't make a global page to call from.
I am just trying to make the Resize event more of a global registration somehow with the Teams Tookit Tab app.
There were multiple problems.
JS == null was causing it to run 1 time no matter what so it would never invoke again.
After removing all statics and un-registering the event with IDisposable, no memory leaks should be left.
Issue is now resolved and loads/unloads on each tab.

Is it possible to load dynamically existing page in a Bootstrap Modal body

I'm new to a Blazor and now I'm working to Blazor WebAssembly project.
I have couple razor page with a table where I'm displaying data from SQL. When I click in one of the table rows it opens a page where I can do the CRUD operation.
Now, instead of opening a page to do the CRUD operation I need to open a bootstarp modal and do the CRUD operation.
I'm doing a generic ModalComponent where I have the header and the footer of the modal.
Is it possibile to load dynamically the body of the model, which in this case it will be the CRUD operation pages that I already had done?
The code below demonstrates how to implement a generic modal dialog i.e. a wrapper for any component/page that you want to display in modal mode. The concrete implementation of IModalDialog shows how to implement a Bootstrap version.
I use a more complex version of this. My edit/View components are written to run in modal or full page mode.
Support Classes
First three support classes that are fairly self-evident:
public class ModalResult
{
public ModalResultType ResultType { get; private set; } = ModalResultType.NoSet;
// Whatever object you wish to pass back
public object? Data { get; set; } = null;
// A set of static methods to build a BootstrapModalResult
public static ModalResult OK() => new ModalResult() { ResultType = ModalResultType.OK };
public static ModalResult Exit() => new ModalResult() { ResultType = ModalResultType.Exit };
public static ModalResult Cancel() => new ModalResult() { ResultType = ModalResultType.Cancel };
public static ModalResult OK(object data) => new ModalResult() { Data = data, ResultType = ModalResultType.OK };
public static ModalResult Exit(object data) => new ModalResult() { Data = data, ResultType = ModalResultType.Exit };
public static ModalResult Cancel(object data) => new ModalResult() { Data = data, ResultType = ModalResultType.Cancel };
}
public class ModalOptions
{
// Whatever data you want to pass
// my complex version uses a <string, object> dictionary
}
public enum ModalResultType
{
NoSet,
OK,
Cancel,
Exit
}
IModalDialog Interface
Now the modal dialog Interface.
using Microsoft.AspNetCore.Components;
public interface IModalDialog
{
ModalOptions Options { get; }
// Method to display a Modal Dialog
Task<ModalResult> ShowAsync<TModal>(ModalOptions options) where TModal : IComponent;
// Method to update the Modal Dialog during display
void Update(ModalOptions? options = null);
// Method to dismiss - normally called by the dismiss button in the header bar
void Dismiss();
// Method to close the dialog - normally called by the child component TModal
void Close(ModalResult result);
}
The Base Bootstrap Implementation
There's no JS.
I use a TaskCompletionSource to make the open/close an async process.
The component to display is passed as part of the open call.
#inherits ComponentBase
#implements IModalDialog
#if (this.Display)
{
<CascadingValue Value="(IModalDialog)this">
<div class="modal" tabindex="-1" style="display:block;">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Modal title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" #onclick="() => Close(ModalResult.Exit())"></button>
</div>
<div class="modal-body">
#this.Content
</div>
</div>
</div>
</div>
</CascadingValue>
}
#code {
public ModalOptions Options { get; protected set; } = new ModalOptions();
public bool Display { get; protected set; }
protected RenderFragment? Content { get; set; }
protected TaskCompletionSource<ModalResult> _ModalTask { get; set; } = new TaskCompletionSource<ModalResult>();
public Task<ModalResult> ShowAsync<TModal>(ModalOptions options) where TModal : IComponent
{
this.Options = options ??= this.Options;
this._ModalTask = new TaskCompletionSource<ModalResult>();
this.Content = new RenderFragment(builder =>
{
builder.OpenComponent(1, typeof(TModal));
builder.CloseComponent();
});
this.Display = true;
InvokeAsync(StateHasChanged);
return this._ModalTask.Task;
}
public void Update(ModalOptions? options = null)
{
this.Options = options ??= this.Options;
InvokeAsync(StateHasChanged);
}
public async void Dismiss()
{
_ = this._ModalTask.TrySetResult(ModalResult.Cancel());
this.Display = false;
this.Content = null;
await InvokeAsync(StateHasChanged);
}
public async void Close(ModalResult result)
{
_ = this._ModalTask.TrySetResult(result);
this.Display = false;
this.Content = null;
await InvokeAsync(StateHasChanged);
}
}
Demo
A simple "Edit" component:
#inject NavigationManager NavManager
<h3>EditForm</h3>
<div class="p-3">
<button class="btn btn-success" #onclick=Close>Close</button>
</div>
#code {
[CascadingParameter] private IModalDialog? modal { get; set; }
private void Close()
{
if (modal is not null)
modal.Close(ModalResult.OK());
else
this.NavManager.NavigateTo("/");
}
}
And a test page:
#page "/"
<div class="p-2">
<button class="btn btn-primary" #onclick=OpenDialog>Click</button>
</div>
<div class="p-2">
result: #Value
</div>
<ModalDialog #ref=this.modal />
#code {
private string Value { get; set; } = "Fred";
private IModalDialog? modal;
private IModalDialog Modal => modal!;
private async void OpenDialog()
{
var options = new ModalOptions();
var ret = await Modal.ShowAsync<EditorForm>(new ModalOptions());
if (ret is not null)
{
Value = ret.ResultType.ToString();
}
StateHasChanged();
}
}

Coded Ui get past Loading Screen on all browsers

I am using Microsoft Coded Ui on Visual Studio 2015 Update 1.
I am building coded Ui tests for the website am running into a problem on Chrome and Firefox. The website is onboard.passageways.com. There is a a div that has this information:
<div id="loadingOverlay" data-bind="fadeVisible: loading" style="display: none; width: 100%; height: 100%; position: fixed; top: 0px; right: 0px; bottom: 0px; left: 0px; z-index: 9999; background: rgb(204, 204, 204);">
Chrome and Firefox render the page so fast that they see the loadingOverlay when the style is "display: table". This loading overlay is on almost every part of the site.
My question is how do I tell Chrome and Firefox to wait for the loadingOverlay to display none so that it can click on the elements in the background.
I have tried this code but it keeps saying that the collection is at 1.
public static HtmlDiv GetOverlay(UITestControl parent, string id)
{
var div = new HtmlDiv(parent);
div.SearchProperties.Add(HtmlDiv.PropertyNames.Id, id);
GetOverlayProperty(div);
return div;
}
private static UITestControlCollection GetOverlayProperty(HtmlDiv overlay)
{
overlay.SearchProperties.Add(HtmlControl.PropertyNames.ControlDefinition, "display: table",
PropertyExpressionOperator.Contains);
UITestControlCollection collection = overlay.FindMatchingControls();
if (collection.Any())
{
GetOverlay(browser, "loadingOverlay");
}
return collection;
}
My Test Initialize
[TestInitialize]
public void ClassInitializer()
{
BrowserWindow.CurrentBrowser = ConfigurationManager.AppSettings.Get("Browser-Type");
CodedUIUtils.browser = BrowserWindow.Launch(new Uri(ConfigurationManager.AppSettings.Get("Browser-Url")));
CodedUIUtils.browser.CloseOnPlaybackCleanup = false;
CodedUIUtils.browser.Maximized = false;
}
My Login Test
[TestMethod]
public void LoginAndLogout()
{
CodedUIUtils.ClickButton(CodedUIUtils.browser, "PassagewaysLogin");
CodedUIUtils.LoginGuest(CodedUIUtils.browser);
CodedUIUtils.ClickLogon(CodedUIUtils.browser, "Logon");
HtmlDiv overlay = CodedUIUtils.GetOverlay(CodedUIUtils.browser, "loadingOverlay");
overlay.WaitForControlNotExist();
CodedUIUtils.ClickLink(CodedUIUtils.browser, "Log Out");
Playback.Wait(2000);
}
Login Utils
public static class CodedUIUtils
{
public static BrowserWindow browser;
public static void LoginGuest(BrowserWindow browser)
{
EnterText(browser, "Email", "onboardTestUserId+guest#gmail.com");
EnterText(browser, "LoginPassword", "Testing2!");
}
public static void ClickButton(UITestControl parent, string id)
{
var button = new HtmlButton(parent);
button.SearchProperties.Add(HtmlButton.PropertyNames.Id, id);
Mouse.Click(button);
}
public static void EnterText(UITestControl parent, string id, string value)
{
var edit = new HtmlEdit(parent);
edit.SearchProperties.Add(HtmlEdit.PropertyNames.Id, id);
edit.Text = value;
}
public static void ClickLink(UITestControl parent, string innerText)
{
var link = new HtmlHyperlink(parent);
link.SearchProperties.Add(HtmlHyperlink.PropertyNames.InnerText, innerText);
link.WaitForControlReady();
Mouse.Click(link);
}
public static void ClickLogon(UITestControl parent, string value)
{
var logonInput = new HtmlInputButton(parent);
logonInput.SearchProperties.Add(HtmlInputButton.PropertyNames.ValueAttribute, value);
Mouse.Click(logonInput);
}
}
It is a common best practice that when something may have loading time/intro animation or just in general to code with WaitForControlReady or WaitForControlExist Functions.
So unless you specifically really care about the loading overlay, then you can just put the function on the first object that the overlay is affecting you really want to interact with.
[TestMethod]
public void StackOverflow()
{
BrowserWindow browser = BrowserWindow.Launch(new Uri("https://onboard.passageways.com/"));
var _hyper = new HtmlButton(browser);
_hyper.SearchProperties.Add("ID", "PassagewaysLogin");
_hyper.WaitForControlReady();
LoadingOverlay(browser);
Mouse.Click(_hyper);
var _email = new HtmlEdit(browser);
_email.SearchProperties.Add("ID", "Email");
_email.WaitForControlReady();
LoadingOverlay(browser);
Keyboard.SendKeys(_email, "testemail#email.com");
Playback.Wait(10000);
}
public static void LoadingOverlay(BrowserWindow browser)
{
var _image = new HtmlDiv(browser);
_image.SearchProperties.Add("ID", "loadingOverlay");
_image.WaitForControlReady();
}

Private chat with SignalR - talk to strangers

I would like to write a simple chat on the principle as omegle.com. I wrote that if the user enters the server and the queue is empty creates a new group and falls to the queue. When the other person enters, it connects with this in the queue.
Here's my code:
public class UserGroup
{
public string GroupName { get; set; }
}
public class ChatHub : Hub
{
public static Queue<UserGroup> Users = new Queue<UserGroup>();
public static string Group { get; set; }
public Task JoinGroup(string groupName)
{
return Groups.Add(Context.ConnectionId, groupName);
}
public override System.Threading.Tasks.Task OnConnected()
{
if(Users.Count == 0)
{
var user = new UserGroup { GroupName = Context.ConnectionId };
Users.Enqueue(user);
Group = user.GroupName;
JoinGroup(user.GroupName);
}
else
{
JoinGroup(Users.Peek().GroupName);
Group = Users.Peek().GroupName;
Users.Dequeue();
}
return base.OnConnected();
}
public void SayHello(string name, string helloMsg)
{
Clients.Caller.Hello(name, helloMsg);
}
public void Send(string msg)
{
Clients.Group(Group).SendMessage(msg);
}
}
Unfortunately, when I connect to someone else, everything breaks down and does not create a new group for new people. All static data, but unfortunately not SignalR allows otherwise. You have an idea how to get around this?
I don't think you need a Queue or use groups in this scenario. The easiest solution would be to send the stranger's client id to the other client. You should incorporate locking because you access shared state from different hub instances:
Create a new ASP.NET Web Application project and select MVC as framework.
Add Microsoft.AspNet.SignalR and knockoutjs nuget packages and update all packages.
The current SignalR version requires that you add a Startup class:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
Add a hub class:
public class ChatHub : Hub
{
private static string waitingUser;
private static readonly object SyncLock = new object();
public void SendMessage(string text, string clientId)
{
this.Clients.Client(clientId).addMessage(text);
}
public override Task OnConnected()
{
var newUser = this.Context.ConnectionId;
string otherUser;
lock (SyncLock)
{
if (waitingUser == null)
{
waitingUser = newUser;
return base.OnConnected();
}
otherUser = waitingUser;
waitingUser = null;
}
this.Clients.Caller.startChat(otherUser);
this.Clients.Client(otherUser).startChat(newUser);
return base.OnConnected();
}
}
Finally, add the following code to your view:
#{
ViewBag.Title = "Chat wit a Stranger";
}
<h1>Chat with a Stranger</h1>
<div data-bind="foreach: messages">
<div data-bind="text: $data"></div>
</div>
<hr/>
<form data-bind="submit: send, visible: connected">
<input type="text" data-bind="value: text" />
<button type="submit">Send</button>
</form>
<script src="~/Scripts/knockout-3.2.0.debug.js"></script>
<script src="~/Scripts/jquery-2.1.1.js"></script>
<script src="~/Scripts/jquery.signalR-2.1.2.js"></script>
<script src="~/signalr/hubs"></script>
<script>
var hub = $.connection.chatHub;
var vm = {
otherUser: "",
messages: ko.observableArray(["Waiting for a stranger..."]),
connected: ko.observable(false),
text: ko.observable(""),
send: function () {
var text = vm.text();
if (text.length == 0) return;
hub.server.sendMessage(text, vm.otherUser);
vm.messages.push("You: " + text);
vm.text("");
},
addMessage: function(text) {
vm.messages.push("Stranger: " + text);
},
startChat: function (otherUser) {
vm.otherUser = otherUser;
vm.messages(["A stranger has connected. Say hello!"]);
vm.connected(true);
}
}
ko.applyBindings(vm);
hub.client.startChat = vm.startChat;
hub.client.addMessage = vm.addMessage;
$.connection.hub.start();
</script>

Categories