0

What I have is a form with multiple inputs that I want to use to query database for some results. Form has some default values and it all works, however I have problem submitting it to itself.

The returned error is "No paramaterless constructor defined for this object" and it is caused by SelectList object.

I have tried this solution and made psUserType private with getter and setter and intialized it as empty list, but then my dropDown menu had no values on start. Not sure why GetUserTypes hadn't filled them.

What am I doing wrong here? How does one have both preselected values and also send the same model with user-selected values, while also displaying results on the same page?

Does it make sense to use the same model for all 3 actions: 1. display form and inputs with default values 2. post selected values during submit 3. return results and selected values? I've read this solution also but not sure how to use 2 or 3 separate models here.

Any help is appreciated. Thanks in advance.

Model

public class SearchDownloadsModel
{
    public SelectList psUserType { get; private set; } //causes problem on submit
    public string psText { get; set; }
    public MultiSelectList psColumns { get; private set; }
    public IEnumerable<ResultsRowModel> psResults { get; set; }

    public SearchDownloadsModel()
    {            
        this.psUserType = GetUserTypes();          
        this.psColumns = GetColumns();
        this.psResults = new List<ResultsRowModel>(); //empty by default
    }

    public SelectList GetUserTypes()
    {
        List<SelectListItem> items = new List<SelectListItem>()
        {
            new SelectListItem { Value="user", Text="Single User" },
            new SelectListItem { Value="group", Text="User group" },
            ...               
        };
        return new SelectList(items, "Value", "Text");
    }

    public MultiSelectList GetColumns()
    {
        List<SelectListItem> items = new List<SelectListItem>()
        {
            new SelectListItem { Value = "user", Text="Username" },
            new SelectListItem { Value = "file", Text="Filename" },
            new SelectListItem { Value = "titl", Text="Title" },
            new SelectListItem { Value = "auth", Text="Author" },
            ...
        };
        return new MultiSelectList(items, "Value", "Text");
    }
}

public class ResultsRowModel
{
    public int ID { get; set; }
    public string EventTime { get; set; }
    public string FileName { get; set; }
    public string FilePath { get; set; }
    public string UserName { get; set; }
    ...
}

View

@model Proj.Models.SearchDownloadsModel

@using (Html.BeginForm("Downloads", "Home", FormMethod.Post))
{
   @Html.DropDownListFor(x => x.psUserType, Model.psUserType)
   @Html.TextBoxFor(x => x.psText)
   @Html.ListBoxFor(x => x.psColumnsSelected, Model.psColumns, new { multiple = "multiple" })

   <button type="submit" class="btn btn-primary">Search</button>
}

@if (Model.psResults != null && Model.psResults.Any())
{
    <table>
        <tr>
            <th>User</th>
            <th>File</th>
        </tr>
        @foreach (var row in Model.psResults)
        {
            <tr>
                <td>@row.UserName</td>
                <td>@row.FileName</td>
            </tr>
        }
    </table>
}

Controller

[HttpGet]
public ActionResult Downloads()
{
    SearchDownloadsModel model = new SearchDownloadsModel();
    model.psColumnsSelected = new List<string>() { "user", "file" }; //preselected values
    return View(model);
}

[HttpPost]
public ActionResult Downloads(SearchDownloadsModel model)
{       
    model.psResults = queryDatabase(model);
    return View(model);
}

private List<ResultsRowModel> queryDatabase(SearchDownloadsModel model)
{
    //...
}

EDIT: Added ResultsRowModel under SearchDownloadsModel

Community
  • 1
  • 1
