Nested binding in ASP.NET MVC Razor - c#

Description
A nested object needs to be bound to a dropdown, there already is a preselected value for the nested objects. The possible values are of an enum type. The dropdownlist with some other data will be posted back to the controller.
Code - types & classes:
[Serializable]
public enum DummyEnum
{
DummyZero = 0,
DummyOne = 1
}
public class Dummy
{
public Guid Id { get; set; }
public Dictionary<Guid, DummyEnum> DummyEnum { get; set; }
}
public class DummyViewModel
{
public Dictionary<Guid, List<Dummy>> Dummies { get; set; }
}
public class DummyController
{
private void Init(DummyViewModel model)
{
model.EnumList = Enum.GetValues(typeof(DummyEnum))
.Cast<DummyEnum>()
.Select(e => new SelectListItem
{
Value = (e).ToString(),
Text = e.ToString()
});
}
}
HTML:
<td>
#Html.DropDownListFor(
m => m.Dummies[dummiesKey][dummyIndex]
.Enum[Id],
new SelectList(Model.EnumList, "Value", "Text", e.ToString()))
</td>
<select
data-val="true"
data-val-required="The Enum field is required."
id="Dummies_guid__0__Enum_guid_"
name="Dummies[guid][0].Enum[guid]"
style="display: none;"
>
<option value="DummyOne">DummyOne</option>
<option selected="selected" value="DummyZero ">DummyZero</option>
</select>
Problem
The problem is that the model doesn't seem to be able to map the payload back to an object or misses the reference to the bound object. Everything is filled in correctly the guid, index and the value of the enum.
Payload:
Dummies[guid][0].Enum[guid]: DummyZero
Dummies[guid][0].Enum[guid]: DummyZero
Attempts
I tried with the following ideas but they weren't successfull for me.
model not binding to dictionary
binding dictionary
What am I missing?

The problem as stated in the question had to do with mvc converting Dictionary to a List<KeyValuePair<Guid, List>>binding or use JSON.
All that needs to be done is break down the object as mvc would and provide the necessary data. As explained in dicitionary binding.
The object was of type Dictionary<Guid, List<Dummy>>. So the object actually becomes List<KeyValuePair<Guid, List<List<KeyValuePair<Guid, enum>>>>>.
Now to break it down
MVC needs the index of the first object that is being used. To get this index we need to covert the dictionary to a list ourself. More specific the values or keys of the dictionarys.
var dummies= Model.Dummies[key];
var dummiesIndex = Model.Dummies.Values.ToList().IndexOf(dummies);
The index needs to be provided along side the post. This can be done by adding it above the dropdown as a hidden field along side the key from the dictionary.
#Html.Hidden("dummies.Index", dummiesIndex)
#Html.Hidden("dummies[" + dummiesIndex + "].Key", key)
Next is the List of objects. Again the index needs to be provided for the binding.
#Html.Hidden("dummies[" + dummiesIndex + "].Value.Index", dummyIndex)
The last step is another dictionary, this is just like the first dictionary
#Html.Hidden("dummies[" + dummiesIndex + "].DummyEnum.Index", dummyEnumIndex)
#Html.Hidden("dummies[" + dummiesIndex + "].DummyEnum.Key", yourKey)
For the value you want to actually post you need to follow the complete path like above.
#Html.DefaultCombo("dummies[" + dummiesIndex + "].DummyEnum[" + dummyEnumIndex+ "]", "Value", "Text", Model.EnumList, enum)
Now MVC can remap your objects.

The front-end response will be in that form that you'll set it there. Then the ASP middleware will parse all those strings back to an object at your back-end.
So key moments here are:
a controller action parameter type - it could be any of your types but it should correlate with your front-end;
front-end's select element name attribute value - it should contain full existing path.
As I got from your code example the following.
You have DummyViewModel view model class. It has property Dummies.
You have Dummy class, that nested in DummyViewModel as Dummies. 2nd level dictionary.
You have DummyEnum enum class, that is in use at DummyEnum values. Same names, different adjacent levels.
The SelectList values are OK. They are directly from the enum.
Based on the structure, to set up a first enum value you need to navigate its level by setting KEY and VALUE. Then do it again for another level. For me, the first enum value in this structure should have something like this:
Dummies[dummiesKeyGuid][dummyIndexId].DummyEnum[dummyEnumKeyGuid];
Where you have the following types in each step:
Dummies[dummiesKeyGuid] is <List<Dummy>>;
Dummies[dummiesKeyGuid][dummyIndexId] is <Dummy>;
Dummies[dummiesKeyGuid][dummyIndexId].DummyEnum[dummyEnumKeyGuid] is <DummyEnum>.
So #Html.DropDownListFor(...) should be updated to set the path as name.
Also:
your action should take the Dummies type as a parameter.
ActionResult SomeFromProcessingAction(DummyViewModel Dummies)
you should handle the passed stringified parameters map to the Dictionary type. It could be used outside of ASP (front-end) but has the issue. Please, check this post and its topic: https://stackoverflow.com/a/29820891/6344916. Sometimes, it is easier to not use the Dictionary there. Just other classes like List or Array.
From your HTML example, I didn't get the m.Dummies type to have in its structure the Enum field. dummiesKey can't have "guid" value. GUID is another type that can be made ToString() easily but not otherwise.
IMHO. Too many Dummys in the names. It confuses and breaks its understanding.
Also, its nested structure is VERY cumbersome. You could use smaller user forms to set the values and take smaller objects or event values on your back-end instead of the huge object parameters.
The List class has no mapping requirements, just denormalize your dictionaries and it will be easier to map them. The same with their navigation on the front-end. If required, you can make the List ToDictionary(). :)=)
For example, Dummy could be written using List<T>:
public class SomeElement
{
public Guid Id { get; set; }
public DummyEnum Enum { get; set; }
}
public class Dummy //kind of aggregation
{
public Guid Id { get; set; }
public List <SomeElement> DummyEnum { get; set; }
}
And so on with DummyViewModel. Or get rid of it and use some List directly.
Hope, this will help.

