Handle Razor Page Exception - asp.net-core

Hi I'm getting strange behavior in our AspNetCore 2.1 application using Razor Pages. When an exception escapes from a Razor Page action like OnGet the application crashes. The exception never reaches any middleware. It looks like the application seems is crashing somewhere in the internal PageActionInvoker.Next method.
I can't paste the code as it far too much. But the following will crash the application:
public async void OnGet() {
await Task.CompletedTask; // normally we await something else
throw new Exception("Boom!");
}
Note:
We are:
combining Views and Razor Pages. The Views are part of the 'older' section of the application.
we are allowing Razor Page area's.
Do we have to configure some feature?
Are we missing something? Does someone have suggestions?
FOUND IT
Nothing special. Well AspNetCore accepts async void OnGet and everything works fine UNTIL an exception is raised.
using the following fixed it.
public async Task OnGetAsync() {

It seems that even AspNetCore doesn't handle the following construction well:
public async void OnGet() {
...
}
Even though it's accepted and seems to be working possible exceptions are not handled correctly.
The correct way to do this is:
public async Task OnGetAsync() {
....
}
Don't know whether this classifies as a AspNetCore bug ... in that sense maybe it should be rejected like many other things. Oh well ... bug fixed.

Related

How should I run a background task that also updates the UI in an intellij settings plugin

I am adding some changes to an intelij plugin that integrates with vault I have settings page that implements Configurable that has a form for credentials and then a "Test Login" button. On button click I want to spawn an asynchronous background task to test the credentials and then update the UI with either success or failure.
Here is a screenshot of my settings
As far as I can tell the correct way to do this would be to use a background task but that requires a "project" which as far as I can tell you have to get from an AnAction and I don't really see how that would work in this context. Here are a few approaches I've played around with
This still blocks the UI and spits out a warning about the UI being blocked for too long
ApplicationManager.getApplication().executeOnPooledThread(() ->
ApplicationManager.getApplication().invokeLaterOnWriteThread(() -> {
// async work
// repaint
}, ModalityState.stateForComponent(myMainPanel)));
// I don't know how to get the project or if I even should here.
ProgressManager.getInstance().run(new Task.Backgroundable(project, "Login"){
public void run(#NotNull ProgressIndicator progressIndicator) {
// async work
// repaint when done
}});
All of this is happening in my AppSettingsComponent button click addActionListener. Here is my complete source code on github
I had the same problem with the project instance required. But by looking at the Task.Backgroundable constructor you can pass a #Nullable project. So I just pass null and it works well for me.
ProgressManager.getInstance().run(new Task.Backgroundable(null, "Login") {
public void run(#NotNull ProgressIndicator progressIndicator) {
// your stuff
}
});

Two way databinding to singleton service Blazor Serverside

I have been playing with Blazor on the client using Webassembly quite a bit. But I thought I would try the serverside version now and I had a simple idea I wanted to try out.
So my understading was that Blazor serverside uses SignalR to "push" out changes so that the client re-renders a part of its page.
what I wanted to try was to databind to a property on a singleton service like this:
#page "/counter"
#inject DataService dataService
<h1>Counter</h1>
<p>Current count: #currentCount ok</p>
<p> #dataService.MyProperty </p>
<p>
#dataService.Id
</p>
<button class="btn btn-primary" #onclick="IncrementCount">Click me</button>
#code {
int currentCount = 0;
void IncrementCount()
{
currentCount++;
dataService.MyProperty += "--o--|";
}
}
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<WeatherForecastService>();
services.AddSingleton<DataService>();
}
Service:
namespace bl1.Services
{
public class DataService
{
public DataService()
{
this.Id = System.Guid.NewGuid().ToString();
}
public string Id {get;set;}
public string MyProperty { get; set; }
}
}
So my question is this. Why, if I open up this page in two tabs, do I not immediately see the value being updated for the property MyProperty with SignalR when I am changing the value on the property in one tab in the other tab? Is there a reason that is not supposed to work or am I just simply doing it wrong?
I thought the upside of using Blazor on the serverside was that you could easily use the fact that SignalR is available and get live updates when values change on the server.
I do get the latest value from the singleton service in the other tab but only after I click the button there.
Sorry you didn't get a better answer earlier Ashkan (I just read your question now). What you were attempting is actually something Blazor does very well and you are correct it is the perfect architecture for problems like this. Using SignalR directly, like the above answer suggested, would be correct for a Blazor WASM solution, but that doesn't address your question, which was about using Server-side Blazor. I will provide the solution below.
The important point is to understand that a blazor component does not "poll" for changes in its bound properties. Instead, a component will automatically re-render itself (to an internal tree) if say a button is clicked on that component. A diff will then be performed against the previous render, and server side blazor will only send an update to the client (browser) if there is a change.
In your case, you have a component that uses an injected singleton for its model. You then open the component in two tabs and, as expected (given Blazor's architecture), only the component in the tab where you clicked the button is re-rendering. This is because nothing is instructing the other instance of the component to re-render (and Blazor is not "polling" for changes in the property value).
What you need to do is instruct all instances of the component to re-render; and you do this by calling StateHasChanged on each component.
So the solution is to wire up each component in OnInitialized() to an event you can invoke by calling Refresh() after you modify a property value; then un-wiring it in Dispose().
You need to add this to the top of your component to correctly clean up:
#implements IDisposable
Then add this code:
static event Action OnChange;
void Refresh() => InvokeAsync(StateHasChanged);
override protected void OnInitialized() => OnChange += Refresh;
void IDisposable.Dispose() => OnChange -= Refresh;
You can move my OnChange event into your singleton rather than having it as a static.
If you call "Refresh()" you will now notice all components are instantly redrawn on any open tabs. I hope that helps.
See the documentation here
A Blazor Server app is built on top of ASP.NET Core SignalR. Each
client communicates to the server over one or more SignalR connections
called a circuit. A circuit is Blazor's abstraction over SignalR
connections that can tolerate temporary network interruptions. When a
Blazor client sees that the SignalR connection is disconnected, it
attempts to reconnect to the server using a new SignalR connection.
Each browser screen (browser tab or iframe) that is connected to a
Blazor Server app uses a SignalR connection. This is yet another
important distinction compared to typical server-rendered apps. In a
server-rendered app, opening the same app in multiple browser screens
typically doesn't translate into additional resource demands on the
server. In a Blazor Server app, each browser screen requires a
separate circuit and separate instances of component state to be
managed by the server.
Blazor considers closing a browser tab or navigating to an external
URL a graceful termination. In the event of a graceful termination,
the circuit and associated resources are immediately released. A
client may also disconnect non-gracefully, for instance due to a
network interruption. Blazor Server stores disconnected circuits for a
configurable interval to allow the client to reconnect. For more
information, see the Reconnection to the same server section.
So in your case the client in another tab is not notified of the changes made on another circuit within another ConnectionContext.
Invoking StateHasChanged() on the client should fix the problem.
For the problem you describe you better use plain SignalR, not Blazor serverside.
Just to add a little more to D. Taylor's answer:
I believe in most instances you'd want to move that OnChange action into the service.
In your services .cs you should add:
public event Action OnChange;
private void NotifyDataChanged() => OnChange?.Invoke();
private int myProperty; //field
public int MyProperty // property
{
get { return myProperty; }
set
{
myProperty= value;
NotifyDataChanged();
}
}
Every time that value is changed, it will now invoke that Action.
Now adjusting what D. Taylor did in the actual .razor file, in the #code{} section now just bind the refresh method to that Action in your service...
void Refresh() => InvokeAsync(StateHasChanged);
override protected void OnInitialized() => MyService.OnChange += Refresh;
void IDisposable.Dispose() => OnChange -= Refresh;
This should now push updates to the client!

Razor Pages Default Page in aspnetcore 2

By default a Razor Page app goes to Home/Index
Is there a way to change this to Home/App?
This is quite easy in MVC, but Razor pages using a different routing setup and thus MVC routing does not apply.
I would think it would be in options somewhere, but I don't see it:
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeFolder("/Account/Manage");
options.Conventions.AuthorizePage("/Account/Logout");
options. ??SetDefaultPage??
});
I have tried this:
options.Conventions.AddPageRoute("/App", "");
But now two default routes are found and an error is generated:
AmbiguousActionException: Multiple actions matched. The following actions matched route data and had all constraints satisfied:
Page: /App
Page: /Index
It's possible to resolve this error by removing Pages/Index.cshtml from the project, but I wanted to keep that page as well.
In my case the ambiguity was caused by Pages/Index.cshtml left in project.
This worked:
options.Conventions.AddPageRoute("/App", "");
remove or rename Pages/Index.cshtml
Pretty sure it isn't possible. The docs say the runtime controls the search for Index as the default. I couldn't find where that happens in the current release, but IndexFileName is a static in the new internal PageRouteModelFactory class added to the upcoming release:
private static readonly string IndexFileName = "Index" + RazorViewEngine.ViewExtension;
It doesn't seem like it would be difficult to just add a config property to RazorPagesOptions, though. The ASP.NET guys are pretty responsive, I'd open a request as a GitHub issue and hope for the best.
I solved the issue by using Microsoft.AspNetCore.Rewrite:
Then adding code to replace the default Index action, in my case with Portfolio:
var options = new RewriteOptions()
.AddRedirect("^", "portfolio"); // Replace default index page with portfolio
More detailed article about rewrite options - https://learn.microsoft.com/en-us/aspnet/core/fundamentals/url-rewriting?tabs=aspnetcore2x
Another way is to simply redirect from the Index OnGet method, like so:
public class IndexModel : PageModel
{
public IActionResult OnGet()
{
return Redirect("/Welcome");
}
}
Notice that I have change the return type of the OnGet method.

