1

I have an application in ASP.NET where I want to pass this list of objects to my Controller. Right now what the application is doing is populating a list via sql query and then loads said list to the view. Up next, I divvy up the list into 4 color categories based on the data I get: red, blue, green and yellow (respectively). I display the count for each of them on 4 <divs>. Each div is its own ActionLink and right now sends a string to a Controller containing the color name.

The Controller then takes the string and compares it against a query where it returns the list objects that belong to that color category; then I do whatever I want to that list.

But my issue is that I am forced to do this data pull over and over and it takes too long as is. Is there a way for me to just load up the list once then pass it to my controller so I am not stuck waiting for my query to finish loading?

Here is my Model:

using System.Collections.Generic;

namespace Foo.Models
{
  public class FooViewModel
  {
    public List<Foo> FooCollection = new List<Foo>();
    /*Contains two properties
      string CarName {get; set;}
      string Color   {get; set;}
      List<Features> Features = new List<Features>();
    */
  }
}

My View

    @model Foo.Models.FooViewModel
    @{
var RedCars = Model.FooCollection.Where(c => c.Color == "Red").ToList();
    ... //{yellow, blue, green}
}
    <div id="FooCollection">
      <section class="no-padding-top no-padding-bottom">
        <div class="container-fluid">
          <div class="public-user-block block">
            <div class="row d-flex align-items-center">

              <!--Red Cars-->
                @using (Ajax.BeginForm("../Bar/Index/Red", null,
new AjaxOptions
{
HttpMethod = "post",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "CarsList"
}, new { id = "RedCarsForm" }))
  {
        <input type="hidden" name="Cars" value="@RedCars" />
          <div id="status-container" class="col-lg-3 d-flex align-items-center">
            <button type="submit">@RedAlerts.Count</button>
            <strong>Red Cars</strong>
          </div>

          }
          <!-- same structure for yellow, green, blue --!>
      </section>
    </div>

My Controller:

public ActionResult Index()
{

   foreach (var car in db.database.Db_GetCars())
   {
      model.FooCollection.Add(new Foo()
      {
         CarName = car.CarName,
         Color= car.Color
      });
    }
    return View(model);
}  

Destination Controller:

    namespace Foo.Controllers
    {
      public class BarController: Controller
      {
        BarViewModel model = new BarViewModel();

[HttpPost, Route("/Bar/Index/{color}")]
        public ActionResult Index(List<Foo> Cars)
        {
          //logic goes here

          return View(model);
        }
      }
    }

My desired output is to send the complete List<Foo> object to my Destination Controller. But right now I am getting a value count of zero (0) when it gets to the controller. Can anyone see what I am doing wrong? Many thanks in advance.

