How to refresh data displayed in ViewComponent's default View : ASP.NET MVC Core

220 views Asked by At

I have a razor view that is divided into two columns. The column on the left contains a list of bootstrap list-group-items ( job cards). On the right is a larger column to display the full details of the active job card. The right column is simply a viewcomponent which takes as parameter a job entity and render it details on the default view.

Here is my main view

@model IEnumerable<JobListing>
<div class=" d-flex justify-content-center col-12 row my-3 mx-0 p-0 row position-relative">

    <!--collection of Listing cards -->
    <div style=" " class=" d-flex justify-content-start col-sm-12 col-lg-5 col-xl-4  m-0 p-0 pe-4">
        <div id="jobList" class="list-group col-12 m-0 p-0  border-0 ">


            @foreach (var listing in Model)
            {
               

                <div id="listing-@count" class="list-group-item list-group-item-action " data-bs-toggle="list" href="#listing-@count">
                    <!-- Listing Card -->
                </div>

                count++;
            }
        </div>
    </div>


    <!--Job Details-->
    <div style="" class="s p-1">
        <div id="jobDetail" class=" h-auto border border-1 border-primary rounded rounded-3 align-items-start ">

            @await Component.InvokeAsync("JobDetail", new {joblisting = Model.ElementAt(0)})
        </div>
    </div>

</div>

Initially the first job in the list is loaded by the viewcomponent. Now, I have added click eventListener to each card to capture when it's selected and then pass a serialized version of the active joblisting object to this ajax method LoadJobDetail(...)

 <script>
     const divItems = document.querySelectorAll('.list-group-item');

     divItems.forEach(function (div) {
         div.addEventListener('click', function () {
             var job = this.getAttribute('data-job');

             LoadJobDetail(job);
         });
     });

     function LoadJobDetail(jobListing) {
         $.ajax({
             type: "GET",
             url: "customer/Home/LoadJobDetail",
             data: { job: JSON.stringify(jobListing) },
             success: function (response) {
                 // Refresh viewcomponent data
             },
             error: function (error) {
                 console.log("operation failed...", error);
             }

         });
     }

 </script>

The ajax method calls this action method which in turn invokes the view component and pass the job...

[HttpGet]
 public IActionResult LoadJobDetail(string job)
 {
     // Convert string to joblisting
     JobListing jobDetail = JsonConvert.DeserializeObject<JobListing>(job);

     if (jobDetail != null)
     {
         var result = ViewComponent("JobDetail", new { jobListing = jobDetail });
         return Json(new { response = result, success = true, message = "Job detail successfully loaded" });
     }
     else
     {
         return Json(new { response = "", success = false, message = "could not load the selected job listing" });
     }
 }

And here is my ViewComponent class

 public async Task<IViewComponentResult> InvokeAsync(JobListing jobListing)
 {

     // return the view
     return View(jobListing);
 }

The view for my ViewComponent expects as model a JobListing object. So far all the code till the step of invoking the ViewComponent works fine. I added a breakpoint on the Default.cshtml for the ViewComponent and the properties are being set. But the data in View never refreshes. Any help on how to display the details for selected job? Perhaps am using the wrong approach. I've thought of using partial view instead but doesn't sound reasonable.

3

There are 3 answers

0
emile keen On BEST ANSWER

Based on @Stilgar suggestion to use partial view, I also found a similar suggestion on Microsoft developers forum and decided to try that and it works. Whether it's the best approach I can't tell as am a beginner.

I created a partial view with same code and model as that in my Default.cshtml for the previous ViewComponent. Then modified the action method to return the partial view with it's model like so

[HttpGet]
  public IActionResult LoadJobDetail(string job)
  {
      // Convert string to joblisting
      JobListing jobDetail = JsonConvert.DeserializeObject<JobListing>(job);

      return PartialView("_JobDetailsPartial", jobDetail);
  }

And then modified the ajax call to load the result inside the jobDetailContainer div

function LoadJobDetail(jobListing) {
    
    $.ajax({
        type: "GET",
        url: "customer/Home/LoadJobDetail",
        data: { job: JSON.stringify(jobListing) },
        success: function (response) {
                
               $('#jobDetail').html(response);
            console.log(response);
        },
        error: function (error) {
            console.log("operation failed...", error);
        }

   });

Everything else remain the same. Don't know if it's the right approach but it looks cleaner than trying to convert a viewComponentResult into html code.

4
Yasin ARLI On

To convert a ViewComponentResult into an HTML string and include it in a JSON response, you can render the ViewComponent to a string and then send that string as part of your JSON response. Here's how you can do it:

Render the ViewComponent to a string in your controller action using IViewRenderService. You'll need to inject this service into your controller. If you haven't already, you should configure it in your Startup.cs:

// Startup.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using YourNamespace.Services;

public void ConfigureServices(IServiceCollection services)
{
    // ...

    services.AddTransient<IViewRenderService, ViewRenderService>();
}

Create a service that implements IViewRenderService to render the ViewComponent to a string. Here's a simple example:

// ViewRenderService.cs

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;

public interface IViewRenderService
{
    Task<string> RenderToStringAsync(string viewName, object model);
}

public class ViewRenderService : IViewRenderService
{
    private readonly IHttpContextAccessor _contextAccessor;
    private readonly IServiceProvider _serviceProvider;

    public ViewRenderService(
        IHttpContextAccessor contextAccessor,
        IServiceProvider serviceProvider)
    {
        _contextAccessor = contextAccessor;
        _serviceProvider = serviceProvider;
    }

    public async Task<string> RenderToStringAsync(string viewName, object model)
    {
        var httpContext = _contextAccessor.HttpContext ?? new DefaultHttpContext { RequestServices = _serviceProvider };
        var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());

        using (var sw = new StringWriter())
        {
            var viewResult = ViewEngines.Engines.FindPartialView(actionContext, viewName);
            var viewContext = new ViewContext(
                actionContext,
                viewResult.View,
                new ViewDataDictionary(model),
                new TempDataDictionary(),
                sw,
                new HtmlHelperOptions()
            );

            await viewResult.View.RenderAsync(viewContext);

            return sw.ToString();
        }
    }
}

In your controller action, render the ViewComponent to a string and include it in your JSON response:

[HttpGet]
public IActionResult LoadJobDetail(string job)
{
    // Convert string to joblisting
    JobListing jobDetail = JsonConvert.DeserializeObject<JobListing>(job);

    if (jobDetail != null)
    {
        // Render the ViewComponent to a string
        var viewComponentHtml = await _viewRenderService.RenderToStringAsync("YourViewComponentName", jobDetail);

        return Json(new { response = viewComponentHtml, success = true, message = "Job detail successfully loaded" });
    }
    else
    {
        return Json(new { response = "", success = false, message = "Could not load the selected job listing" });
    }
}

Replace "YourViewComponentName" with the actual name of your ViewComponent.

Now, when you make an AJAX request to your LoadJobDetail action, it will return the rendered HTML of your ViewComponent as part of the JSON response. You can then use JavaScript to update the content of the corresponding element with this HTML.

2
Stilgar On

I think the problem is that the result you are assigning to the response is nothing like a string. It's some form of ActionResult. You probably want to return it directly and use the string as HTML rather than JSON, I don't see the use for the JSON here at all. In addition it seems that you can use the simpler partial views here as you don't do anything in the ViewComponent Invoke method. Finally this whole thing is super weird since you are sending the data from the client to the server just to produce the HTML but you already have the data on the client and you can produce the HTML there by replacing a bunch of HTML elements.