I'm trying to understand why, sometimes, my sub-objects disappear.
In my List view, I have the standard
<% foreach (var item in Model)
and when I inspect the item and the model, I can see that item.Map has a couple of elements.
In fact, in my List view I can do:
<% foreach (var map in item.Map)
<% Html.RenderPartial("MapView", map); %>
and MapView can access and display all the properties. I'm including the sub-objects with:
list = from item in _entities.DataTable
.Include("LookupTable1")
.Include("Map")
.Include("Map.LookupTable2") select item;
return View("List", list);
But if I try to do:
<%= Html.Encode(item.Map.FirstOrDefault().Field)%>
I get a null pointer exception, and item.Map has 0 elements.
Can anyone explain why this is happening?
You could probably do without the .Include statements. I'm not sure if that solves your problem though, without knowing the cause of the null-pointer.
Just as a note: when doing MVC, you probably shouldn't be doing Linq queries in the view (like .FirstOrDefault).
list = from item in _entities.DataTable
select new
{
Name = item.Name
Maps = item.Maps
};
Using this syntax you can execute more Linq queries in the controller instead
list = from item in _entities.DataTable
select new
{
Name = item.Name
FirstMap = item.Maps.FirstOrDefault()
};
Or even
list = from item in _entities.DataTable
select new
{
Name = item.Name
Maps = from map in item.Maps
where map = somecondition
select map
};
Related
I'm fairly new at MVC and linq and viewmodels in particular. I managed to get a create and index views to work. The "insert" wasn't as hard as the "list".
I have this linq query:
public ActionResult Index()
{
List<BlendElVM> BEVM = new List<BlendElVM>();
var list = (from Blend in db.blends
join BlendEl in db.blendEl on Blend.ID equals BlendEl.ID
select new
{
Blend.ID, Blend.Title, Blend.TransDt, BlendEl.Comment
}).ToList();
foreach (var item in list)
{
BlendElVM o = new BlendElVM(); // ViewModel
o.Comment = item.Comment;
o.Title = item.Title;
o.TransDt = item.TransDt;
o.ID = item.ID;
BEVM.Add(o);
}
return View(BEVM);
}
What I'm not sure about is the "foreach" section. When I'm running in debug, the "list" shows up fine, but if I comment out the "foreach" I get an error - ie not expecting the model. What does the foreach do? It has to do with the database, but I don't understand the where it is using the "o" and setting the columns. I thought it would all be in one linq query. Is it possible to combine the two and eliminate the "foreach"?
var BEVM = (from blend in db.blends
join BlendEl in db.blendEl on Blend.ID equals BlendEl.ID
select new BlendELVM
{
ID = blend.ID,
Title = blend.Title,
TransDT = blend.TransDt,
comment = blendEl.Comment
}).ToList();
I believe that the foreach is needed in order to read every element in the object so in this case you have:
BlendElVM o = new BlendElVM();
So you're creating and object named " o " of the type BlendELVM and this object contains all the attributes that you declared before which are: ID, Title, TransDT, etc
When you put:
foreach (var item in list)
{
BlendElVM o = new BlendElVM(); // ViewModel
o.Comment = item.Comment;
o.Title = item.Title;
o.TransDt = item.TransDt;
o.ID = item.ID;
BEVM.Add(o);
}
You're assigning to the new object o the item that you're reading in the list and in the end adding it to the BVEM list and answering if you can combine them i will say no because at first you're declaring the query and then you're reading the items on the list and assining them to the BEVM list
My model contains an array of zip code items (IEnumerable<SelectListItem>).
It also contains an array of selected zip codes (string[]).
In my HTML page, I want to render each selected zip code as a drop down with all the zip code options. My first attempt did not work:
#foreach (var zip in Model.ZipCodes) {
Html.DropDownList( "ZipCodes", Model.ZipCodeOptions )
}
I realized that although that would produce drop downs with the right "name" attribute, it wouldn't know which element of ZipCodes holds the value for that particular box, and might just default to the first one.
My second attempt is what really surprised me. I explicitly set the proper SelectListItem's Selected property to true, and it still rendered a control with nothing selected:
#foreach (var zip in Model.ZipCodes) {
Html.DropDownList( "ZipCodes", Model.ZipCodeOptions.Select( x => (x.Value == zip) ? new SelectListItem() { Value = x.Value, Text = x.Text, Selected = true } : x ) )
}
There, it's returning a new IEnumerable<SelectListitem> that contains all the original items, unless it's the selected item, in which case that element is a new SelectListItem with it's Selected property set to true. That property is not honored at all in the final output.
My last attempt was to try to use an explicit index on the string element I wanted to use as the value:
#{int zipCodeIndex = 0;}
#foreach (var zip in Model.ZipCodes) {
Html.DropDownList( "ZipCodes[" + (zipCodeIndex++) + "]", Model.ZipCodeOptions )
}
That doesn't work either, and probably because the name is no longer "ZipCodes", but "ZipCodes[x]". I also received some kind of read-only-collection error at first and had to change the type of the ZipCodes property from string[] to List<string>.
In a forth attempt, I tried the following:
#for (int zipCodeIndex = 0; zipCodeIndex < Model.ZipCodes.Count; zipCodeIndex++)
{
var zip = Model.ZipCodes[zipCodeIndex];
Html.DropDownListFor( x => x.ZipCodes[zipCodeIndex], Model.ZipCodeOptions )
}
That produces controls with id like "ZipCodes_1_" and names like "ZipCodes[1]", but does not select the right values. If I explicitly set the Selected property of the right item, then this works:
#for (int zipCodeIndex = 0; zipCodeIndex < Model.ZipCodes.Count; zipCodeIndex++)
{
var zip = Model.ZipCodes[zipCodeIndex];
Html.DropDownListFor( x => x.ZipCodes[zipCodeIndex], Model.ZipCodeOptions.Select( x => (x.Value == zip) ? new SelectListItem() { Value = x.Value, Text = x.Text, Selected = true } : x ) )
}
However, the problem with that approach is that if I add a new drop downs in JavaScript and give them all the name "ZipCodes", then those completely override all the explicitly indexed ones, which never make it to the server. It doesn't seem to like mixing the plain "ZipCodes" name with explicit array elements "ZipCodes[1]", even though they map to the same variable when either is used exclusively.
In the U.I., user's can click a button to add a new drop down and pick another zip code. They're all named ZipCodes, so they all get posted to the ZipCodes array. When rendering the fields in the loop above, I expect it to read the value of the property at the given index, but that doesn't work. I've even tried remapping the SelectListItems so that the proper option's "Selected" property is true, but it still renders the control with nothing selected. What is going wrong?
The reason you first 2 snippets do not work is that ZipCodes is a property in your model, and its the value of your property which determines what is selected (not setting the selected value in the SelectList constructor which is ignored). Since the value of ZipCodes is an array of values, not a single value that matches one of the option values, a match is not found and therefore the first option is selected (because something has to be). Note that internally, the helper method generates a new IEnumerable<SelectListItem> based on the one you provided, and sets the selected attribute based on the model value.
The reason you 3rd and 4th snippets do not work, is due to a known limitation of using the DropDownListFor() method, and to make it work, you need to use an EditorTemplate and pass the SelectList to the template using AdditionalViewData, or construct a new SelectList in each iteration of the loop (as per your last attempt). Note that all it needs to be is
for(int i = 0; i < Model.ZipCodes.Length; i++)
{
#Html.DropDownListFor(m => m.ZipCodes[i],
new SelectList(Model.ZipCodeOptions, "Value", "Text", Model.ZipCodes[i]))
}
If you want to use just a common name (without indexers) for each <select> element using the DropDownList() method, then it needs to be a name which does not match a model property, for example
foreach(var item in Model.ZipCodes)
{
#Html.DropDownList("SelectedZipCodes",
new SelectList(Model.ZipCodeOptions, "Value", "Text", item))
}
and then add an additional parameter string[] SelectedZipCodes in you POST method to bind the values.
Alternatively, use the for loop and DropDownListFor() method as above, but include a hidden input for the indexer which allows non-zero based, non consecutive collection items to be submitted to the controller and modify you script to add new items using the technique shown in this answer
Note an example of using the EditorTemplate with AdditionalViewData is shown in this answer
I've a List<> included in ViewData.
reloadCollectionModel.ReloadCollection = new List<ReloadModels>();
foreach (DatoPaquete dato in result)
{
reloadModel = new ReloadModels();
reloadModel.IdPaquete = dato.id;
reloadModel.Value = dato.monto;
reloadCollectionModel.ReloadCollection.Add(reloadModel);
}
//disordered (it's works)
ViewData["ReloadModelsCollection"] = reloadCollectionModel.ReloadCollection.AsEnumerable();
Then in razor I show this List using foreach. This works.
#foreach (var item in ViewData["ReloadModelsCollection3"] as List<ReloadModels>)
{
}
My problem... When I sort the list, I can't use foreach to show them (launch Exception).
ViewData["ReloadModelsCollection3"] = reloadCollectionModel.ReloadCollection.OrderBy(item => item.Value).AsEnumerable();
I need to change my ViewData? Or change my foreach?
Thanks in advance, sorry for my bad english.
Because you are casting to a List. After ordering make them a list as follows:
ViewData["ReloadModelsCollection3"] =
reloadCollectionModel.ReloadCollection.OrderBy(item => item.Value).ToList();
That is not AsEnumerable
I've got what appears to be a fairly basic loop:
<% foreach (var item in Model.Items.OrderByDescending(b => b.ItemDateTime)) {%>
Instead of looping through all the items I just want to output the first item, how can I do this?
You can use FirstODefault() method of your collection. Try something like this:
// check if the model contains any item
if (Model.Items.Count() > 0)
{
//show the item...
var firstItem = Model.Items.OrderByDescending(b => b.ItemDateTime).FirstOrDefault();
}
To expand on Felipe's comment, it's better design usually to put things like this in your view models or controllers, not the views.
You could put this on your viewmodel
public Item EarliestItem
{
get { return Items.OrderByDescending(b => b.ItemDateTime).FirstOrDefault(); }
}
Then use this in your view
Or whatever it is you want to do with the earliest item.
All,
I've read through a lot of posts about Checkboxes and ASP.MVC but I'm not that much wiser.
My scenario:
I have a strongly typed View where I pass a collection of summary objects to the view for rendering in a for-each. This summary object contains label data based on a unique id. I also add a checkbox to the row so do so via:
<td>
<%= Html.CheckBox("markedItem", Model.MarkedItem, new { TrackedItemId = Model.Id })%>
</td>
When I perform a POST to get the submitted results my action method takes the strongly typed ViewModel back but the original summary object that I used to create the list is not populated.
Ok, this is annoying, but I can understand why so I'll live with it.
What I then do is to add a new property to my ViewModel called "MarkedItem" which is a string collection.
On postback this marked item is filled with the before and after states if the checkbox has changed but nothing to tell me which key they were for. Just to clarify, if I send this
TrackedItemId = A, Value = false
TrackedItemId = B, Value = true
TrackedItemId = C, Value = false
and set the page to this:
TrackedItemId = A, Value = true
TrackedItemId = B, Value = true
TrackedItemId = C, Value = false
I will get back this:
MarkedItem[0] = true
MarkedItem[1] = false
MarkedItem[2] = true
MarkedItem[3] = false
in other words [0] is the new value and [1] is the old value, [2] and [3] represent values that haven't changed.
My questions are:
Is this right - that I get before and after in this way? Is there any way to only send the latest values?
How can I get hold of the custom attribute (TrackedItemId) that I've added so that I can add meaning to the string array that is returned?
So far I like MVC but it not handling simple stuff like this is really confusing. I'm also a javascript noob so I really hope that isn't the answer as I'd like to return the data in my custom viewmodel.
Please make any explanations/advice simple :)
<p>
<label>
Select project members:</label>
<ul>
<% foreach (var user in this.Model.Users)
{ %>
<li>
<%= this.Html.CheckBox("Member" + user.UserId, this.Model.Project.IsUserInMembers(user.UserId)) %><label
for="Member<%= user.UserId %>" class="inline"><%= user.Name%></label></li>
<% } %></ul>
and in the controller:
// update project members
foreach (var key in collection.Keys)
{
if (key.ToString().StartsWith("Member"))
{
int userId = int.Parse(key.ToString().Replace("Member", ""));
if (collection[key.ToString()].Contains("true"))
this.ProjectRepository.AddMemberToProject(id, userId);
else
this.ProjectRepository.DeleteMemberFromProject(id, userId);
}
}
With thanks to Pino :)
ok, one hack I've come up with - I really hate that I have to do this but I don't see another way round it and I'm sure it will break at some point.
I've already implemented by own ModelBinder to get round some other issues (classes as properties for example) so have extended it to incorporate this code. We use Guid's for all our keys.
If there are any alternatives to the below then please let me know.
Html
<%= Html.CheckBox("markedItem" + Model.Id, false)%>
C#
(GuidLength is a const int = 36, Left and Right are our own string extensions)
//Correct checkbox values - pull all the values back from the context that might be from a checkbox. If we can parse a Guid then we assume
//its a checkbox value and attempt to match up the model. This assumes the model will be expecting a dictionary to receive the key and
//boolean value and deals with several sets of checkboxes in the same page
//TODO: Model Validation - I don't think validation will be fired by this. Need to reapply model validation after properties have been set?
Dictionary<string, Dictionary<Guid, bool>> checkBoxItems = new Dictionary<string, Dictionary<Guid, bool>>();
foreach (var item in bindingContext.ValueProvider.Where(k => k.Key.Length > GuidLength))
{
Regex guidRegEx = new Regex(#"^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$");
if (guidRegEx.IsMatch(item.Key.Right(GuidLength)))
{
Guid entityKey = new Guid(item.Key.Right(GuidLength));
string modelKey = item.Key.Left(item.Key.Length - GuidLength);
Dictionary<Guid, bool> checkedValues = null;
if (!checkBoxItems.TryGetValue(modelKey, out checkedValues))
{
checkedValues = new Dictionary<Guid, bool>();
checkBoxItems.Add(modelKey, checkedValues);
}
//The assumption is that we will always get 1 or 2 values. 1 means the contents have not changed, 2 means the contents have changed
//and, so far, the first position has always contained the latest value
checkedValues.Add(entityKey, Convert.ToBoolean(((string[])item.Value.RawValue).First()));
}
}
foreach (var item in checkBoxItems)
{
PropertyInfo info = model.GetType().GetProperty(item.Key,
BindingFlags.IgnoreCase |
BindingFlags.Public |
BindingFlags.Instance);
info.SetValue(model, item.Value, null);
}