I have seen a number of questions that are similar to this one, but I have not come across one with an answer that works for me. Yet :)
My Model looks like this:
public class MyModel
{
public List<MyItem> MyItems{get;set;}
public List<SelectListItem> MySet{get;set;}
}
MyItem looks like this:
public class MyItem
{
public int MyItemId
}
My view has #model MyModel at the top.
My WebGrid looks like this:
#{
var grid = new WebGrid( Model.MyItems, canPage: false );
var gridColumns = new List<WebGridColumn>
{
grid.Column(
format: (item) => #Html.DropDownListFor(modelItem => item.MyItemId.ToString(), Model.MySet ) )
};
#grid.GetHtml( "wgtable",
rowStyle: "gridrow",
alternatingRowStyle: "altgridrow",
displayHeader: true,
columns: grid.Columns( gridColumns.ToArray( ) ) )
}
In this case, the error I get is CS1963: An expression tree may not contain a dynamic operation.
I have also tried
format: (item) => #Html.DropDownListFor(item.MyItemId.ToString(), Model.SubPayGroups ) )
which gives me CS1973: Model has no applicable method named 'DropDownListFor'.
Am I even close?
I ended up moving away from DropDownListFor, using DropDownList instead, also building an IEnumerable: this was my final working solution for the column:
grid.Column(
header:"Sub Pay Group",
format: (item) => #Html.DropDownList("MyItemId", Model.MySet.Select( u => new SelectListItem
{
Text = u.Text,
Value = u.Value,
Selected = u.Value == ((WebGridRow)item)["SubPayGroupId"].ToString() }),
It wasn't in my initial problem definition, but I also needed to set a selected item. Thus, the additional Select from MySet...
Related
I'm trying to implement master/detail using ReactiveUI for a list of attachments. Here's a simplified version of my view model:
public class AttachmentsViewModel : ReactiveObject, ISupportsActivation
{
private ReactiveList<Attachment> _attachments;
public ReactiveList<Attachment> Attachments
{
get => _attachments;
set => this.RaiseAndSetIfChanged( ref _attachments, value );
}
private IReactiveDerivedList<AttachmentViewModel> _attachmentViewModels;
public IReactiveDerivedList<AttachmentViewModel> AttachmentViewModels
{
get => _attachmentViewModels;
set => this.RaiseAndSetIfChanged(ref _attachmentViewModels, value );
}
private AttachmentViewModel _selected;
public AttachmentViewModel Selected
{
get => _selected;
set => this.RaiseAndSetIfChanged( ref _selected, value );
}
}
In my view, I have a ListView and a set of controls to let the user edit properties (e.g. a TextBox called AttachmentName). Here's what I would do in the view, based on the view model above:
public class AttachmentsView : IViewFor<AttachmentsViewModel>
{
public AttachmentsView()
{
InitializeComponent();
this.WhenActivated( d => {
this.OneWayBind( ViewModel, vm => vm.AttachmentViewModels, v => v.List.ItemsSource ).DisposeWith( d );
this.Bind( ViewModel, vm => vm.Selected, v => v.List.SelectedItem ).DisposeWith( d );
this.Bind( ViewModel, vm => vm.Selected.Name, v => v.AttachmentName.Text ).DisposeWith( d );
}
}
}
All of this works as expected, when I click on a row in the ListView, the controls in my panel change accordingly.
However, when I de-select the currently selected item, AttachmentName still displays the old value (instead of showing nothing). I'm assuming that the linq expression does not fire the property changed event because the binding is more than one property deep?
Is there any way around this? Maybe there's another way to achieve master-detail navigation/editing?
You can observe in your view the SelectedItem and change the AttachmentName:
this.WhenAnyValue(x => x.ViewModel.Selected).Where(x => x == null)
.Subscribe(selected => AttachmentName.Text == "").DisposeWith(d);
Problem
I want my Ajax form to pass the value of the selected DropDownListFor to the controller but I can't figure out why the controller is not taking any value.
I am working with ASP.NET MVC and I would like to stick with the helper functions as much as I can.
View
#using (Ajax.BeginForm(new AjaxOptions {
HttpMethod = "Get",
UpdateTargetId = "UserResults",
InsertionMode = System.Web.Mvc.Ajax.InsertionMode.Replace }))
{
#Html.DropDownListFor(Model => Model.Roles, new SelectLi(ViewBag.Groups
as System.Collections.IEnumerable, "Value", "Text"), "Select a group",
new { id = "Value", onchange = "$(this.form).submit();" })
}
#Html.Partial("_UsersGroup", ViewData)
Controller
public ActionResult test(int? selectGroup)
{
// Generate dropdownlist Groups
List<SelectListItem> groupList = new List<SelectListItem>();
var query = from s in db.Roles select s;
if (query.Count() > 0)
{
foreach (var v in query)
{
groupList.Add(new SelectListItem { Text = v.Name, Value =
v.ID.ToString() });
}
}
ViewBag.Groups = groupList;
// End
// This part is supposed to take the passed value as parameter
if (selectGroup == null)
{
// To do code comes here, which takes selectGroup as parameter
}
Details
The form should pass a value based on the selection to the controller which takes it as "selectGroup".
ps. this is my first time asking a question, I'm sorry if I made mistakes.
The parameter of you method needs to match the name of the control which is name="Roles" so the method should be
public ActionResult test(int? roles)
Other potential issues with your code
Your controller generates List<SelectListItem> for use by the dropdownlist. There is no need for the unnecessary extra overhead of then creating a new SelectList (which is IEnumerable<SelectListItem>) from it. The view code can simply be #Html.DropDownListFor(m => m.Roles, (IEnumerable<SelectListItem>)ViewBag.Groups, "Select a group")
Do not use Model (capital M) in the expression. If you make any other reference to the model in your view (e.g. #Model.SomeProperty) you will get an error. Using lower case model => model.somProperty is OK but you can simply use m => m.someProperty
The helper will generate an id attribute (in you case id="Role") so it seems unclear why you are adding new { id = "Value", ..}, especially since you don't appear to be referencing the element by its id anywhere
Learn to use Unobtrusive Javascript rather than polluting you mark up with behavior. Remove the onclick attribute and use $('#Roles').change(function() { $('form').submit(); });
I'm trying to get name of input correct so a collection of objects on my view model can get bound.
#{ViewData.TemplateInfo.HtmlFieldPrefix = listName;}
#Html.EditorFor(m => m, "DoubleTemplate", new {
Name = listName,
Index = i,
Switcher = (YearOfProgram >= i +1)
})
As you can see here, I pass in the "listName" as the prefix for my template, the value of listName = "MyItems".
And here is my template:
#model Web.Models.ListElement
#if (ViewData["Switcher"] != null)
{
var IsVisible = (bool)ViewData["Switcher"];
var index = (int)ViewData["Index"];
var thisName = (string)ViewData["Name"] + "[" + index + "].Value";
var thisId = (string)ViewData["Name"] + "_" + index + "__Value";
if (IsVisible)
{
#*<input type="text" value="#Model.Value" name="#thisName" id ="#thisId" class="cell#(index + 1)"/>*#
#Html.TextBoxFor(m => m.Value, new { #class ="cell" + (index + 1)})
#Html.ValidationMessageFor(m => m.Value)
}
}
but I found that the generated name becomes this: MyItems.[0].Value
It has one extra dot. How can I get rid of it?
Incidentally, I tried to manually specify the name inside the template and found the name gets overridden by the Html helper.
Update
The reason why I have to manually set the HtmlFieldPrefix is the property name (MyItems which is a list of objects) will get lost when MyItems is passed from main view to the partial view. By the time, the partial view called my template and passed in one object in MyItems, the template itself has no way to figure out the name of MyItems as it has been lost since the last "pass-in".
So that's why I have to manually set the html field prefix name. And I even tried to use something similar to reflection(but not reelection, I forgot the name) to check the name of passed in object and found it returned "Model".
Update 2
I tried Stephen's approach, and cannot find the html helper PartialFor().
I even tried to use this in my main view:
Html.Partial(Model, "_MyPartialView");
In Partial View:
#model MvcApplication1.Models.MyModel
<h2>My Partial View</h2>
#Html.EditorFor(m => m.MyProperty)
Here is my templat:
#model MvcApplication1.Models.ListElement
#Html.TextBoxFor(m => m.Value)
Here is my Model:
public class MyModel
{
private List<ListElement> myProperty;
public List<ListElement> MyProperty
{
get
{
if (myProperty == null)
{
this.myProperty = new List<ListElement>() { new ListElement() { Value = 12 }, new ListElement() { Value = 13 }, new ListElement() { Value = 14 }, new ListElement() { Value = 15 }, };
}
return this.myProperty;
}
set
{
this.myProperty = value;
}
}
}
public class ListElement
{
[Range(0, 999)]
public double Value { get; set; }
}
And here is my controller:
public ActionResult MyAction()
{
return View(new MyModel());
}
It only generates raw text("12131415") for me, instead of the wanted text box filled in with 12 13 14 15.
But if I specified the template name, it then throws an exception saying:
The template view expecting ListElement, and cannot convert
List<ListElement> into ListElement.
There is no need set the HtmlFieldPrefix value. MVC will correctly name the elements if you use an EditorTemplate based on the property type (and without the template name).
Assumed models
public class ListElement
{
public string Value { get; set; }
....
}
public class MyViewModel
{
public IEnumerable<ListElement> MyItems { get; set; }
....
}
Editor template (ListElement.cshtml)
#model YourAssembly.ListElement
#Html.TextBoxFor(m => m.Value)
Main view
#model YourAssembly.MyViewModel
...
#Html.EditorFor(m => m.MyItems) // note do not specify the template name
This will render
<input type="text" name="MyItems[0].Value" ...>
<input type="text" name="MyItems[1].Value" ...>
....
If you want to do this using a partial, you just can pass the whole model to the partial
MyPartial.cshtml
#model #model YourAssembly.MyViewModel
#Html.EditorFor(m => m.MyItems)
and in the main view
#Html.Partial("MyPartial")
or create an extension method
public static MvcHtmlString PartialFor<TModel, TProperty>(this HtmlHelper<TModel> helper,
Expression<Func<TModel, TProperty>> expression, string partialViewName)
{
string name = ExpressionHelper.GetExpressionText(expression);
object model = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
var viewData = new ViewDataDictionary(helper.ViewData)
{
TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = name }
};
return helper.Partial(partialViewName, model, viewData);
}
}
and use as
#Html.PartialFor(m => m.MyItems, "MyPartial")
and in the partial
#model IEnumerable<YourAssembly.ListElement>
#Html.EditorFor(m => m)
Call your partial this way:
#Html.Partial("_SeatTypePrices", Model.SeatTypePrices, new ViewDataDictionary
{
TemplateInfo = new TemplateInfo() {HtmlFieldPrefix = nameof(Model.SeatTypePrices)}
})
Partial view:
#model List
#Html.EditorForModel()
Editor template implementation:
#using Cinema.Web.Helpers
#model Cinema.DataAccess.SectorTypePrice
#Html.TextBoxFor(x => Model.Price)
This way your partial view will contain list of items with prefixes.
And then call EditorForModel() from your EditorTemplates folder.
I found that I can change the value of HtmlFeildPrefix in my template.
So what I did to solve my problem was just to assign the correct value to HtmlFeildPrefix in the template directly rather than in the page which calls the template.
I hope it helps.
If I want to pass the HtmlFieldPrefix I use the following construct:
<div id="_indexmeetpunttoewijzingen">
#Html.EditorFor(model => model.MyItems, new ViewDataDictionary()
{
TemplateInfo = new TemplateInfo()
{
HtmlFieldPrefix = "MyItems"
}
})
</div>
I Have MVC Razor view with lot of DropDrownFor.
i wanna set default value to that DropdownListFor.
This is my View:
#Html.DropDownListFor(model => model.DestCountryId, ViewBag.CountryIdList as SelectList, "select", new { #class = "form-control input-sm" })
This is my ViewBag:
ViewBag.CountryIdList = new SelectList(db.Countries.Where(a => a.Currency != null), "Id", "Name");
Ho w to set Default value in this scenario
you need to do like this:
ViewBag.CountryIdList = new SelectList(db.Countries.Where(a => a.Currency != null), "Id", "Name",1);
For Example in the Countries List, you have a item CountryName and its id is 1, you need to pass the 1 in the last parameter, and element with that Id 1 will be shown selected by default.
Example:
public class Country
{
public int Id { get; set; }
public string Name { get; set; }
}
List<Country> list = new List<Country>();
list.Add(new Country{ Id = 1, Name="Test"});
list.Add(new Country{ Id = 2, Name="Test2"});
now in controller action:
int Selected = 2;
ViewBag.CountryIdList = new SelectList(list, "Id", "Name",Selected);
Now the Test2 will be shown selected default in View.
The first parameter that you give to #Html.DropDownListFor is an expression that identifies the object that contains the properties to display.
You have already given "Selected" as default value if nothing is selected or your DestCountryId doesn't hold any value or it doesn't matches with the values passed in the CountryIdList. You need to assign a value to DestCountryId before rendering this view. You can do this either in controller or where you build your view-model like:
viewModel.DestCountryId = 33; where this value exists in the selectList value that you are giving to dropdownlist.
Also, a good practise is to not to use ViewBag. Try creating a simple model with properties that your current view needs.
You can also use SelectList(IEnumerable, String, String, Object) overload of SelectList where the object is the selected value.
Hope this helps.
I am new to MVC, please help me to set the selected value in drop down list populated using View Data. I have gone through so many solutions but every solutions deals with selecting value for single drop down. I have same drop down listed using the foreach loop. Setting selected value for each dropdown in that foreach loop.
My code is shown below.
[In view]
int i = 0;
foreach (var item in Model.Select((x, j) => new { Data = x, Index = j + 1 })) {
#Html.DropDownListFor(model => item.Data.CategoryID,(IEnumerable<SelectListItem>)ViewData["categories"], new { #id = "category" + i })
i++;
}
[in controller]
SelectList selectList = new SelectList((IEnumerable<Category>)ConvertCategoryToList(dt1), "CategoryID", "CategoryName");
ViewData["categories"] = selectList;
There is a lot missing from your sample (e.g.what a category looks like, what the Edit actions look like, what is dt1, what the model is you pass to the view etc), but I will try based on what is shown.
Problem 1: Can't use foreach in binding
You can't use a foreach with a collection of form items. The items have no way of knowing which individual element to bind with (when they are sent back to the server they need more information to make each entry unique).
Solution:
It only knows how to bind if you use indexing (e.g. using for (int i=0; < count; i++){ #Html.DropDownListFor(model=>item[i]...)
Problem 2: Selected values only can come from SelectList!
This is the most irritating feature of DropDownListFor and appears to be a bug. It will not take the current selected value from the bound data. It will only take a current value from a SelectList, which means you need a unique SelectList for every dropdown.
Solution:
Create a unique SelectList in the view, per drop down required, with the item value as the current selection.
With some faking of your data, I got the following working:
Example
Controller:
Only store the list of items for the SelectList in the ViewBag:
ViewData["categories"] = ConvertCategoryToList(dt1);
View:
1. You need to pass a List and not an IEnumerable as IEnumerable cannot be indexed.
#model List<MyApplication.Item>
2. Create a SelectList for each dropdown
SelectList newSelectList = new SelectList((IEnumerable<MyApplication.Category>)ViewData["categories"], "CategoryID", "CategoryName", Model[i].Id);
3. Use the indexing method for addressing your items in the collection.
#Html.DropDownListFor(m => Model[i].Id, newSelectList, "Select an item")
Problem 3: What data to pass?
The other issue with your code, as-it-stands, is what to pass back and forth to/from the edit view? At the moment I would guess (from the variable name dt1) you are passing a DataTable(?). You will need to be very explicit about the data you are using in order to get a clean solution to this one. I would suggest posting a second question with all your code and Razor view HTML.
If you need to see more of my sample code (below), please post your own code so I can make them consistent.
Full dummy controller code below
public class TestController : Controller
{
public List<Category> dt1 { get; set; }
public TestController()
{
this.dt1 = new List<Category>();
for (int i = 1; i <= 10; i++)
{
this.dt1.Add(new Category() { CategoryId = i, CategoryName = string.Format("Category {0}", i) });
}
}
[HttpGet]
public ActionResult Edit()
{
//SelectList selectList = new SelectList((IEnumerable<Category>)ConvertCategoryToList(dt1), "CategoryID", "CategoryName");
ViewData["categories"] = ConvertCategoryToList(dt1);
List<Item> items = new List<Item>();
items.Add(new Item(){Id = 1});
items.Add(new Item(){Id = 2});
items.Add(new Item(){Id = 3});
items.Add(new Item(){Id = 4});
items.Add(new Item(){Id = 5});
return View(items);
}
[HttpPost]
public ActionResult Edit(FormCollection form)
{
// Turn form submission back into a compatible view model
List<Item> items = new List<Item>();
foreach (string key in form.Keys)
{
string val = form[key];
items.Add(new Item() { Id = int.Parse(val) });
}
// Recreate the select list
ViewData["categories"] = ConvertCategoryToList(dt1);
return View(items);
}
List<Category> ConvertCategoryToList(IEnumerable<Category> dt)
{
return dt.ToList();
}
}
Note: The post version of Edit simply recreates the list of data (using the selected values posted back) and returns to the Edit view. This is just for testing.
Screen shot
Dummy category class
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
}
Full test View
#model List<MyApplication.Item>
#using (Html.BeginForm())
{
for (int i = 0; i < Model.Count; i++)
{
SelectList newSelectList = new SelectList((IEnumerable<MyApplication.Category>)ViewData["categories"], "CategoryID", "CategoryName", Model[i].Id);
#Html.DropDownListFor(m => Model[i].Id, newSelectList, "Select an item")
}
<input type="submit" value="Submit"/>
}