I think your issue is not related to nested properties or input naming and your implementation seems fine.
The issue you encountered is linked to Dictionary<Guid, ...> default model binder behavior. The default binder simply does not seem to handle it correctly. (ie. Dictionaries with Guid as keys)
I have reproduced your issue and then switched to Dictionary<string, ...> and everything worked fine this time.
The only way your could overcome this should probably be to implement your own model binder for Dictionary<Guid, object>.
I tried to understand the root problem and it seems to be located here (Invalid explicit cast from string to Guid) as also described here (found later :-)...)

Related

`[FromQuery]` IEnumerable<SomeObject> parsing in ASP.NET Core 3.1?

So, when I tested how binding works for an IEnumerable<string> argument, you simply pass the argument's name in the query string, repeatedly, like this: ?a=item1&a=item2&a=item3...
So, what must I write, if I have an argument of type IEnumerable<SimpleObject> a, where SimpleObject is defined as the following:
public class SimpleObject
{
public string Number { get; set; }
public string Text { get; set; }
}
in order to successfully bind it to a list of said objects? Or no such default ModelBinder exists for that mapping? (Please provide a sample ModelBinder in that case)
The default model-binding setup supports an indexed format, where each property is specified against an index. This is best demonstrated with an example query-string:
?a[0].Number=1&a[0].Text=item1&a[1].Number=2&a[1].Text=item2
As shown, this sets the following key-value pairs
a[0].Number = 1
a[0].Text = item1
a[1].Number = 2
a[2].Text = item2
This isn't quite covered in the official docs, but there's a section on collections and one on dictionaries. The approach shown above is a combination of these approaches.

Class design best way to handle multi-value options and the selected value?

