#model dynamic binding with arrays - asp.net-mvc

I have a data class that I want to show via a View, allow the User to change it, and bring it back in via data binding. I want to do this via #model dynamic. Is this possible?
This is my model
public class MyData
{
public int A { get; set; }
public string B { get; set; }
}
Getting sent to a view like so
public ActionResult Index()
{
MyData[] myDataList = new MyData[2];
myDataList[0] = new MyData() { A = 2, B = "Fred" };
myDataList[1] = new MyData() { A = 3, B = "Sue" };
dynamic charts = (from ch in myDataList
select new
{
ch.A,
ch.B
}).AsEnumerable().Select(c => c.ToExpando());
return View(charts);
}
With the view like so (this is wrong)
#model IEnumerable<dynamic>
#foreach (dynamic item in Model)
{
<text>
#Html.TextBox("item.B")
</text>
}
I'm floundering a bit here as I don't know if this is even possible and I can't find many complex examples of #model dynamic
To be clear, this example is an academic one, what I want here is to understand what's possible with dynamic models in asp mvc
Any advice?
Thanks

Try this in your view:
#for(int i = 0; i < Model.Lenght; i++)
{
#Html.TextBoxFor(model => model[i].A)
#Html.TextBoxFor(model => model[i].B)
}

While you can get it to work, I wouldn't recommend it.
The simple fix IMHO is to use the ExpandoObject as model directly. There's no point in converting it to dynamic at all.

Related

How to create dropdown in MVC using model class and need to display the values(numbers) instead of names?

I tried much applications, but no useful. Can any one help me with this case.
For example in dropdown list I have fruits list apple, banana, grape etc ..
If I selected apple in dropdown, then I want to display the value as 1, if banana selected need to display the value is 2.
Thanks in advance.
If you don't have a model, you need to create first. Example:
Model
public class ModelName
{
public List<SelectListItem> Numbers { get; set; }
public int SelectedNumber {get; set;} //add this after your view
}
In your controller, set this:
public ActionResult Index()
{
var model = new ModelName();
model.Numbers = new List<SelectListItem>();
for (int i = 1; i <= 10; i++)
{
model.Numbers.Add(new SelectListItem { Value = i.ToString(), Text = i.ToString() });
}
return View(model);
}
In your view put this:
<div>
#Html.DropDownListFor(m => m.SelectedNumber, Model.Numbers)
</div>
Then you can access the selected number in your controller:
[HttpPost]
public ActionResult Index(ModelName model)
{
int selectedNumber = model.SelectedNumber;
// do anything with selected number
}

Displaying one object value (from a collection) in a label

I am learning MVC4. I could display records in a tabular format using foreach.
Now, I need to display theDescription of (only) first Topic object in a label. I need to do it without a foreach. How can we do it?
VIEW
#model MvcSampleApplication.Models.LabelDisplay
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm())
{
foreach (var item in Model.Topics.Select((model, index) => new { index, model }))
{
<div>#(item.index) --- #item.model.Description---- #item.model.Code</div> <div></div>
}
}
Controller Action
public ActionResult Index()
{
LabelDisplay model = new LabelDisplay();
Topic t = new Topic();
t.Description = "Computer";
t.Code=101;
Topic t3 = new Topic();
t3.Description = "Electrical";
t3.Code = 102;
model.Topics = new List<Topic>();
model.Topics.Add(t);
model.Topics.Add(t3);
return View(model);
}
Model
namespace MvcSampleApplication.Models
{
public class LabelDisplay
{
public List<Topic> Topics;
}
public class Topic
{
public string Description { get; set; }
public int Code { get; set; }
}
}
REFERENCE
Iterate through collection and print Index and Item in Razor
I need to display theDescription of (only) first Topic object in a label
Unless I totally misunderstood you, selecting the first item (only) in your view would look something like:
#if (Model.Topics.Any())
{
#Html.DisplayFor(x => x.Topics.First().Description)
}

Rebinding MVC Models with multiple array selections

