Search code examples
c#jqueryajaxasp.net-core-mvc.net-7.0

Value lost while RedirectToAction


I have an ASP.NET Core 7 MVC app which is showing unexpected behavior while using return view() and return RedirectToAction with combination of Ajax. Let me show the relevant code first, then I will share replication steps of the problem.

This is my view Index.cshtml:

@model NoSql_MVC_Core.Models.Product.ProductInfo
@{
    ViewData["Title"] = "Home Page";
}
<div class="container container-fluid">
<p>Total : @Model.Total</p>
<p>Skip : @Model.Skip</p>
<p>Limit : @Model.Limit</p>
<table id="products" class="table table-bordered table-striped">
    <thead>
        <tr class="table-dark">
            <th>Title</th>
            <th>Description</th>
            <th>Price</th>
            <th>discountPercentage</th>
            <th>Rating</th>
            <th>Stock</th>
            <th>Brand</th>
            <th>Category</th>
            <th>Thumbnail</th>
        </tr>
    </thead>
    <tbody>
        @if(Model!= null && Model.Products != null && Model.Products.Count > 0)
        {
            foreach(var item in Model.Products)
            {
                <tr>
                    <td>@item.Title</td>
                    <td>@item.Description</td>
                    <td>@item.Price</td>
                    <td>@item.DiscountPercentage</td>
                    <td>@item.Rating</td>
                    <td>@item.Stock</td>
                    <td>@item.Brand</td>
                    <td>@item.Category</td>
                    <td><img src="@item.Thumbnail" style="width:120px;height:80px"></td>
                </tr>
            } 
        }
        else
        {
            <tr>
                <td colspan="9" class="text-center">No Records</td>
            </tr>
        }
    </tbody>
</table>
</div>

@section Scripts
{
    <script type="text/javascript" src="~/js/scripts/products.js"></script>
}

And this is my controller:

public class HomeController : Controller
{
        private readonly ILogger<HomeController> _logger;

        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
        }

        public IActionResult Index(ProductInfo productInfo)
        {
            return View(productInfo);
        }

        [HttpPost]
        public IActionResult LoadProducts([FromBody] ProductInfo productInfo)
        {
            return RedirectToAction("Index", "Home", productInfo);
        }
}

Here is my product.js code file:

$(document).ready(function () {
    GetProducts();
});

function GetProducts() {
    $.ajax({
        type: "GET",
        contentType: "application/json; charset=utf-8",
        url: "https://dummyjson.com/products",
        dataType: "json",
        async: false,
        success: function (Result) {
            var productsInfo = {};
            if (Result) {
                productsInfo.total = Result.total;
                productsInfo.skip = Result.skip;
                productsInfo.limit = Result.limit;
                productsInfo.products = Result.products;
                LoadProducts(productsInfo);
            }
            else {

            }
        },
        error: function (e) {
            if (e.status != 200) {
                alert("error " + e.status + ' : ' + e.statusText);
            }
        }
    });
}

function LoadProducts(param) {
    $.ajax({
        type: "POST",
        contentType: "application/json; charset=utf-8",
        url: "/Home/LoadProducts",
        data: JSON.stringify(param),
        dataType: "json",
        contentType: 'application/json; charset=utf-8',
        async: false,
        success: function (Result) {
            if (Result) {
            }
            else {
            }
        },
        error: function (e) {
            if (e.status == 200) {
            }
            if (e.status != 200) {
                alert("error " + e.status + ' : ' + e.statusText);
            }
        },
        complete: function (e) {
            
        }
    });
}

These are my model classes:

public class ProductInfo
{
        public List<Product> Products { get; set; }
        public int Total { get; set; }
        public int Skip { get; set; }
        public int Limit { get; set; }
}

public class Product
{
        public int Id { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public Decimal Price { get; set; }
        public float DiscountPercentage { get; set; }
        public float Rating { get; set; }
        public int Stock { get; set; }
        public string Brand { get; set; }
        public string Category { get; set; }
        public string Thumbnail { get; set; }
        public string[] Images { get; set; }
}

In my application, on page load, first js function GetProducts() is invoked within document.ready (refer to the product.js file). It's actually hitting a dummy API to get some data.

Once it get the data, it calls LoadProducts() method from the same js file.

Within LoadProducts, I am making an Ajax call to the action method LoadProducts() on my controller, and passing the data as post request body.

Now within the ASP.NET Core MVC action method, if I use return view(), then it does not refresh the page:

[HttpPost]
public IActionResult LoadProducts([FromBody] ProductInfo productInfo)
{
    return View("Index", productInfo);
}

Is this approach is wrong, will it not reload the page with data?

If I use return RedirectToAction, then it does invoke the index.cshtml file, debugger moves to razor syntax but this time it lost data partially in view.

[HttpPost]
public IActionResult LoadProducts([FromBody] ProductInfo productInfo)
{
    return RedirectToAction("Index", "Home", productInfo);
}

Here we can see jQuery Ajax has posted data to action method and now we will do RedirectToAction and

enter image description here

Once the control moved within Index, value for Limit, SKip And Total is there only for products it became as count zero. Product is a List of Type Product and its content lost while doing redirect to action.

enter image description here

Although the value of Limit, SKip and Total was there in Index action method but at view it get displayed as zero always

So the question is: if I use Ajax and transport data to action method than will it ignore to re-render the view despite calling the return view() statement

And if jQuery post to another method like we did here, Ajax posted to loadproducts and loadproducts invoke index with redirect to action statement, how does value get lost only for one property and view still not getting updated for other properties?

Is this approach incorrect? If yes, then what improvements can I make?


Solution

  • You can't render a view from AJAX directly. In your case, I think you can use partial view to achieve, Please refer to this demo.

    I create two partial views:

    _PartialForm.csthml

    @model ProductInfo
    
    @if (Model != null && Model.Products != null && Model.Products.Count > 0)
    {
        foreach (var item in Model.Products)
        {
            <tr>
                <td>@item.Title</td>
                <td>@item.Description</td>
                <td>@item.Price</td>
                <td>@item.DiscountPercentage</td>
                <td>@item.Rating</td>
                <td>@item.Stock</td>
                <td>@item.Brand</td>
                <td>@item.Category</td>
                <td><img src="@item.Thumbnail" style="width:120px;height:80px"></td>
            </tr>
        }
    }
    else
    {
        <tr>
            <td colspan="9" class="text-center">No Records</td>
        </tr>
    }
    

    _PartialHead.cshtml

    @model ProductInfo
    
    
    @if (Model != null)
    {
        <p>Total : @Model.Total</p>
        <p>Skip : @Model.Skip</p>
        <p>Limit : @Model.Limit</p>
    }
    else
    {
        <p>Total : null</p>
        <p>Skip : null</p>
        <p>Limit : null</p>
    }
    

    Then Change your Index view like:

    <div class="container container-fluid">
    
        <div id="partialViewHeader">       
        </div>
        
        <table id="products" class="table table-bordered table-striped">
            <thead>
                <tr class="table-dark">
                    <th>Title</th>
                    <th>Description</th>
                    <th>Price</th>
                    <th>discountPercentage</th>
                    <th>Rating</th>
                    <th>Stock</th>
                    <th>Brand</th>
                    <th>Category</th>
                    <th>Thumbnail</th>
                </tr>
            </thead>
            <tbody id="partialViewForm">           
                          
            </tbody>
        </table>
    </div>
    

    Controller/Action

            [HttpPost]
            public IActionResult LoadProductsHead([FromBody] ProductInfo productInfo)
            {
                return PartialView("_PartialHead", productInfo);
            }
    
            [HttpPost]
            public IActionResult LoadProductsFrom([FromBody] ProductInfo productInfo)
            {
                return PartialView("_PartialForm", productInfo);
            }
    

    Finally, In JavaScript code, I write will send two ajax to above actions and return partial view to the Index View.

    $(document).ready(function () {
                GetProducts();
            });
    
            function GetProducts() {
                $.ajax({
                    type: "GET",
                    contentType: "application/json; charset=utf-8",
                    url: "https://dummyjson.com/products",
                    dataType: "json",
                    async: false,
                    success: function (Result) {
                        var productsInfo = {};
                        if (Result) {
                            productsInfo.total = Result.total;
                            productsInfo.skip = Result.skip;
                            productsInfo.limit = Result.limit;
                            productsInfo.products = Result.products;
                            LoadProductsHead(productsInfo);
                            LoadProductsForm(productsInfo);
                        }
                        else {
    
                        }
                    },
                    error: function (e) {
                        if (e.status != 200) {
                            alert("error " + e.status + ' : ' + e.statusText);
                        }
                    }
                });
            }
    
            function LoadProductsHead(param) {
                $.ajax({
                    type: "POST",
                    contentType: "application/json; charset=utf-8",
                    url: "/Test/LoadProductsHead",
                    data: JSON.stringify(param),
                    dataType: "html",
                    contentType: 'application/json; charset=utf-8',
                    async: false,
                    success: function (Result) {
                        if (Result) {
                            $('#partialViewHeader').html(Result)
                        }
                        else {
                        }
                    },
                    error: function (e) {
                        if (e.status == 200) {
                        }
                        if (e.status != 200) {
                            alert("error " + e.status + ' : ' + e.statusText);
                        }
                    },
                    complete: function (e) {
    
                    }
                });
            }
    
            function LoadProductsForm(param) {
                $.ajax({
                    type: "POST",
                    contentType: "application/json; charset=utf-8",
                    url: "/Test/LoadProductsFrom",
                    data: JSON.stringify(param),
                    dataType: "html",
                    contentType: 'application/json; charset=utf-8',
                    async: false,
                    success: function (Result) {
                        if (Result) {
                            $('#partialViewForm').html(Result)
                        }
                        else {
                        }
                    },
                    error: function (e) {
                        if (e.status == 200) {
                        }
                        if (e.status != 200) {
                            alert("error " + e.status + ' : ' + e.statusText);
                        }
                    },
                    complete: function (e) {
    
                    }
                });
            }
    

    gif demo

    enter image description here

    Hope this answer can give you some help.