I am still struggling with this--
All I am after is an order form, initially it will be a laptop order form but it might be a computer, printer etc in the future. So I have an Order Controller with a Laptop Action which makes a new laptop model. In the model I want to collect various information such as Customer Name, Customer Details etc. In my MODEL I also want to keep a Select List but I've been trying for ages and just cant seem to get it running. My laptop model has this:
--Laptop Model Select List
public SelectList screenSize = new SelectList(new[]
{
new SelectListItem { Text = "11.6", Value = "11.6" },
new SelectListItem { Text = "15.6", Value = "15.6" },
new SelectListItem { Text = "17", Value = "17" }
}, "Value", "Text");
In the controller I accept the laptop on the post
[HttpPost]
public ActionResult Index(Laptop laptopToEmail)
{
if (ModelState.IsValid)
{
...send an email
}
else return View(laptopToEmail)
}
In the view i am able to render out the list of items and I have a select list but I dont get a value passed to the email when i use
laptopToEmail.screenSize.SelectedValue
The view has this helper.
<%: Html.DropDownList("screenSize",Model.screenSize) %>
Am i missing something here? Surely it cant be this difficult to get a select list to work without a database in MVC.
You know, all these neato MVC Html helpers do have HTML equivalents. For a 2-item drop down, why not just write a bit of html:
<select>
<option value="a">Option A</option>
<option value="b">Option b</option>
</select>
Related
First post here so be gentle please :)
I am creating an ASP.NET with MVC web app that shows a list of items of the same class (Laptop)
I want to create a Dropdown list in the main view below each title that will allow me to filter the results OnChange - hence the selection is empty, but the user can click and select the value in the DropDown list, and the main view items list will update immediately according to the selection.
This is how the list looks now:
Snapshot of the list
I want to implement a dropdown, but I can't seem to get the selected value from the dropdown: (The DropDownlist is populated properly, and working)
<select class="form-control" asp-items="Html.GetEnumSelectList<purpose>()"
onchange="#{Model = Model.Where(m=>m.Purpose == /*HERE SHOULD BE THE VALUE SELECTED*/)}">
<option selected="selected" value="">-Select one-</option>
</select>
And then refreshing the page... but - how do I get the selected value from inside the selection?
If it was in JavaScript I would have done:
html.document.getElementById("The id of the selection").value
but I don't want JavaScript since this is all ASP.NET
To be clear, I have 5 different dropdown lists to filter by, and they can be selected or not.
You're mixing up client-side vs. server-side code. The example below uses only MVC and a full client-server architecture. Each request requires a round-trip to the server.
You have 3 components in this scenario.
ProductsViewModel.cs
public class ProductsViewModel
{
public IList<Laptop> Laptops { get; set; }
public PurposeEnum Purpose { get; set; }
}
ProductsController.cs
public class ProductsController : Controller
{
[HttpGet]
public IActionResult Index()
{
// Retrieve all records without a filter
var unfiltered = db.Laptops.ToList();
var viewModel = new ProductsViewModel() { AvailableLaptops = unfiltered };
return View(viewModel);
}
[HttpPost]
public IActionResult Index(ProductsViewModel viewModel)
{
// Use viewModel.Purpose & viewModel.Maker to filter records from database
var filtered = db.Laptops.Where(l => l.Purpose == viewModel.Purpose).ToList();
var filteredViewModel = new ProductsViewModel()
{
AvailableLaptops = filtered,
Purpose = viewModel.Purpose
};
return filteredViewModel;
}
}
Products\Index.cshtml
#model MyNameSpace.ViewModel
using (BeginForm())
{
#Html.DropDownList(Html.GetEnumSelectList<PurposeEnum>())
foreach (var l in Model.AvailableLaptops)
{
// Loop through Model.AvailableLaptops and generate table
}
<input type="submit" value="Search" />
}
When you visit the URL /Products/Index for the first time, the GET action handler will be triggered. It will generate an unfiltered list of your products along with the dropdown list required for filtering.
When you make your selection and submit the form, the POST action handler will be triggered, and use the selected values in the Purpose and Maker properties of the view model to filter the records. The same view is generated, but with a filtered down list of products.
This is very basic code that ignores validation, error handling and security.
Experimenting with Blazor (Server, if that makes any difference), and I'm having difficulty getting binding to a MultiSelectList to work....
Bit of background: I'm dealing with EF Core and have a Many-to-Many relationship, let's say between people and cars. I'm currently loading a page that shows the existing details, and allowing the user to update this page.
So in my Service, I load my Person entity from the DB, and this includes the details of all the cars they currently own. I also load the list of all the available cars. My Service method then creates a MultiSelectList and adds it to my ViewModel (to be returned to the Razor Page):
Service method
vm.CarSelector = new MultiSelectList(
allCars,
nameof(Car.CarId),
nameof(Car.Name),
person.OwnedCars.Select(oc => oc.CarId));
This is fictitious code, but I hope you get the picture. When debugging this (in the Service method) I can see that this MultiSelectList has an entry for every car, and the ones that are already selected are showing as Selected. Great!
Blazor Razor Page
So, this is where I come unstuck.... I can't work out how to do the two-way data-binding of a Razor control to this object.
I'm trying to use an <InputSelect />, but that might not be the best control to use.
ideally (actually, that's more of a "must have"), each option should have CheckBox.
I'm wondering whether the use of a MultiSelectList really buys me anything
Checkboxes are a bit different in blazor. Normally you would use the bind-value attribute on an input element as shown below, however, this is not recommended as you will only be able to read the value and NOT update the UI by changing the boolean value via code:
<input type="checkbox" #bind-value="#item.Selected"/>
Instead, use the #bind syntax for checkboxes, which is much more robust and will work both ways (changing the bound boolean value from code & interacting with the checkbox on the UI). See the syntax below:
<input type="checkbox" #bind="#item.Selected"/>
The bind attribute will automatically bind your boolean value to the "checked" property of the html element.
Also make sure you are binding to the "Selected" property rather than the "Value" property.
Using the built in bind will prevent the need to manually setup events as you did in your answer. You can also get rid of the if/else block and merge your code into a single code flow since you are now binding to the boolean rather than setting the checked property manually. If you still need to tap into an event to fire off some process(maybe hiding parts of UI on checking a box), I'd suggest using the onclick event and manually passing in the multiselect Item for each line. Here is the final code:
#foreach(var item in list)
{
<input type="checkbox" #bind="item.Selected" #onclick="(()=>handleClick(item))" />
}
#foreach(var item in list.Where(x=>x.Selected))
{
<p> Item #item.Text is Selected</p>
}
#code {
MultiSelectList list = new MultiSelectList(new List<Car> { new Car { Year = 2019, Make = "Honda", Model = "Accord" }, new Car { Make = "Honda", Model = "Civic", Year = 2019 } });
private void handleClick(SelectListItem item)
{
//Do something crazy
}
}
I got this to work with a component that takes the MultiSelectList as a parameter. There may be more elegant ways to achieve this (please do update if you know of a better way).
#using Microsoft.AspNetCore.Components
#using Microsoft.AspNetCore.Mvc.Rendering
<div class="multiselect">
<div id="checkboxes">
#foreach (var item in this.Items)
{
<div>
<label for="#item.Value">
#if (item.Selected)
{
<input type="checkbox" id="#item.Value" checked="checked" #onchange="#((e) => CheckboxChanged(e, item.Value))" />
}
else
{
<input type="checkbox" id="#item.Value" #onchange="#((e) => CheckboxChanged(e, item.Value))" />
}
#item.Text
</label>
</div>
}
</div>
</div>
#code
{
[Parameter]
public MultiSelectList Items { get; set; } = null!;
private void CheckboxChanged(ChangeEventArgs e, string key)
{
var i = this.Items.FirstOrDefault(i => i.Value == key);
if (i != null)
{
i.Selected = (bool)e.Value;
}
}
}
I am using the following syntax in my View to create a dropdown list from a Enum in my class. The error is
One or more compilation references are missing. Possible causes include a missing preserveCompilationContext property under buildOptions in the application's project.json.
<div class="form-group">
<select asp-for="Subscription" class="form-control" asp-items="#new SelectList(Enum.GetNames(typeof(SubscriptionTypes)))">
<option disabled selected value="">Select a Subscription</option>
</select>
</div>
The Enum in the class is:
public enum SubscriptionTypes
{
Type1, Type2, Type3
}
You should use the build in generator, to generate userfriendly Values. See [Display(Name="Some Cool Name with whitespaces"] Annotation on enum values.
Html.GetEnumSelectList<CountryEnum>()
So your razor page would look like this:
#model CountryEnumViewModel
<form asp-controller="Home" asp-action="IndexEnum" method="post">
<select asp-for="EnumCountry"
asp-items="Html.GetEnumSelectList<Some.NameSpace.CountryEnum>()">
</select>
<br /><button type="submit">Register</button>
</form>
More info here: https://learn.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-2.2#the-select-tag-helper
Common root cause for this problem is using of non-fully qualified type names (like SubscriptionTypes) when no using directive is specified.
To fix it either add using directive at the source top:
#using Your.Namespace.Here
or use fully qualified type names:
<select asp-for="Subscription" class="form-control" asp-items="#new SelectList(Enum.GetNames(typeof(Your.Namespace.Here.SubscriptionTypes)))">
I think this was a pretty elegant solution. I will use one of my own examples to demonstrate. I have a Model where one of the fields needs to be selected from an enumeration. The Model is called DonationMaterials and the property is Availability
public string? Availability { get; set; } = String.Empty;
This property can have 3 values; Available, Not Available, or Unknown. I created a public static method under the model called AvailabilityOptions
public static IEnumerable<SelectListItem>? AvailabilityOptions()
{
return new[]
{
new SelectListItem { Text = "Available", Value = "Available"},
new SelectListItem { Text = "Not Available", Value = "Not Available"},
new SelectListItem { Text = "Unknown", Value = "Unknown"}
};
}
Once I created the static method, anytime I need to fill the SelectList I simply use the code in the cshtml for the page. You will need to include the Namespace of the model to have accessibility to the static method. This is clean. You could also answer the enumeration from the static method and use code to convert the enumeration into a collection of SelectListItem's
<select asp-for="DonationMaterials.Availability" asp-items="DonationMaterials.AvailabilityOptions()" class="form-control"></select>
The beauty of this approach is that the enumeration is really a Domain (mode) specific data and it keeps it where it belongs (in the model). The use of the model in the interface is quite simple.
I want to use multiple select in
Chosen.
I have Skill model like,
public class Skill
{
public int Id { get; set; }
public string Name { get; set; }
}
This works in my application:
<select data-placeholder="Choose a Country..." class="chzn-select" multiple >
<option value=""></option>
<option value="United States">United States</option>
<option value="Albania">Albania</option>
<option value="Algeria">Algeria</option>
</select>
I want to replace Countries data with my data. In controller i write:
var list = MyService.LoadAllSkills();
ViewBag.Skills = new MultiSelectList(list, "Id", "Name");
In view:
#Html.ListBox("Name", ViewBag.Skills as MultiSelectList,
new { #class = "chzn-select" } )
View result of #Html.ListBox() and #Html.DropDownList() is not like <select>
I get so result:
But, I want to get result as
How can I change Chosen sample?
The only difference I can see between the hardcoded example (which you stated that is working) and the one you generate with the ListBox helper is the absence of the data-placeholder attribute. So:
#Html.ListBox(
"Countries",
ViewBag.Skills as MultiSelectList,
new { #class = "chzn-select", data_placeholder = "Choose a Country..." }
)
This should at least generate the same markup as what you said is working. If it doesn't work then probably you haven't setup the plugin correctly or you have some other javascript errors. Read the documentation of the plugin about how it should be setup.
as other dudes mentioned, it seems your problem cause is not server-side (razor), it's actually client-side (most probably your Jquery plugin initialization).
probably when the plugin initialization called the html DOM is not generated yet, put your plugin initialization script at the end of the body or inside $(document).ready() and don't forget to take a look at the console to see if there is any errors
happy coding
I have following problem. In my view model I defined some list properties as follows:
public class BasketAndOrderSearchCriteriaViewModel
{
List<KeyValuePair> currencies;
public ICollection<KeyValuePair> Currencies
{
get
{
if (this.currencies == null)
this.currencies = new List<KeyValuePair>();
return this.currencies;
}
}
List<KeyValuePair> deliverMethods;
public ICollection<KeyValuePair> DeliveryMethods
{
get
{
if (this.deliverMethods == null)
this.deliverMethods = new List<KeyValuePair>();
return this.deliverMethods;
}
}
}
This view model is embedded in another view model:
public class BasketAndOrderSearchViewModel
{
public BasketAndOrderSearchCriteriaViewModel Criteria
{
[System.Diagnostics.DebuggerStepThrough]
get { return this.criteria; }
}
}
I use 2 action methods; one is for the GET and the other for POST:
[HttpGet]
public ActionResult Search(BasketAndOrderSearchViewModel model){...}
[HttpPost]
public ActionResult SubmitSearch(BasketAndOrderSearchViewModel model){...}
In the view I implement the whole view model by using the EditorFor-Html Helper which does not want to automatically display DropDownLists for List properties!
1. Question: How can you let EditorFor display DropDownLists?
Since I could not figure out how to display DropDownLists by using EditorFor, I used the DropDownList Html helper and filled it through the view model as follows:
public IEnumerable<SelectListItem> DeliveryMethodAsSelectListItem()
{
List<SelectListItem> list = new List<SelectListItem>();
list.Add(new SelectListItem()
{
Selected = true,
Text = "<Choose Delivery method>",
Value = "0"
});
foreach (var item in this.DeliveryMethods)
{
list.Add(new SelectListItem()
{
Selected = false,
Text = item.Value,
Value = item.Key
});
}
return list;
}
My 2. question: As you can see I pass my view model to the action metho with POST attribute! Is there a way to get the selected value of a DropDownList get binded to the passed view model? At the moment all the DropDownList are empty and the selected value can only be fetched by the Request.Form which I definitely want to avoid!
I would greatly appreciate some ideas or tips on this!
For those like me that got to this post these days I'd recommend you to fully download the tutorial from http://www.asp.net/mvc/tutorials/mvc-music-store-part-1 which covers this and most of the common techniques related with .NET MVC applications.
Anyway Really usefull your post and answers man (If I could vote you I would :)
Let's try to take on this one:
Answer to Question 1: How can you let EditorFor display DropDownLists?
When you call Html.EditorFor() you can pass extra ViewData values to the EdiorTemplate View:
<%: Html.EditorFor(model => Model.Criteria, new { DeliveryMethods = Model.DeliveryMethods, Currencies = Model.Currencies}) %>
Now you have ViewData["DeliveryMethods"] and ViewData["Currencies"] initialized and available inside your EditorTemplate.
In your EditorTemplate you somehow need to call and convert those entries into DropDowns / SelectLists.
Assuming you've got an ascx file of type System.Web.Mvc.ViewUserControl<BasketAndOrderSearchCriteriaViewModel> you could do the following:
<%: Html.LabelFor(model => model.DeliveryMethods) %>
<%: Html.DropDownList("SelectedDeliveryMethod", new SelectList(ViewData["DeliveryMethods"] as IEnumerable, "SelectedDeliveryMethod", "Key", "value", Model.SelectedDeliveryMethod)) %>
Same goes for the Currencies.
<%: Html.LabelFor(model => model.Currencies) %>
<%: Html.DropDownList("SelectedCurrency", new SelectList(ViewData["Currencies"] as IEnumerable, "SelectedDeliveryMethod", "Key", "value", Model.SelectedCurrency)) %>
This setup will make your DeliveryMethodAsSelectListItem() obsolete and you can use any kind of list. Means you are not bound to KeyValuePairs. You'll just need to adjust your call on Html.DropDownList() from now on.
As you can see, I have introduced some new properties to your BasketAndOrderSearchCriteriaViewModel:
Model.SelectedDeliveryMethod
Model.SelectedCurrency
They are used to store the currently selected value.
Answer to Question 2: Is there a way to get the selected value of a DropDownList get binded to the passed view model?
In the EditorFor template we are passing the newly created Model.SelectedDeliveryMethod and Model.SelectedCurrency properties as the SelectedValue Parameter (See 4th Overload of the DropDownList Extension Method).
Now that we have the View doing it's job: How can we get the currently selected value inside the POST Action?
This is really easy now:
[HttpPost]
public ActionResult SubmitSearch(BasketAndOrderSearchViewModel model)
{
...
var selectedDeliveryMethod = model.Criteria.SelectedDeliveryMethod;
var selectedCurrency model.Criteria.SelectedDeliveryMethod;
...
}
Note: I don't have an IDE to test it right now, but it should do the trick or at least show you in which direction to go.