I have read somewhat on the post-redirect-get design pattern and I'm not sure if it works for my purpose as what I have is an MVC site which is design to look like an application, I have multiple dropdowns on the page which all bind to an integer array as below in my controller:
[HttpPost]
public ViewResult ResponseForm(PartyInvites.Models.GuestResponse response, int[] SelectedCustomer)
{
return View(response); // works but resets all my selected dropdowns
// return View(); // gives an error that it can't rebind items in view
}
My View:
#foreach (Schedule sched in Model.Schedules)
{
#Html.DropDownList("MySelectedCustomer", new SelectList(sched.Customers, "Id", "FirstName"), "Select A Customer", new { #class = "SelectedCustomer" })
}
The GuestResponse:
public class GuestResponse
{
[Required(ErrorMessage = "You must enter your name")]
public string Name { get; set; }
public string SomeString = "someString";
public string Email { get; set; }
public string Phone { get; set; }
public bool? WillAttend { get; set; }
public int SelectedSchedule = 0;
public int SelectedCustomer = 0;
public List<Schedule> Schedules
{
get
{
return new List<Schedule>() { new Schedule() { ScheduleName = "party1", ScheduleId = 1 }, new Schedule() { ScheduleId = 2, ScheduleName = "party2" } };
}
set
{
Schedules = value;
}
}
}
The SelectCustomer property is a property on the GuestResponse class. All the dropdowns are bound and if I change a few they bind nicely to the int[] SelectedCustomer collection. However I want to return my View back (so it does nothing essentially) but this resets all the dropdowns to their original state as the response was never fully bound because there was multiple dropdowns and MVC couldn't model bind to it. What it the best way of doing this so it maintains state so to speak?
The correct way to handle this is to use a view model instead of passing your domain models to the view.
But if you don't want to follow good practices you could generate your dropdowns like this as a workaround:
for (int i = 0; i < Model.Schedules.Count; i++)
{
#Html.DropDownList(
"MySelectedCustomer[" + i + "]",
new SelectList(
Model.Schedules[i].Customers,
"Id",
"FirstName",
Request["MySelectedCustomer[" + i + "]"]
),
"Select A Customer",
new { #class = "SelectedCustomer" }
)
}
The correct way is to have a property of type int[] SelectedCustomers on your view model and use the strongly typed version of the DropDownListFor helper:
for (int i = 0; i < Model.Schedules.Count; i++)
{
#Html.DropDownListFor(
x => x.SelectedCustomers,
Model.Schedules[i].AvailableCustomers,
"Select A Customer",
new { #class = "SelectedCustomer" }
)
}
and your POST controller action will obviously take the view model you defined as parameter:
[HttpPost]
public ViewResult ResponseForm(GuestResponseViewModel model)
{
// The model.SelectedCustomers collection will contain the ids of the selected
// customers in the dropdowns
return View(model);
}
And since you mentioned the Redirect-After-Post design pattern, this is indeed the correct pattern to be used. In case of success you should redirect to a GET action:
[HttpPost]
public ViewResult ResponseForm(GuestResponseViewModel model)
{
if (!ModelState.IsValid)
{
// the model is invalid => redisplay the view so that the user can fix
// the errors
return View(model);
}
// at this stage the model is valid => you could update your database with the selected
// values and redirect to some other controller action which in turn will fetch the values
// from the database and correctly rebind the model
GuestResponse domainModel = Mapper.Map<GuestResponseViewModel, GuestResponse>(model);
repository.Update(domainModel);
return RedirectToAction("Index");
}
Note: I'm first addressing why it's not binding anything, but that's not addressing the array issue, which I will get to afterwards. Where most people go wrong with MVC is that they do not take advantage of the built-in features of MVC to deal with these situations. They insist on doing foreach's and manually rendering things, but do not take into account the collection status.
The reason why the values are reset is because you are using Html.DropDownList() rather than Html.DropDownListFor(), and you are renaming the posted property name to a different name than your model property name.
You could simply change it to this:
#Html.DropDownList("SelectedCustomer", // note the removal of "My"
new SelectList(sched.Customers, "Id", "FirstName"),
"Select A Customer", new { #class = "SelectedCustomer" })
However, you would not have had this issue, and saved yourself a huge headache if you had just used the strongly typed version.
#Html.DropDownListFor(x => x.SelectedCustomer,
new SelectList(sched.Customers, "Id", "FirstName"),
"Select A Customer", new { #class = "SelectedCustomer" })
As for the Array, you should use an EditorTemplate for Schedules, and in that EditorTemplate you simply create your html as if it were a single item. That's the great thing about Editor/DisplayTemplates is that they automatically deal with collections.
Create a folder in your Views/Controller folder called EditorTemplates. In that folder, create an empty file called Schedule.cshtml (assuming Schedules is a List or array of Schedule). In that, you have code to render a single schedule.
EDIT:
Darin brings up a good point. I would make a small change to the model and add a Selected property to both Schedule and GuestResponse, then you can use Linq to return the selected schedule and it would simplify things.
EDIT2:
You some conflicts between the problem you've described and the code you've shown. I suggest you figure out exactly what you're trying to do, since your code does not really reflect a viable model for this.

Validation ASP.NET MVC Child Elements

I'm currently working on an ASP.NET MVC 3 application. I'm building a screen with out-of-the-box validation. Allow me to clarify the situation using the following screenshot.
Above you see a collection of TemplateItems. The second column 'Uitvoerder' is required. This works fine in most cases.
The problem however, is that it's not a regular list of items, but that it's structured to represent the hierarchy shown in the UI. Hence the second item is a child of the first, and thus contained in the first TemplateItem object you see.
Validation does not fire for the contained items.
You could argue that the front end model should be flattened and made less complex, but I'd like to avoid that. Is there any way I can have the validation trigger for the child elements as well?
The model looks like this:
public class WorkflowTemplateItemModel
: IValidatableObject
{
public WorkflowTemplateItemModel[] ChildWorkflowTemplateItems { get; set; }
public long? ExecutionParticipantId { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (ExecutionParticipantId == null)
{
yield return new ValidationResult("Contact needs to be specified",new[] {"ExecutionParticipantId"});
}
}
}
The relevant Razor part:
<td>
#Html.DropDownListFor(model => model.ExecutionParticipantId,
Model.AvailableUsers.Select(user => new SelectListItem
{
Text = user.UserName,
Value = user.Id.ToString(),
Selected = (Model.ExecutionParticipantId == user.Id)
}),
string.Empty
)
</td>
and the razor which builds the tree view:
#for (int i = 0; i < Model.ChildWorkflowTemplateItems.Length; i++)
{
#Html.EditorFor(model => model.ChildWorkflowTemplateItems[i], new { Depth = Depth + 1, ParentId = Model.WorkflowItemId, RootModel = GetViewData<CreateWorkflowModel> ("RootModel") })
}
You can do that but you will need to create a custom validation including the client-side validation code.
The process is similar to this one: http://haacked.com/archive/2009/11/18/aspnetmvc2-custom-validation.aspx

read implicit return type in Razor MVC View

I'm kind of new to razor MVC, and I'm wondering how can I read the values I return in the view?
My code is like this:
public ActionResult Subject(int Category)
{
var db = new KnowledgeDBEntities();
var category = db.categories.Single(c => c.category_id == Category).name;
var items = from i in db.category_items
where i.category_id == Category
select new { ID = i.category_id, Name = i.name };
var entries = from e in db.item_entry
where items.Any(item => item.ID == e.category_item_id)
select new { ID = e.category_item_id, e.title };
db.Dispose();
var model = new { Name = category, Items = items, Entries = entries };
return View(model);
}
Basically, I return an anonymous type, what code do I have to write to read the values of the anonymous type in my view?
And if this is not possible, what would be the appropriate alternative?
Basically, I return an anonymous type
Nope. Ain't gonna work. Anonymous types are emitted as internal by the compiler and since ASP.NET compiles your views into separate assemblies at runtime they cannot access those anonymous types which live in the assembly that has defined them.
In a properly designed ASP.NET MVC application you work with view models. So you start by defining some:
public class MyViewModel
{
public string CategoryName { get; set; }
public IEnumerable<ItemViewModel> Items { get; set; }
public IEnumerable<EntryViewModel> Entries { get; set; }
}
public class ItemViewModel
{
public int ID { get; set; }
public string Name { get; set; }
}
public class EntryViewModel
{
public int ID { get; set; }
public string Title { get; set; }
}
and then you adapt your controller action to pass this view model to the view:
public ActionResult Subject(int Category)
{
using (var db = new KnowledgeDBEntities())
{
var category = db.categories.Single(c => c.category_id == Category).name;
var items =
from i in db.category_items
where i.category_id == Category
select new ItemViewModel
{
ID = i.category_id,
Name = i.name
};
var entries =
from e in db.item_entry
where items.Any(item => item.ID == e.category_item_id)
select new EntryViewModel
{
ID = e.category_item_id,
Title = e.title
};
var model = new MyViewModel
{
CategoryName = category,
Items = items.ToList(), // be eager
Entries = entries.ToList() // be eager
};
return View(model);
}
}
and finally you strongly type your view to the view model you have defined:
#model MyViewModel
#Model.Name
<h2>Items:</h2>
#foreach (var item in Model.Items)
{
<div>#item.Name</div>
}
<h2>Entries:</h2>
#foreach (var entry in Model.Entries)
{
<div>#entry.Title</div>
}
By the way to ease the mapping between your domain models and view models I would recommend you checking out AutoMapper.
Oh, and since writing foreach loops in a view is kinda ugly and not reusable I would recommend you using display/editor templates which would basically make you view look like this:
#model MyViewModel
#Model.Name
<h2>Items:</h2>
#Html.DisplayFor(x => x.Items)
<h2>Entries:</h2>
#Html.DisplayFor(x => x.Entries)
and then you would define the respective display templates which will be automatically rendered for each element of the respective collections:
~/Views/Shared/DisplayTemplates/ItemViewModel:
#model ItemViewModel
<div>#item.Name</div>
and ~/Views/Shared/DisplayTemplates/EntryViewModel:
#model EntryViewModel
<div>#item.Title</div>

Resources