kendo ui editorfor imagebrowser returns 403

I'm new to web development and I'm trying to implement the Kendo UI editor with an image browser to insert into the document on an MVC 4.5 page. the editor is working fine, however, when i click the insert image button i gt a 403 forbidden popup message.
I've created a custom image browser controller pointing to ~/Content/images.
and in my view, i am using the custom browser controller within my code
#(Html.Kendo().EditorFor(m => m.QuestionText)
.Encode(false)
.HtmlAttributes(new { style = "width: 100%; height: 200px" })
.Name("EditQuestionText")
.Tools(tools => tools.Clear().InsertImage())
.ImageBrowser(imageBrowser => imageBrowser
.Image("~/JFA/QuestionImages/{0}")
.Read("Read", "JFAImageBrowser"))
)
I've compared my code to the sample project from Kendo for the EditorFor (which will browse the folder) but can find no discernible differences... I also cannot find much in the way of other people who are having this problem so i suspect there is a setting that i cannot find that is causing my issue, any help would be GREATLY appreicated
my image browser (taken directly from the demo)
public class JFAImageBrowserController : EditorImageBrowserController
{
private const string contentFolderRoot = "~/Content/images";
public override string ContentPath
{
get
{
return contentFolderRoot;
}
}
additionally, using Fiddler the click event for the "Insert Image" button is
GET /JFA/JFAImageBrowser/Read?path=%2F HTTP/1.1
where as the demo is
POST /ImageBrowser/Read HTTP/1.1
I don't know why the demo is using a POST where as mine is using a GET, unless this is because of the overridden image browswer
That code looks fine. Can you make sure your JFAImageBrowser controller looks something like this?
public class BlogImagesController : EditorImageBrowserController
{
//
// GET: /BlogImage/
public ActionResult Index()
{
return View();
}
public override string ContentPath
{
get { return AssetFilePaths.BlogContentPath; }
}
}
It's important that it inherits from EditorImageBrowserController
Also, a 403 may mean that the user doesn't have permission to access the directory. Check the permissions for the user you're running as.
It turns out my problem was in the _Layout page. I was using bundling and either
A) I made some error when setting up the bundling
-or-
b) the bundling didn't work as expected/intended.
either way i added the individual script/java script references and it works as expected.
Here is the solution to this problem
the page this issue fixed was it kendo forum
http://www.telerik.com/forums/implementing-image-browser-for-editor
and the direct link for the demo
http://www.telerik.com/clientsfiles/e3e38f54-7bb7-4bec-b637-7c30c7841dd1_KendoEditorImageBrowser.zip?sfvrsn=0
and if this demo didn't work you can see this sample i made from above
https://www.mediafire.com/?9hy728ht4cnevxt
you can browse the editor through HomeController and the action name is homepage (home/homepage)
& I think that the error was in different uses of paths between the base controller & child controller you make.

