I'm trying to retrieve data from my Database to an HTML instead of retrieving to the Html.DropDownListFor but I'm unable to retrieve to tag.
NewCustomerViewModel
public class NewCustomerViewModel
{
public int CustId { get; set; }
[Required]
public string CustFirstName { get; set; }
[Required]
public string CustLastName { get; set; }
[Required]
public int StId { get; set; }
public IEnumerable<State> States { get; set; }
}
CustomerController
public class CustomerController : Controller
{
private CustomerDbContext _context;
public CustomerController(CustomerDbContext context)
{
_context = context;
}
// GET: /<controller>/
public IActionResult Index()
{
return View(_context.Customers.ToList());
}
public IActionResult Create()
{
var stateNames = _context.States.ToList();
var viewModel = new NewCustomerViewModel
{
States = stateNames
};
return View(viewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(Customer customer)
{
if (ModelState.IsValid)
{
_context.Customers.Add(customer);
_context.SaveChanges();
return RedirectToAction("Index");
}
return View(customer);
}
}
Create View
The HTML DropDownListFor below works fine:
#Html.DropDownListFor(m => m.StId, new SelectList(Model.States, "StId", "StName"))
I'm unable to get the select tag to work though.
<select asp-for="StId" asp-items="#Model.States" class="form-control">
<option>Select State</option>
</select>
All of my HTML in my Create view using and rather than the HTML Helpers which is what I'm trying to avoid. I would just like to be able to retrieve the data to the tag instead.
For the select tag helper, asp-items expects SelectListItem collection\SelectList, and each item in that has a Value and Text property. The Value properties value will be used for the option's value and Text propertiesvalue will be used for the display text of the option in the UI.
Items in your States collection does not have a Value and Text property, but has StId and StName property. So we need to convert this type to SelectListItem type.
So your code should be
<select asp-for="StId" asp-items="#(new SelectList(Model.States,"StId","StName"))">
<option>Please select one</option>
</select>
Additional reference
Select Tag Helper in MVC 6
Related
Ok, I'm trying to do a proper dropdown in Core 3.1. In this example https://learn.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-3.1#the-select-tag-helper
Model has a new list with hardcoded values
public string Country { get; set; }
public List<SelectListItem> Countries { get; } = new List<SelectListItem>
{
new SelectListItem { Value = "MX", Text = "Mexico" },
new SelectListItem { Value = "CA", Text = "Canada" },
new SelectListItem { Value = "US", Text = "USA" },
};
I looked for examples where the list is coming from the database but they are very inconsistent. The only way I was able to do the dropdown list is with the ViewBag which is not advised.
I have two models. 1.
public partial class Glossary
{
public int UniqueId { get; set; }
public int Category { get; set; }
public string DisplayText { get; set; }
}
which is my view model
public partial class AdminUser
{
[Key]
public int Id { get; set; }
public string UserName { get; set; }
public string UserLocation { get; set; }
public string UserStatus { get; set; }
//public IEnumerable<Glossary> Glossary { get; set; } //I used this for ViewBag
public List<SelectListItem> UserLocations { get; } = new List<SelectListItem>
{
according to the example my query should go here
};
}
Here is my controller:
public IActionResult Create()
{
// This is the ViewBag that worked with HTML helpers, but I'm trying to use tag-helpers.
/*IEnumerable<SelectListItem> LocationsList = _context.Glossary.Where(x => x.Category == 1).Select(x => new SelectListItem
{
Value = x.UniqueId.ToString(),
Text = x.DisplayText
});
ViewBag.LocationsList = LocationsList;
*/
return View();
}
All examples that found were hardcoded lists and nothing with getting it from the database. What is the proper way to get the data from the Glossary table through the view model with ViewBag? Any pointers are appreciated.
ALSO:
When using this example: Select Tag Helper in ASP.NET Core MVC
When I used
public SelectList Employees { set; get; }
I got error: InvalidOperationException: The entity type 'SelectListGroup' requires a primary key to be defined. If you intended to use a keyless entity type call 'HasNoKey()'.
Both of my tables have PK and adding [Key] to Glossary model didn't fix it.
If you'd like to retrieve data from db and populate a dropdown with retrieved data through a view model (or ViewBag), you can refer to following code snippet.
In AdminUser view model class, include these properties
public string Selected_Glossary { get; set; }
public List<SelectListItem> Glossary_List { get; set; }
In controller
public IActionResult Create(AdminUser model)
{
var adminuser_model = new AdminUser
{
UserName="test"
//for other properties
};
//retrieve data from Glossary table
var items = _context.Glossary.Where(x => x.Category == 1).Select(x => new SelectListItem
{
Value = x.UniqueId.ToString(),
Text = x.DisplayText
}).ToList();
//pass dropdown items through a view model
adminuser_model.Glossary_List = items;
////pass dropdown items through ViewBag
//ViewBag.Glossary_List = items;
return View(adminuser_model);
}
In view page
#model AdminUser
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<form asp-controller="Home" asp-action="Create" method="post">
<select asp-for="Selected_Glossary" asp-items="Model.Glossary_List"></select>
#*populate it through ViewBag*#
#*<select asp-for="Selected_Glossary" asp-items="ViewBag.Glossary_List"></select>*#
<input type="submit" value="Submit" />
</form>
Test Result
I'm new at this and trying to figure out what happen and think this is the best place to ask. Well when I select the project and press the add button the property [Bind""]Model came null but why?
This is my View:
#model PortfolioDetailsVM
<form asp-controller="Portfolio" asp-action="AddProject" method="POST">
<div class="form-group">
<div class="input-group mb-3">
<select asp-for="PortfolioProjects.ProjectId" class="custom-select form-control">
<option disabled selected value="#null">Choose...</option>
#foreach (var item in Model.Projects)
{
<option value="#item.ProjectID">#item.Title</option>
}
</select>
The var PortfolioVM came null with any data.
And this is my Controller and my View Model:
namespace PEG.Models
{
public class PortfolioDetailsVM
{
public PortfolioDetailsVM()
{
Portfolios = new Portfolio();
PortfolioProjects = new PortfolioProject();
}
public Portfolio Portfolio;
public PortfolioProject PortfolioProjects;
public IEnumerable<Project> Projects;
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddProject([Bind("PortfolioProject")]PortfolioDetailsVM PortfolioVM) //<----Null
{
var addproject = PortfolioVM.PortfolioProjects;
if (ModelState.IsValid)
{
try
{
context.Update(addproject);
await context.SaveChangesAsync();
return RedirectToAction("Details", "Portfolio" + PortfolioVM.PortfolioProjects.PortfolioId);
}
catch (DbUpdateException)
{
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
}
return RedirectToAction("Index", "Portfolio");
}
This the other model and Details Method:
// GET: Portfolio/Details/5
public async Task<ActionResult> Details(int id)
{
PortfolioDetailsVM PortfolioVM = new PortfolioDetailsVM
{
Projects = await context.Project.Include(x => x.Task).ToListAsync(),
Portfolios = await context.Portfolio.SingleOrDefaultAsync(x => x.PortfolioID == id)
};
return View(PortfolioVM);
}
namespace PEG.Models
{
public partial class PortfolioProject
{
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
[Column(TypeName = "datetime2")]
public DateTime? CreatedDate { get; set; }
//RelationsId
[Key]
[Column(Order = 0)]
public int PortfolioId { get; set; }
[Key]
[Column(Order = 1)]
public string ProjectId { get; set; }
//Relations
[ForeignKey("PortfolioId")]
public virtual Portfolio Portfolio { get; set; }
[ForeignKey("ProjectId")]
public virtual Project Project { get; set; }
}
}
Bind to properties
First off, you have defined fields in your ViewModels. They can be read in your View, but for Model Binding to work you need to declare them as Properties, with a get and set accessor:
public class PortfolioDetailsVM
{
//...
public Portfolio Portfolio { get; set; }
public PortfolioProject PortfolioProject { get; set; }
public IEnumerable<Project> Projects { get; set; }
}
This should make your binding code work.
Better binding models
Second, you are using Model Binding in a slightly incorrect way. Try not to bind directly to your data models (e.g. the type of PortfolioProject). The model you're binding to shouldn't contain any reference to data model types.
Instead, I usually only declare what I really need in the model I'm binding to, so that I won't ever have to use that ol' Bind attribute in the first place. A simple example for your case:
public class DetailsAddProjectVM
{
public string SelectedProjectId { get; set; }
}
With a corresponding form:
#model PortfolioDetailsVM
<select asp-for="SelectedProjectId" class="custom-select form-control">
...
</select>
which posts to
public async Task<IActionResult> AddProject(DetailsAddProjectVM bindingModel)
{
//look ma, no [Bind]!
var projectid = bindingModel.SelectedProjectId;
}
Of course, for the corresponding form to render, you'd also have to declare a SelectedProjectId property in your original PortfolioDetailsVM.
As you can see, you don't have to bind to your original View Model at all.
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()
{
//...
}
This is quite simple situation actually, but I can't get how to make this work. So, there is list of checkboxes rendered in PartialView. Data passed from parent ViewModel to child ViewModel in PartialView. All these wrapped by form, unfortunately I can't get the data from PartialView.
Parent ViewModel:
public class UserProgramsViewModel
{
public int Id { get; set; }
[Required(ErrorMessage = "Введите название")]
[DisplayName("Название")]
public string ProgramName { get; set; }
[DisplayName("Пользователь")]
public string UserName { get; set; }
[DisplayName("Пользователь")]
public int UserId { get; set; }
[DisplayName("Дни Программы")]
public ICollection<ProgramDaysDTO> ProgramDays { get; set; }
public IEnumerable<DaysViewModel> Days { get; set;} //Passed to Partial
}
Child ViewModel:
public class DaysViewModel
{
public int Id { get; set; }
public string DayName { get; set; }
}
Parent View:
#Html.Partial("Days", Model.Days)
PartialView (here we are using attribute name 'Days' to bind it to Parent model)
#model IEnumerable<BBN.Admin.ViewModels.DaysViewModel>
<ul class="list-group col-lg-2">
#foreach (var item in Model)
{
<li class="list-group-item"><input type="checkbox" name="Days" value="#item.Id" /> #item.DayName</li>
}
</ul>
Controller:
[HttpPost]
[RBAC]
public async Task<ActionResult> Create(UserProgramsViewModel model)
{
var groups = await _us.GetAll();
ViewBag.Users = groups.Select(x => new SelectListItem
{
Text = x.Login,
Value = x.Id.ToString()
});
var dto = new UserProgramsDTO
{
ProgramName = model.ProgramName,
UserId = model.UserId,
Days = model.Days
};
var result = await _ps.Create(dto);
if (result.IsSuccess == (BLL.Utilities.Enums.IsSuccess)Enums.IsSuccess.Success) return RedirectToAction("Index");
else return View("Create");
}
You can use FormCollection. Assign item.DayName to input's name attribute:
PartialView(fragment):
<li class="list-group-item">
<input type="checkbox" name="#(item.DayName)Days" #if (item.Id > 0) { <text>checked</text> } /> #item.DayName
</li>
Then process FormCollection parameter and fill model's Days property with it's help:
Controller:
[HttpPost]
[RBAC]
public async Task<ActionResult> Create(UserProgramsViewModel model, FormCollection formCollection)
{
model.Days = new List<DaysViewModel>();
foreach(var key in formCollection.AllKeys.Where(x => x.Contains("Days")))
model.Days.Add(new DaysViewModel { Id = formCollection[key] == "on" ? 1 : 0, DayName = key.Replace("Days", "")} );
//other stuff...
}
Have you set values for "Days" in your parent view from controller?
like Suppose your parent view name is "Parent" then you should write like this,
public ActionResult Parent()
{
UserProgramsViewModel loUser = new UserProgramsViewModel();
//Assign Your values here
View(loUser);
}
So may be you will not get Null value here.
I am developing a simple mvc application . The code is as follows:
Model .cs:
public class CustomModel
{
public IEnumerable<lang> lstlang { get; set; }
public IEnumerable<org> lstOrg { get; set; }
}
public class lang
{
public int langid { get; set; }
public string langName { get; set; }
}
public class org
{
public int orgId { get ;set;}
public string orgName { get; set; }
}
Controller.cs
public Action Index()
{
// Get data from database and fill the model
var model = new CustomModel();
return View(model);
}
public Action Partial()
{
// Get data from database and fill the model
var model = new CustomModel();
return PartialView(model);
}
[HttpPost]
public Action Partial(FormCollection frm, CustomModel model)
{
// Get data from database and fill the model
var model = new CustomModel();
return PartialView(model);
}
Index.cshtml
#model CustomModel
#Html.TextboxFor(x => x.lang.FirstOrDefault().id);
<input type="button" id="btn" />
#Html.RenderPartial("Partial", model)
Partial.cshtml
#model CustomModel
#Html.TextboxFor(x => x.lang.FirstOrDefault().id);
<input type="submit" id="submit" />
The thing is, when I click the submit button in the Partial.cshtml page, and examine the model in httppost method in public Action Partial(FormCollection frm, CustomModel model), the model contains null for both lists lstlang and lstOrg, but the formcollection[0] will give the selected textbox value.
What am I missing, or is this the right way of using partial views?
Don't use FirstOrDefault(). If you want to post something back to the front end with collections, you'll need to use indexing.
Public class CustomModel
{
public ICollection<lang> lstlang { get; set; }
public ICollection<org> lstOrg { get; set; }
}
#HTML.textboxfor(x=>x.lang[0].id);