mmangual83
  • 151
  • 1
  • 2
  • 11
  • you're not doing anything with the parameter you are passing. Can you explain better? – Stormhashe Feb 16 '18 at 19:14
  • @Stormhashe of course, right now all im doing is passing a string. this sitring I then pass to another controller that does the EXACT same thing my first controller does. With the exception that it condenses the list based on the color I pass. See my code above under **Destination Controller** What I'm looking for is how do I structure the View I have now to send a List object. – mmangual83 Feb 16 '18 at 19:20
  • 1
    Caching is the keyword here – Sami Kuhmonen Feb 16 '18 at 19:20
  • @SamiKuhmonen I'm fairly new to ASP.NET, care to elaborate? – mmangual83 Feb 16 '18 at 19:26
  • can you set the method signature to `public ActionResult Index(Foo cars)` and in the action link instead of `new {id = "color"}` do `new { Foo = Model.FooCollection(x => x.color == "green"}` – crunchy Feb 16 '18 at 19:35
  • Seems to me your first query materializes and transmits far more fields that it would ever use. The first query should be just a group by with count by color. Then next action then makes a query with more fields. – Jasen Feb 16 '18 at 19:45
  • @crunchy okay good news and bad news. good news: when I debug I am getting the FooCollection as an object now. The bad news: Its coming up as empty. Not null, just empty. Have an idea why? – mmangual83 Feb 16 '18 at 19:45
  • if the slow db lookup is your main concern, either switch to dapper instead of EF (dapper is MUCH faster) or implement caching as @SamiKuhmonen mentioned above. Here is a link about caching: https://learn.microsoft.com/en-us/aspnet/core/performance/caching/memory – nurdyguy Feb 16 '18 at 19:53
  • And remove those foreach loops (use `.Select(c => ...)`). Or at the very least take the query out of the loop. `var query = GetCars(); foreach(var car in query) {...}` – Jasen Feb 16 '18 at 19:57
  • @nurdyguy well, one problem at a time. I will get to improving my db lookup time. But for now I'm more concerned with my data not making it to the next controller/view. I have tried some options for pre-caching dataa nd it does it well but when I try passing the data as a parameter in the action link it just returns an empty list at that point. – mmangual83 Feb 16 '18 at 20:20
  • @mmangual83 A link is a get request. You generally don't sent lists in get requests... You could turn them in to mini forms and post the lists that way. – nurdyguy Feb 16 '18 at 20:39
  • @nurdyguy interesting. Can you show me an example? This is because I'm still learning – mmangual83 Feb 16 '18 at 20:41
  • A quick clarification first: Are you trying to pass the list itself to the second controller or just the color? – nurdyguy Feb 16 '18 at 20:42
  • @nurdyguy I am trying to pass the condensed list to the second controller, i.e FooCollection.Where(x=>x.Color == "Red").ToList() – mmangual83 Feb 16 '18 at 20:45
  • `@html.HiddenFor(x => x.FooCollection)` – crunchy Feb 16 '18 at 20:48
  • @crunchy when I added, new { id = Html.HiddenFor(m=>m.FooCollection) } it gives me an URL error: A potentially dangerous Request.QueryString value was detected from the client – mmangual83 Feb 16 '18 at 20:54

1 Answers1

8

So your main problem is that you are trying to use an action (which is a link, an anchor tag) to pass a list to the controller. But an anchor tag is a "Get" request and you normally don't pass lists via Gets (yes, it is possible, but generally not advised). It would be better for you to use a form post for this. Here is a basic outline:

<!-- red cars -->
<form action="/bar/index/red" method="post">

    @foreach(var car in Model.Where(c => c.Color == "red"))
    {
        <input type="hidden" name="carnames" value="@car.CarName" />
    }
    <button class="btn btn-primary" type="submit">Red Cars</button>

</form>

<!-- green cars -->
<form action="/bar/index/green" method="post">

    @foreach(var car in Model.Where(c => c.Color == "green"))
    {
        <input type="hidden" name="carnames" value="@car.CarName" />
    }
    <button class="btn btn-primary" type="submit">Green Cars</button>

</form>

<!-- blue cars -->
<form action="/bar/index/blue" method="post">

    @foreach(var car in Model.Where(c => c.Color == "blue"))
    {
        <input type="hidden" name="carnames" value="@car.CarName" />
    }
    <button class="btn btn-primary" type="submit">Blue Cars</button>

</form>

<!-- yeller cars -->
<form action="/bar/index/yellow" method="post">

    @foreach(var car in Model.Where(c => c.Color == "yellow"))
    {
        <input type="hidden" name="carnames" value="@car.CarName" />
    }
    <button class="btn btn-primary" type="submit">Yellow Cars</button>

</form>

This creates a form for each color so when you click the submit button, only the cars from that one form are sent in the post. Note that the input name is always the same. That is how you get them wrapped together in a list.

In your controller, use something like this:

[HttpPost, Route("bar/index/{color}")]
public IActionResult Index(string color, List<string> carNames)
{
    // do stuff...

    return View();
}

The color variable will get picked up from the url and the carNames will be pulled in from the post.

EDIT: In the comments the added question was essentially "what if I want the car name and the color on the object, so a List<Foo>, to be posted?

Posting a list of complex objects is a bit messier but here is what you need in the view:

<!-- red cars -->
<form action="/stuff/cars/red" method="post">
    @{
        var cars = Model.Where(c => c.Color == "red").ToList();
        for (var i = 0; i < cars.Count; i++)
        {
            <text>
            <input type="hidden" name="cars.Index" value="@i" />
            <input type="hidden" name="cars[@i].CarName" value="@cars[i].CarName" />
            <input type="hidden" name="cars[@i].Color" value="@cars[i].Color" />
            </text>
        }
    }
    <button class="btn btn-primary" type="submit">Red Cars</button>

</form>

Switching to a regular for loop gives us an index variable which we use to tell the form which pair of values belongs together. Notice also that I actually created a temp var cars = ... above it so that I can loop through the smaller list. Now just change the other colors to match this code and change your controller to accept string color, List<Foo> cars and you are all set!

Edit 2: If you want to do this in ajax instead then create an object in the javascript:

var cars = [
    { 'CarName': 'name', 'Color': 'color' },
    { 'CarName': 'name', 'Color': 'color' },
    { 'CarName': 'name', 'Color': 'color' },
    { 'CarName': 'name', 'Color': 'color' },
    { 'CarName': 'name', 'Color': 'color' }
];

You can populate the actual name and color various ways using jQuery. Then use ajax post:

$(document).ready(function()
{
    $('#ajaxPost').click(function ()
    {
        $.ajax(
        {
            type: 'POST',
            url: '/stuff/cars',
            contentType: "application/json",
            data: JSON.stringify(cars),
            success: function (data)
            {
                // do stuff
            },
            error: function (e)
            {
                alert(e);

            }

        });
    });
});

Note: This will be a different endpoint in the controller from the previous one. Depending on which version of .net you are using things will act slightly differently.

New controller action:

[HttpPost, Route("stuff/cars/")]
public IActionResult Cars2([FromBody] List<FooViewModel> cars)
{
    // do stuff...

    return View();
}

Notice the [FromBody] tag, that is necessary in .net Core, as is the JSON.stringify in the ajax call. If you aren't sure which version you are on, alternate adding/removing each of those. Core can be a little finicky...

nurdyguy
  • 2,876
  • 3
  • 25
  • 32
  • I had to tweak the html a little because my tags were not buttons initially but it worked! Thank you. – mmangual83 Feb 16 '18 at 21:57
  • Hey I am having a problem. I am trying to pass the List object and not just the list of car names. I tried changing the form where the value takes in Model.FooCollection.where(x=>x.Color == "Red").ToList() but when I check my destination controller, my List parameter comes up as null. Any thoughts? – mmangual83 Feb 19 '18 at 21:12
  • so I fixed the null issue but now I am getting a count of zero for my list. I checked the action url and it seems fine. Any thoughts? – mmangual83 Feb 19 '18 at 21:48
  • this is giving me null onto my destination controller – mmangual83 Feb 19 '18 at 22:07
  • What is the signature for your destination controller action? – nurdyguy Feb 19 '18 at 22:15
  • /Bar/Index/Red and on my destination controller I have it as: [HttpPost, Route("/Bar/Index/{color}")] ActionResult Index(List FooCollection) – mmangual83 Feb 19 '18 at 22:17
  • Sorry, didn't read it correctly. Make sure the variable name matches the name in the cshtml. In my example I used `cars` in the form and `cars` in the function declaration. Because of how the model binding is for complex objects things get trickier and those names must match. – nurdyguy Feb 19 '18 at 22:24
  • lol, dont feel bad I overlooked it too. it works now thank you. But I can see this getting convoluted when you are searching through more nested collections. What would be the better approach in that situation? – mmangual83 Feb 19 '18 at 22:41
  • You are right, this could get way out of hand in a hurry. I would either do an ajax post and marshal the data in the javascript or just send a list of id values. If you keep it a list of simple things like the ids then this works great. – nurdyguy Feb 19 '18 at 22:46
  • how would you send data like that as an ajax post? lets say I have a data structure that contains a list inside? sorry for peppering you with so many questions about this topic. Even a link to this hypothetical yet real issue would be helpful. – mmangual83 Feb 19 '18 at 22:49