Kendo UI Upload Async Error

I downloaded a trial version of Kendo.UI, so logging onto the forums, at this stage is not possible, hoping someone on here can help me solve this problem.
I'm trying to implement the async upload onto a basic MVC 4 application. I've added a reference to the Kendo.UI.MVC wrappers, and added the necessary namespace Kendo.UI.MVC to both web.config files (root and under Views).
If I implement a basic uploader on my landing view (index.cshtml) it works fine:
<form action="/Home/Save" method="post">
#(Html.Kendo().Upload().Name("files"))
<input type="submit" value="Submit" />
</form>
However, as soon as I add the Save() method to Async, I get an "index was out of range" exception. I know it's the save method, because if I just add "AutoUpload(true)" without an action reference, it does not throw an exception. If I just add "Remove("Remove", "Home")" it still shows the Select button, without an error, but the "Save("Save", "Home")" method keeps throwing the mentioned exception.
I followed the examples that ship with the trial to the letter and it should web working, yet it does not.
View (index.cshtml):
#(Html.Kendo()
.Upload()
.Name("files")
.Async(async => async
.Save("Save", "Home")))
-- Error is being thrown on the above statement
#(Html.Kendo()
.Upload()
.Name("files")
.Async(async => async
.AutoUpload(true)))
-- this line works
Controller (HomeController):
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Save(IEnumerable<HttpPostedFile> files)
{
// I just want the break-point to be hit
// does not due to IndexOutOfRange exception being thrown
return Content("");
}
}
The only thing that seems wrong is the Razor syntax:
(#Html.Kendo()
should be
#(Html.Kendo()
I was able to run your code with this small change.
Updated the MVC templates for Visual Studio and it works. Thanks.