Search code examples
c#asp.netrazor

How can I avoid GetAwaiter().GetResult in an overridden page


I'm using VS2022 and Net7 web App.

I have this page, to handle common stuff so I don't have to put it in every page

public class ApplicationPageModel : PageModel
{        
    public IActionResult? OnGet()
    {
        //cookie handling
        //page set up
        //security checking
        //etc

        return null;
    }
}

Then all my other pages look like this

public class PermissionsModel : ApplicationPageModel
{
    public override async void OnPageHandlerExecuted(PageHandlerExecutedContext context)
    {
        if (context.HandlerMethod!.MethodInfo.Name == nameof(OnGet))
        {
            string page = HttpContext.Request.Query["id"].ToString();

            //page specific code goes here'
            //do something awaitable here
        }

        base.OnPageHandlerExecuted(context);

    }
}

I can't use OnGet in the actual page because I get this error

An unhandled exception occurred while processing the request. InvalidOperationException: Multiple handlers matched. The following handlers matched route data and had all constraints satisfied:

The problem is using OnPageHandlerExecuted I then have to use .GetAwaiter().GetResult() on my async method because the Razor page renders before my awaitable API call completes. So any model updates (null) aren't complete so I get null exceptions in the page. For exmaple if I use:-

@foreach(var stuff in Model.Stuff) //Model.Stuff is always null because that method completes after the page.

Without the override using a normal OnGet everything is fine, but then I have to put code in every page and I'm planning alot of pages in my app.

I have no issues with using .GetAwaiter().GetResult() but I'm being told its either not safe or not a good idea. How else could I acheive the same in a safe way.

Any ideas anyone?


Solution

  • I'd argue your actual problem isn't about await or GetAwaiter, but about a pretty unfortunate class-design. Your base-class seems to be in no way related to a PageModel, but happens to have this inheritance only to share some common functionality on all your pages. This is best done using composition instead of inheritance. So after all your ApplicationModel-class is just a utility-class. This is also indicated by your OnGet-method just returning null instead of a valid response. So instead of deriving that utility-class, just use it:

    class static ApplicationHelpers
    {
        public static async Task DoSomething() { await something }
    }
    public class MyPage : PageModel
    {
        // now you can easily implement OnGet here, no more naming-collisions
        public Task<IActionResult> OnGetAsync()
        {
            // do the commmon stuff
            await ApplicationHelpers.DoSomething();
    
            // do the page-specific stuff here
        }
    }
    

    As ApplicationHelpers.DoSomething (or whatever you name it) now makes some request as well, you are free to make it async and thus to avoid this annoying GetAwaiter-thing in the first place. Of course you should consider to inject the dependency to this helper-class in your Program.cs-file somehow, for brevity I omited that and made the class static.

    If you can't change the inheritance for whatever reason, you could at least just rename the OnGet-function in your base-class, as the method is not really a request-handler. Instead it is just some common logic that should happen when a request is done. So just call that function withkin every request-handler like so:

    class ApplicationPageModel : PageModel
    {
        public async Task DoSomething() { await something }
    }
    public class MyPage : PageModel
    {
        // now you can easily implement OnGet here, no more naming-collisions
        public Task<IActionResult> OnGetAsync()
        {
            // do the commmon stuff
            await base.DoSomething();
    
            // do the page-specific stuff here
        }
    }