Quick Tip: How to Load ViewComponents in Razor Pages

What do you mean 400 error? In today's quick tip, we examine how to load ViewComponents through Razor Pages using JavaScript

Written by Jonathan "JD" Danylko • Last Updated: • Develop •

Picture of clock parts

ViewComponents first appeared in the latest version of ASP.NET Core back in 2016 providing developers with a component-based mentality.

Before ViewComponents, HTMHelpers were available to create "partial" HTML sections. If we needed to render a portion of HTML, we would call a Controller and return back a PartialViewResult with usually a name and an optional model.

public IActionResult OnGetPartial() =>
    new PartialViewResult
    {
        ViewName = "_MyPartialView",
        ViewData = myModel,
    };

When ViewComponents arrived, we could attach business logic to their views, making them almost like mini-controllers. This gave us more control on what to display instead of mixing our C# with HTML. Business logic is just one of the many benefits to using ViewComponents

However, with Razor Pages, there wasn't an obvious way to load ViewComponents through JavaScript where you could display pockets of data on a webpage.

I shouldn't say that.

This WAS actually documented under the ViewComponents section, but it was for accessing it through a Controller, not through a Razor Page.

After working on a recent project with Razor Pages, you can achieve the same result, but it's a different approach and what I consider to be a "code smell."

Access ViewComponents through Razor Pages

To access your ViewComponent, you first need an "entry" page.

Once you create your Razor Page, you will see the standard OnGet() and returns the view attached to it by default.

public void OnGet()
{
}

However, if you want to return a ViewComponent instead, change your ActionResult to point to a ViewComponent instead.

public IActionResult OnGet()
{
    var myModel = new MyModel();
    return ViewComponent("MyWidget", myModel);
}

Your MyWidgetViewComponent should be located in your search path. These search paths, by default, are:

  • /Views/{Controller Name}/Components/{View Component Name}/{View Name}
  • /Views/Shared/Components/{View Component Name}/{View Name}
  • /Pages/Shared/Components/{View Component Name}/{View Name}

Of course, you can change these locations to fit the requirements of your project.

If it can't find the ViewComponent, it'll bomb.

What about the JavaScript?

Let's say you have an Refresh button on your web page and you want a section updated.

Logically, you want to group your calls together so they're not all over the place.

For example, if I want to refresh a section of a page, I want to keep a refresh method in that Razor Page. I don't want to make a refresh call to another Razor Page. You want to keep it together on the current page. All of your functionality, including JavaScript "Ajax" calls, should be contained in that Razor Page.

Here's the process I follow:

  1. Create a method in the Razor Page called OnGetRefresh(). Your OnGetRefresh should return a ViewComponent() like above (see the MyWidgetViewComponent).
  2. In the web page, create a fetch to call "/Index?handler=Refresh"
  3. When your JavaScript fetch returns the data back to the page, you'll replace the content in the page with the newly-returned HTML.

While this Refresh example is simplified, this gives you a starting point for taking it further.

Another example is POST-ing data back based on an action and returning back a ViewComponent with the updated HTML section. Your method name would change from OnGet to OnPost. This is a Razor Page convention over configuration concept.

Caveat to POST-ing data

Which reminds me, there are a couple of gotchas when you go down this road.

  • To avoid Cross-Site Scripting, you are required to add an @Html.AntiForgeryToken() in your HTML. When posting back, your JavaScript should grab the token value and pass it back through the headers for your POST to function properly. This is REQUIRED to avoid 400 errors. Thanks to MikesDotNetting for this post!
  • When performing a POST, the JavaScript URL to your Razor Page MUST BE THE SAME as when you requested the page. One trick I've seen (and used) is where the URL to my Razor Page is "?handler=MyActionToPerform". This uses the current page URL and adds it to the end to confirm it will post back to the correct Razor Page method.

These two issues have taken me close to a day to find the answer, so I'm hoping they provide a shortcut and help others out in the process. :-)

Conclusion

In this post, we examined a way to load ViewComponents through JavaScript and discussed some of the nuances when working with ViewComponents in ASP.NET 5.

Oh, you remember how I mentioned at the beginning of this post where it feels like a code smell?

I'm referring to the "?handler=<action>" when posting back to a Razor Page.

I understand it's required to execute various methods and verbs, but it still feels...different and doesn't sit well with me. However, at the moment, I can't come up with a better solution.

So we continue with the handler approach. :-)

Did I miss a "gotcha?" How do you call your ViewComponents from JavaScript? Post your comments below and let's discuss.

ASP.NET 8 Best Practices on Amazon

ASP.NET 8 Best Practices by Jonathan Danylko


Reviewed as a "comprehensive guide" and a "roadmap to excellence" with over 120 Best Practices for ASP.NET Core 8, Jonathan's first book by Packt Publishing explores proven techniques for every phase of the SDLC.

Learn industry-standard concepts to improve your coding, debugging, and deployment of ASP.NET Core websites.

Order now on Amazon.com button

Picture of Jonathan "JD" Danylko

Jonathan "JD" Danylko is an author, web architect, and entrepreneur who's been programming for over 30 years. He's developed websites for small, medium, and Fortune 500 companies since 1996.

He currently works at Insight Enterprises as an Architect.

When asked what he likes to do in his spare time, he replies, "I like to write and I like to code. I also like to write about code."

comments powered by Disqus