serge
serge

Reputation: 15239

ASP.NET Core strategy for returning partial views via AJAX requests

I have an ASP.NET Core 5 web application and would like to know what strategy to use for partial vs. normal views.

So, for example, when a user navigates in the web browser to /bars, I would like to load the page with the Bar list (along with the menu, headers, footer, and other shared layout elements).

However, if the user clicks on the Bars menu, I would like to reload only the main container (without the menu, headers, footer, etc.).

In the Controller, should I create an Action for each page with a PartialAction, like this:

public async Task<IActionResult> Index()
{
    return View(await _repository.ListAsync<T>());
}
    
public async Task<IActionResult> IndexPartial()
{
    return PartialView(await _repository.ListAsync<T>());
}

And then call the AJAX on the menu with /Bar/IndexPartial, leaving the main action for the normal view? Related, should I create separate views for each action?

enter image description here

Upvotes: 1

Views: 4368

Answers (3)

Jeremy Caney
Jeremy Caney

Reputation: 7624

This is an interesting question! Your general approach of exposing an action for each page and returning a PartialViewResult for the inner contents of that page makes good sense to me.

If the only difference between the full view and the partial view is the common elements, however, I'd be looking into ways of centralizing the entry point views—if not the entry point actions themselves—as otherwise they're going to get really repetitive.

Below, I’ll scaffold the basic strategy I’d consider for handling the main entry point.

Note: Given the nature of the question, I’m going to assume you’re comfortable implementing the AJAX portion, and instead focus exclusively on the server-side architecture, which I believe to be at the heart of your question. Obviously, the existing answers address the client-side implementation, if that’s important.

View Models

To begin, you might establish a general view model that handles the overall page content. This might include properties for e.g. your navigation items. But it would also contain the name of the (partial) view as well as the view model for that view, as you’ll likely want these in order to pre-render the inner content:

public class PageViewModel 
{
    …
    public string View { get; set; }
    public object ViewModel { get; set; }
}

You'd then have individual view models for each page, such as:

public class BarListViewModel 
{
    public Collection<Bar> Bars { get; } = new();
}

Controller

Now, in your controller, you’ll introduce a method that establishes a BarListViewModel, so that you don’t need to repeat your logic between both your entry point as well as the actions which return a partial view:

[NonAction]
public BarListViewModel GetBarListViewModel() 
{
    var viewModel             = new BarListViewModel() 
    {
        Bars                  = …
    };
    return viewModel;
}

With that, you can now have an action that delivers your Bars page, similar to what you proposed:

public IActionResult Bars() => PartialView(GetBarListViewModel());

But, instead of having a different Index action for each partial, you could instead have something like the following

public IActionResult Index(Page page = "Home") 
{
    var viewModel = new PageViewModel 
    {
        View = page.ToString(),
        ViewModel = page switch 
        {
            "Bars" => GetBarListViewModel(),
            _ => GetDefaultViewModel()
        }
    };
    return View("Index", viewModel);
}

This provides a single “dispatch” for wiring up any entry point, without needing to create a new action or view for each one.

View

Finally, in your Index.cshtml, you'd have something like the following to dynamically inject the correct partial view based on the view model:

@model PageViewModel

…
<main>
  @await Html.PartialAsync(Model.View, Model.ViewModel)
</main>
…

That would load an e.g. Bars.cshtml which would contain the logic for looping through the BarListViewModel.Bars property.

Note: This assumes you want to prerender your partial view on the server side. Obviously, if you want the initial load to occur via AJAX, you wouldn’t need to pass the nested BarListViewModel, or call PartialAsync().

Routing

Presumably, the above would be paired with a route configuration that would allow the page to be passed in as a route parameter—possibly even baking in a default Controller and Action for your entry point:

app.UseEndpoints(endpoints => 
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{page?}", 
        new { controller = "Home", action = "Index" }
    );
});

Note: Obviously, you’ll need to be careful not to lock out your AJAX calls or other entry points with an overly greedy route. The above is just intended as a basic demonstration of how to capture the page route parameter.

Conclusion

This is similar to the approach proposed by @User1997, but it allows their proposed Index.cshtml and implied Index() action to be reused for all of your entry points. And, of course, if you had multiple controllers that followed this pattern, this could even be generalized as a base controller.

Upvotes: 1

Lirzae
Lirzae

Reputation: 66

You should create two views and one controller for each view. One parital view: _IndexPartial.cshtml and a normal view: Index.cshtml.

Partial view renders a portion of view content so you should include to the normal view "Index.cshtml" when the page is rendered for the first time.In this way you remove code rewriting. Everytime you want to change the content make a request to the ParitalView Controller and add the response(partial view) to the html element that displays the partial view.

@model List<T>
@{
    ViewData["Title"] = "Test";
}

<label>Partial Content</label>
<div id="partialContent">
    @await Html.PartialAsync("_IndexPartial.cshtml", Model)
</div>

<button id="UpdateContent">Update</button>

@section scripts{
    <script>
        $("#UpdateContent").click(function () {
            $.ajax({
                method: 'get',
                url: 'Home/IndexPartial',
                success: function (response) {
                    $('#partialContent').html(response);
                }
            })
        })
    </script>
}

Upvotes: 0

mj1313
mj1313

Reputation: 8479

and call the ajax on the menu with "/bar/IndexPartial", leaving the main action for the normal view? (should I, by the way, create separate views for each action?)

Yes, Index action for the normal Index view. Create a new partial view called "_IndexPartial.cshtml" for content that may change in the main view. Here you can just put the main container to your partial view. When click the button, use ajax to request IndexPartial to get the returned partial view html content, then replace it in the main view.

A simple example for understanding:

Index.cshtml:

@model List<Book>
@{
    ViewData["Title"] = "Home Page";
}

<label>BookList:</label>
<div id="bookpartial">
    @foreach(var book in Model)
    {
        <div>
            @book.Name
        </div>
    }
</div>

<button id="update">Update</button>

@section scripts{
    <script>
        $("#update").click(function () {
            $.ajax({
                method: 'get',
                url: 'Home/IndexPartial',
                success: function (result) {
                    $('#bookpartial').empty();
                    $('#bookpartial').html(result);
                }
            })
        })
    </script>
}

_IndexPartial.cshtml:

@model List<Book>

@foreach (var book in Model)
{
    <div>
        @book.Name
    </div>
}

Controller:

public IActionResult Index()
{
    var books = new List<Book>
    {
        new Book{ BookId = 1, Name = "BookA"},
        new Book{ BookId = 2, Name = "BookB"},
        new Book{ BookId = 3, Name = "BookC"},
    };
    return View(books);
}

public IActionResult IndexPartial()
{
    var newbooks = new List<Book>
    {
        new Book{ BookId = 4, Name = "BookD"},
        new Book{ BookId = 5, Name = "BookE"},
        new Book{ BookId = 6, Name = "BookF"},
    };
    return PartialView("_IndexPartial", newbooks);
}

Result:

enter image description here

Upvotes: 1

Related Questions