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

8
  • 1
    Can you please also add definition of class "ResultsRowModel" Commented Oct 7, 2016 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: stackoverflow.com/questions/1355464/… Commented Oct 7, 2016 at 14:55
  • @KD ResultsRowModel is pretty much just a class with properties to store columns like public string NameColumn {get;set;}, nothing else. Commented Oct 7, 2016 at 14:58
  • 1
    Make you property public IEnumerable<SelectListItem> psUserType { get; set; }. Commented Oct 7, 2016 at 21:29
  • 1
    And you cannot use @Html.DropDownListFor(x => x.psUserType, Model.psUserType). psUserType is a complex type ('IEnumerable<SelectListItem>` and you can only bind a <select> to a value type or string - you need an additional property to bind to - e.g. public string SelectedUserType { get; set; }. And suggest you consider posting the values using ajax and returning a partial view of the results so you do not need a full refresh. And remove new { multiple = "multiple" } - the ListBoxFor() method already adds it Commented Oct 7, 2016 at 21:35

3 Answers 3

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 *@
Sign up to request clarification or add additional context in comments.

4 Comments

Good answer, @Peter! I'll certainly use the .ToSelectList() extension method more often, now :)
Suggesting OP use ViewBag rather than an view model property is terrible advice.
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.
@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?
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.

1 Comment

SUBMIT THE FORM , what if it is a ajax post does the view-model still send the selected value?
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()
{
    //...
}

4 Comments

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<SelectListItem> Columns { get; set; } and in the view its simply @Html.ListBoxFor(x => x.psSelectedColumns, Model.Columns) (no casting required)
@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<SelectListItem>'." Does it mean I have to change the way I declare preselected values?
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<SelectListItem>'
@StephenMuecke Right, I missed repopupating SelectList in Post. Updated my answer, thank you so much for your help.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.