What's the best way to design a class (or classes) that can hold the potential values of item, as well as the one the user actually selected? I've come across this problem before and always feel like I'm missing a core class design feature.
Right now I usually do something like the following
class MultiChoice
Name (I.e. Box Size)
Default Value ("22x15")
PotentialValues ({"10x10","20x20","22x15"})
But that doesn't handle the actual value the user selected, so I add that in.
class MultiChoice
Name (I.e. Box Size)
Default Value ("22x15")
PotentialValues ({"10x10","20x20","22x15"})
SelectedValue
That doesn't feel right though, because when I construct a drop-down I'm filling in stuff with SelectedValue = null. Then when I store the data, I'm storing all the options too, which I don't need.
Is there a better way to handle this with an interface or other language construct? I always feel like I'm missing something blatantly obvious here.
You really have two separate entities here:
MultiChoiceQuestion
MultiChoiceAnswer
Create two separate classes to represent these two separate concepts.
ASP.NET MVC has the SelectList class. While you might not actually be working in ASP.NET MVC, it seems clear that Microsoft felt that the concept of "backing class for a dropdown" was universal enough to warrant its own class.
In whatever you consider the "Model" (that part of your program containing the business domain classes and business logic), there will always exist database tables that serve as lookups for these dropdowns.
tblCountries
CountryID PK
CountryCode string
FullName string
In your ViewModel, there will be a corresponding list of countries from which you can populate the dropdown:
public class InvoiceViewModel
{
...
public int CountryID { get; set; }
public SelectList Countries { get; set; }
// or
public List<Country> Countries { get; set; }
...
}
Of course, by the time you get to the UI, the actual dropdown contains enough plumbing to hold both the select list and the selected value.
You really only need a single Value field. Set it to whatever you want in the constructor (so it's defaulted when the object is created). You can also change your 'potential values' to be static, so it's the same for the entire class.
public class Box
{
public string Value { get; set; }
public static List<string> AllowedValues { get; private set; }
public Box()
{
AllowedValues.AddRange(new string[]{"10x10","20x20","22x15"});
Value = AllowedValues.First();
}
}
Then when a user changes the value, simply update it.
Box thisBox = new Box();
string val = "22x15";
if (Box.AllowedValues.Contains(val))
thisBox.Value = val;

MVC 4 Dynamic action method argument

I have the following class definition:
public class CallGroupViewModel
{
public string Name { get; set; }
public string BulkEmails { get; set; }
public List<dynamic> Members {get;set;}
}
The Members property is some dynamic knockout code and the contents change depending on UI stuff. I am wanting to take a short cut since the UI is so up in the air right now.
My problem is that when it get to my action method, everything gets populate, including Members but its a array of objects and I can't get at the individual properties on them.
public ActionResult SaveGroup(CallGroupViewModel group)
group.Members //this has a count
group.Members[0].email //this pukes as 'email' is not valid for 'object'
I'm probably just missing something, any help is appreciated.
Thanks!
The model binding system which populates MVC action method arguments isn't intended to work with dynamic objects. The system requires type information to know which information to bind. Consider changing your model to use strong types instead.
For accessing dynamic properties from the object, in your case you can use :
var email = group.Members[0].GetType()
.GetProperty("Email")
.GetValue(group.Members[0], null);

How do I pass an entire model from my Index View to a completely different controller?

Here's the relevant part of my Index view (Index.cshtml):
#foreach (var item in Model) {
<li>
#Html.ActionLink(item.name, "Index", "Filler", new { cap = item }, null)
</li>
}
As you can see, the ActionLink is tied to the Index action on the Filler Controller, and is passing in the entire item (the model)- "item" is of type "capsule".
Now, on my Filler Controller, in the Index action:
public ActionResult Index(capsule cap)
{
var fillers = db.fillers.ToList();
return View(fillers);
}
The capsule class that was automatically generated by Entity Framework is:
namespace CapWorx.Models
{
using System;
using System.Collections.Generic;
public partial class capsule
{
public capsule()
{
this.fillers = new HashSet<filler>();
}
public int pk { get; set; }
public string name { get; set; }
public virtual ICollection<filler> fillers { get; set; }
}
}
The problem is "cap" is NULL in the above Index action. But, if I change the type to "object" instead of "capsule", I do get some weird non-null data, but I can't cast the object to "capsule". Does anyone know why this is NULL?
Thanks,
Mike
You usually just have to pass in the id to the action. For example, can you refactor your code so that it can take in a capsuleId, get the capsule from db and do whatever processing is needed. Adding the entire object to route values in ActionLink doesn't make any sense. Have a look at the link being generated. It is probably just something like ...?cap=Namespace.Capsule as the object would have be ToStringed
The first problem is in MVC you can't bind to an interface (ICollection). You'll need to change it to a List - List<filler>. The second problem you will face is that Lists/Arrays need to be represented in array notation for proper posting, something like name="books[0].book_id". Even though MVC does a lot of magic, the model in your link still has to be represented as a query string eventually.
Depending on what you are trying to do, you may be better off representing your model as a JSON object and posting with .ajax().
See this SO post for other ideas - Need help with binding Set with Spring MVC form
I'm not totally sure why this would work(I think you're nulling out the html attributes), but try to remove the "null" part of the actionlink.
Or, the controller which created the models is wrong.
Again, don't kill me for this.
#Html.ActionLink just generates an anchor element (...), so it makes no sense to attempt to bind a complete object to the routeValues parameter. As #manojlds says, it make much more sense to just pass the relevent key value, since you'll be performing the lookup then anyway (remember, the web is "stateless").

Editing form fields via ajax and .net mvc

I have a number of inputs on my page. I would like to save the changes to the model on the input blur, so as I change the value of each input it gets saved back to server, like Google contacts.
<input id="FirstName" name="FirstName">Jack</input>
I create a blur event using jquery to post the value back to the server. It posts a structure with the name of the input, the value and an id of the entity.
$.post(url, { id: "2", key: "FirstName", value: "Jack" }, successFuction);
In my controller I have:
public ActionResult EditField(int id, string key, string value)
I then retrieve the entity using EntityFramework with the id. I then wanted to update the property on the model for the field.
var entity = _db.Get(id);
entity[key] = value;
return Content "Success";
Which I obviously can't do! The only way I can think off is multiple methods for each field so EditName, EditAddress etc. which seems wrong. I want this method to be able to handle each property of the model.
What is a better way to structure the controller instead of writing multiple methods for each individual field?
You could post your entire form (e.g. first name, last name, etc.) on each blur for any of your fields (this should be fine since you're saving all changes as the user progresses on the form anyway). Unless you're really trying to save bytes, posting the whole form seems fine.
You could just post the field name and then use reflection to look up the property of your object and set the value.
I think that you can do it if you are willing to model the entity in a general way:
public class FieldEntity {
public int Id { get; set; }
public string Key { get; set; }
public string Value { get; set; }
}
Then use it inside the context like:
var fieldEntity = db.Find(id);
fieldEntity.Key = key;
fieldEntity.Value = value;
db.SaveChanges();
However, it is usually better to structure data in a way that is meaningful. In the example you describe it looks like you might have a Person and Address entity. So why not have a Person entity that has a property Address?

Categories

Resources