简体   繁体   中英

Submit model with SelectList in ASP.NET MVC

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

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 *@

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.

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()
{
    //...
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM