I am trying to retrieve a value from window.sessionStorage in my BLazor application. I have been able to successfully SET the value. It's when I go to retreive it that it simply stops ... no error or anything ... it just hits the function and stops.
I have my code set up in a "Code-Behind" structure so my .razor page is inheriting from a BASE class. In that base class is where I am storing all the logic.
This is the RAZOR page ...
#page "/PhotoViewer"
#inherits PhotoViewerBase
<h3>Photo Viewer</h3>
<p>ActiveMediaCode: #ActiveMediaCode</p>
<button class="btn btn-primary" #onclick="#btn_OnClick">Push it</button>
And this is PhotoViewerBase that it inherits from ...
namespace IF.APP.BlabaBoothBlazor.Pages
{
public class PhotoViewerBase : Models.PageBase
{
protected String ActiveMediaCode { get; set; }
protected override Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
}
return base.OnAfterRenderAsync(firstRender);
}
protected void btn_OnClick()
{
ActiveMediaCode = GetMediaCodeAsync().Result;
}
}
}
namespace IF.APP.BlabaBoothBlazor.Models
{
public class PageBase : ComponentBase
{
[Inject]
protected IJSRuntime JS { get; set; }
[Inject]
protected NavigationManager Navigator { get; set; }
public async Task<String> GetMediaCodeAsync()
{
// STOPS PROCESSING HERE
return await JS.InvokeAsync<String>("sessionStorage.getItem", "ActiveMediaCode");
}
public async Task SetMediaCodeAsync(String mediaCode)
{
await JS.InvokeVoidAsync("sessionStorage.setItem", "ActiveMediaCode", mediaCode);
}
}
}
The SetMediaCodeAsync() method works with no problem. But when I click on the Button on the razor page I step through to the GetMediaCodeAsync() method and, where commented above, the debugger just stops. It doesn't drop out of debug mode ... just nothing. My output window begins to report that threads are gracefully shutting down but not errors or exceptions.
I am making the call AFTER the page has loaded in the browser, ensuring that the sessionStorage is available.
Just looking for a fresh perspective and maybe some ideas ...
ActiveMediaCode = GetMediaCodeAsync().Result;
is a very wrong way to do async. It probably deadlocks.
Replace
protected void btn_OnClick()
{
ActiveMediaCode = GetMediaCodeAsync().Result;
}
with
protected async Task btn_OnClick()
{
ActiveMediaCode = await GetMediaCodeAsync();
}
don't change the razor markup, that is fine.
Related
I am attempting to use some JS code in my blazor client side components, I have followed some examples online but cannot get it to work.
Component.razor.cs
namespace MyApp.Web.Components.Select
{
public partial class Select
{
[Parameter]
public RenderFragment ChildContent { get; set; } = default!;
[Inject]
public IJSRuntime JSRuntime { get; set; }
private IJSObjectReference jsModule;
public void OnClick()
{
ShowAlertWindow();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
try
{
jsModule = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./scripts/test.js");
}
catch (Exception ex)
{
System.Console.WriteLine($"Failed to load JS module. Error: {ex}");
}
}
public void ShowAlertWindow()
{
System.Console.WrteLine("Showing Alert Window"); // this works
JSRuntime.InvokeVoidAsync("showAlert", "hello"); // this does nothing
}
}
}
Component.razor
<div class="wrapper">
<div #onclick="#OnClick">
#ChildContent
</div>
</div>
wwwroot/scripts/test.js
export function showAlert(message) {
alert(message);
}
At run time, the click should run the custom showAlert function in the JS file. but nothing seems to happen, no errors in browser dev console neither.
What I am doing wrong?
You have to use the IJSObjectReference that you obtained inside OnAfterRenderAsync to invoke the showAlert function:
jsModule.InvokeVoidAsync("showAlert", "hello");
I have 4 pages and a service, the purpose of each pages and a service:
First page: animals introduction > when user select animals then start the service
Second page: cities selection
Service (MyService): to get the data from the API and assign the result to the session storage which can be accessed later on from any pages and afterwards stop the service, otherwise loop through the service until it found the result or the service stopped manually
Third page: Summary of what user selected
Fourth page: to stop the service and to get the animals information from the session storage if found (foods liked or disliked by animals selected, animal's age, from where the animal coming from, etc), if not found then call the API directly from this page (but supposedly the data already being assigned to the session storage)
The reason on why I put as a service, is because I don't want user to wait and also user could select multiple animals from the first page and then pass the data to the service and the response from the service could take more than 1 second
Above scenarios already achieved, however when I wants to set the response from the API to the session storage, it didn't response to anything, then when comes to the fourth page, there is no data in session storage which when I manually query the DB, there is a response and also when I put the breakpoint at the line where it will assign to the session storage, it didn't get pass there and the last line after set to session storage never executed
Here is the code that I am using:
First Page
#inject
Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage.ProtectedSessionStorage SessionStorage
#inject MyService MyService
#code {
protected override void OnInitialized()
{
MyService.SessionStorage = SessionStorage;
}
// On user submit will execute below function
private void SetBackgroundService()
{
if (MyService.IsStartService)
return;
MyService.IsStartService = true;
if (!MyService.IsRunning)
MyService.StartAsync(new System.Threading.CancellationToken());
}
}
MyService
public class MyService : IHostedService, IDisposable
{
public bool IsRunning { get; set; }
public bool IsStartService { get; set; }
public Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage.ProtectedSessionStorage SessionStorage { get; set; }
public Task StartAsync(CancellationToken cancellationToken)
{
Task.Run(async () =>
{
if (!IsStartService)
return;
while (!cancellationToken.IsCancellationRequested)
{
if (!IsStartService)
return;
await Task.Delay(2000, cancellationToken);
await DoWorkAsync();
}
}, cancellationToken);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
IsStartService = IsRunning = false;
return Task.CompletedTask;
}
private async Task DoWorkAsync()
{
IsRunning = true;
var Animals = await <API Call>
if (Animals == null)
return;
await SessionStorage.SetAsync("Animals", JsonConvert.SerializeObject(Animals)); // this is where the debug stops
await StopAsync(new CancellationToken()); // this line never executed
}
public void Dispose()
{
}
}
Fourth page
#inject MyService MyService
protected override void OnInitialized()
{
if (MyService.IsRunning)
MyService.StopAsync(new System.Threading.CancellationToken());
}
Startup
public void ConfigureServices(IServiceCollection services)
{
// hide other codes for simplicity
services.AddHostedService<MyService>();
}
Any idea what I am doing wrong?
Thank you very much.
I am trying to have some basic configuration from json file to a singleton service inside my client side blazor application at the start up.
Below is my code setup
AppConfig and IAppConfig files
interface IAppConfig
{
string BaseUrl { get; set; }
string Source { get; set; }
}
and
public class AppConfig : IAppConfig
{
public string BaseUrl { get; set; }
public string Source { get; set; }
}
Than a json file by the name of environment.json inside wwwroot as wwwroot/ConfigFiles/environment.json
Than a service to read this file
interface ISharedServices
{
Task<AppConfig> GetConfigurationAsync();
}
and
public class SharedServices : ISharedServices
{
private HttpClient Http { get; set; }
public SharedServices(HttpClient httpClient)
{
Http = httpClient;
}
public async Task<AppConfig> GetConfigurationAsync()
{
return await Http.GetJsonAsync<AppConfig>("ConfigFiles/environment.json");
}
}
Now i am calling it into my component which load first
public class IndexComponent : ComponentBase
{
[Inject]
internal IAppConfig AppConfig { get; set; }
[Inject]
internal ISharedServices sharedServices { get; set; }
protected override async Task OnInitializedAsync()
{
var appconfig = await sharedServices.GetConfigurationAsync();
AppConfig = appconfig;
}
}
All this works fine , but i want to have this configuration ready at the time of application load in browser , so as suggested by "auga from mars" in my other Question i tried below code inside startup.cs at the moment i add IAppConfig as singleton service
services.AddSingleton<IAppConfig, AppConfig>(provider =>
{
var http = provider.GetRequiredService<HttpClient>();
return http.GetJsonAsync<AppConfig>("ConfigFiles/environment.json").GetAwaiter().GetResult();
});
But , buy using this code the blazor app never start up , all it show a blank white page with text Loading.... , not even any error but in every 5 min pop up show - page taking too much time to load with two option of wait and close .
If i change this code a bit from
return http.GetJsonAsync<AppConfig>("ConfigFiles/environment.json").GetAwaiter().GetResult();
to
return http.GetJsonAsync<AppConfig>("ConfigFiles/environment.json").Result;
Than it say - "Maximum call stack size exceed"
How to have configuration ready at startup ??
Update 1:
A little Update
in Basecomponent file , code is
protected override async Task OnInitializedAsync()
{
var appconfig = await sharedServices.GetConfigurationAsync();
AppConfig.BaseUrl = appconfig.BaseUrl;
AppConfig.Source = appconfig.Source;
}
I have to set every property one by one manually , need to get rid of this too
I am writing an website where I get some data from the database. When starting the website on my computer I get the data for 15 min. After these 15 min the files don't load anymore.
When I restart the backend (Visual Studio C#) then it happens the same.
Controller from the file:
[UnitOfWorkActionFilter]
[RoutePrefix("categories")]
public class CategoriesController : ApiController {
private ICategoriesProcessor _categoriesProcessor;
private IPagedDataRequestFactory _pagedDataRequestFactory;
public CategoriesController(ICategoriesProcessor categoriesProcessor, IPagedDataRequestFactory pagedDataRequestFactory) {
_pagedDataRequestFactory = pagedDataRequestFactory;
_categoriesProcessor = categoriesProcessor;
}
[Route()]
[HttpGet]
public PagedResponse<Category> GetCategories(HttpRequestMessage requestMessage) {
var request = _pagedDataRequestFactory.Create(requestMessage.RequestUri);
return _categoriesProcessor.GetCategories(request);
}
}
here is the code from the UnitWorkActionFilterAttribute
public class UnitOfWorkActionFilterAttribute : ActionFilterAttribute {
public virtual IActionTransactionHelper ActionTransactionHelper { get { return WebContainerManager.Get<IActionTransactionHelper>(); } }
public override bool AllowMultiple { get { return false; } }
public override void OnActionExecuting(HttpActionContext actionContext) {
ActionTransactionHelper.BeginTransaction();
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) {
ActionTransactionHelper.EndTransaction(actionExecutedContext);
ActionTransactionHelper.CloseSession();
}
}
I found out that the problem is, that the Session opens but not close but I don't know how to fix it.
Does someone has an idea why it's not working?
have you try access from Fiddler ??? what the client you used to call your API...
see what the fiddler got message, and if you call the API, that is call that Method API or not...need detail information, this error have come to method or just in client stuff...
I am adding a form to my .net web forms application and I want to redirect the user to another page but display a status message after the redirect such as 'Your articles has been submitted successfully'.
Any good ways of doing this?
I was thinking of doing it with sessions and a user control but wanted to see if there is an easier way.
Thought about code like this:
User control codebehind:
public String SessionName { get; set; }
public String Message
{
get
{
if (Session[SessionName] == null)
return String.Empty;
return Session[SessionName].ToString();
}
}
protected void Page_Unload(object sender, EventArgs e)
{
Session[SessionName] = null;
}
User control markup:
<% if (!String.IsNullOrEmpty(Message))
{%>
<div>
<%= Message %>
</div>
<%} %>
No, saving it in session and then reading on another page is the way to go.
What if your redirection included a query-string parameter that the destination page recognized and selected the right message? That avoids session altogether.
Landed on this post when trying to figure out how to pass the default StatusMessage field you get in net6 web apps (Razor Pages in this case).
I got it working with session as suggested above. However, I realised that in my case it was utterly unnecessary: there is a [TempData] attribute you can stick on the StatusMessage property of one page that will store it in memory (I'm guessing) and delete it after you read it.
Therefore, all you need to do is have the field defined in both the caller page and the receiving page and just set it on the caller page - the receiving page will find the value you set on the caller when you try to read it on the html side.
Next time you try to read it it will have been deleted (so you don't keep showing the same message over and over).
Example.
Caller page post call:
public class Create : PageModel
{
private readonly IMyService _service;
public Create(IMyService service)
{
_service = service;
}
[TempData] public string StatusMessage { get; set; } = string.Empty;
public async Task<IActionResult> OnPostAsync()
{
var model = new SomeModel();
try
{
await _service.Create(model);
}
catch(Exception e)
{
StatusMessage = $"Error: {e.Message}";
return Page();
}
StatusMessage = $"Model created successfully";
return RedirectToPage("/SomeModels/Index");
}
}
Receiving page:
public class Index : PageModel
{
[TempData] public string StatusMessage { get; set; } = string.Empty;
public readonly List<ExerciseCategory> Models;
public async Task<IActionResult> OnGet()
{
var models = await _service.Get();
Models.AddRange(models);
return Page();
}
}