yosh
  • 3,245
  • 7
  • 55
  • 84
  • 1
    Can you please also add definition of class "ResultsRowModel" – K D Oct 07 '16 at 14:44
  • I agree with @KD - One of your classes has a constructor that takes parameters (e.g. `public MyClass(string one, string two)`), but no constructor without (e.g. `public MyClass()`) and is causing this error. Take a look at your classes, and you should be able to figure this one out yourself :) info here: http://stackoverflow.com/questions/1355464/asp-net-mvc-no-parameterless-constructor-defined-for-this-object – Geoff James Oct 07 '16 at 14:55
  • @KD ResultsRowModel is pretty much just a class with properties to store columns like `public string NameColumn {get;set;}`, nothing else. – yosh Oct 07 '16 at 14:58
  • @GeoffJames Yes, the problem is SelectList's constructor, like in the first solution I've mentioned. But if I use that solution then there is a problem with populating values in model. Perhaps if I populated it from my Action Method and used Peter's suggestionto use ViewBag it could work. – yosh Oct 07 '16 at 15:07
  • @yosh - I think it's definitely worth giving Peter's answer a go. Nice and clear. Good luck! :) – Geoff James Oct 07 '16 at 15:09
  • 1
    Make you property `public IEnumerable psUserType { get; set; }`. –  Oct 07 '16 at 21:29
  • 1
    And you cannot use `@Html.DropDownListFor(x => x.psUserType, Model.psUserType)`. `psUserType` is a complex type ('IEnumerable` and you can only bind a ` –  Oct 07 '16 at 21:35
  • @StephenMuecke It works, thank you! Is it because IEnumerable does have parameterless constructor, in contrast to SelectList? – yosh Oct 10 '16 at 15:30

3 Answers3

2

In ASP.NET MVC you should only put variables containing the posted or selected values in the ViewModel class. Select List items are considered extra info and are typically passed from the Action Method into the View (.cshtml) using ViewBag items.

Many of the rendering extension methods are even written specifically for such an approach, leading to code such as this:

Controller

ViewBag.PersonID = persons.ToSelectList(); // generate SelectList here

View

@Html.DropDownListFor(model => model.PersonID)
@* The above will look for ViewBag.PersonID, based on the name of the model item *@
Peter B
  • 22,460
  • 5
  • 32
  • 69
  • Good answer, @Peter! I'll certainly use the `.ToSelectList()` extension method more often, now :) – Geoff James Oct 07 '16 at 15:08
  • Suggesting OP use `ViewBag` rather than an view model property is terrible advice. –  Oct 07 '16 at 21:26
  • The ViewBag contains the list, and the Model contains the selected item ID. I did not come up with that approach, Microsoft did, and many of their examples use it. – Peter B Oct 09 '16 at 08:35
  • @StephenMuecke I've just posted how I solved this problem, but I'm here to learn and I'm very open to suggestions and better solutions. Could you say why ViewBag is terrible? – yosh Oct 13 '16 at 10:33
0

The DropDownListFor generates a <select> element with the name of the property you bind it to. When you submit the form, that name will be included as one of the form fields and its value will be the option's value you select.

You're binding the DropDownList to a property of type SelectList (psUserType) and when your action is called, a new instance of SelectList must be created in order to bind the form field to it. First of all, the SelectList class does not have a parameterless constructor and, thus, your error. Secondly, even if a SelectList could be created as part of model binding, the <select> element is submitting a string value which wouldn't be convertible to SelectList anyways.

What you need to do is to add a string property to your SearchDownloadsModel, for example:

public string SelectedUserType { get; set; }

Then bind the dropdownlist to this property:

@Html.DropDownListFor(x => x.SelectedUserType, Model.psUserType)

When you submit the form, this new property will have the value you selected in the drop down.

Andrei Olariu
  • 556
  • 4
  • 8
  • SUBMIT THE FORM , what if it is a ajax post does the view-model still send the selected value? – sss111 Jan 14 '20 at 19:59
0

Peter's answer and Stephen's comments helped me solve the problem.
Pehaps someone will find it useful.

Any further suggestions always welcome.

Model

public class PobraniaSzukajModel
{
    public IEnumerable<SelectListItem> UserTypes { get; set; }
    public string psSelectedUserType { get; set; }
    public IEnumerable<SelectListItem> Columns { get; set; }
    public IEnumerable<string> psSelectedColumns { get; set; }
    public string psText { get; set; }
    public ResultsModel psResults { get; set; }
}

View

@Html.ListBoxFor(x => x.psSelectedUserType, Model.Columns)
@Html.TextBoxFor(x => x.psText)
@Html.ListBoxFor(x => x.psSelectedColumns, Model.Columns)

Controller

[HttpGet]
public ActionResult Downloads()
{    
    SearchDownloadsModelmodel = new SearchDownloadsModel();  
    model.UserTypes = GetUserTypes();
    model.Columns = GetColumns();     
    model.psColumnsSelected = new List<string>() { "user", "file" }; //preselected values
    return View(model);
}

[HttpPost]
public ActionResult Downloads(SearchDownloadsModel model)
{
    model.UserTypes = GetUserTypes();
    model.Columns = GetColumns();     
    model.psResults = GetResults(model);
    return View(model);
}
public SelectList GetUserTypes()
{
    //...
}
public MultiSelectList GetColumns()
{
    //...
}
public ResultsModel GetResults()
{
    //...
}
yosh
  • 3,245
  • 7
  • 55
  • 84
  • 1
    You already has a view model so make use of it rather that using un-typed `ViewBag` (typeof `dynamic`). The model should contain a property `public IEnumerable Columns { get; set; }` and in the view its simply `@Html.ListBoxFor(x => x.psSelectedColumns, Model.Columns)` (no casting required) –  Oct 13 '16 at 23:34
  • @StephenMuecke If I do this, it results in error: "The ViewData item that has the key 'psSelectedColumns' is of type 'System.String[]' but must be of type 'IEnumerable'." Does it mean I have to change the way I declare preselected values? – yosh Oct 14 '16 at 10:30
  • 1
    No, you just need to repopulate the SelectList(s) if you need to return the view (just as you did in the GET method, and you currently doing but with `ViewBag` instead of a view model) - Also refer [The ViewData item that has the key 'XXX' is of type 'System.Int32' but must be of type 'IEnumerable'](http://stackoverflow.com/questions/34366305/the-viewdata-item-that-has-the-key-xxx-is-of-type-system-int32-but-must-be-o) –  Oct 14 '16 at 10:33
  • @StephenMuecke Right, I missed repopupating SelectList in Post. Updated my answer, thank you so much for your help. – yosh Oct 14 '16 at 10:47