I have a model in my ASP.NET MVC application:
public class SearchArrayModel
{
public long ID { get; set; }
public string Name { get; set; }
public struct AttribStruct
{
public string AttribName { get; set; }
public string[] AttribValues { get; set; }
}
public AttribStruct[] AttribStructTable { get; set; }
}
In controller I'm filling it by some data from WebAPI (filling process is okay), I created an array of SearchArrayModel because I have a lot of items to fill from webAPI (It's a webAPI from site similar to ebay), for example some phones with its names, and attributes which u normally see on the auction site).
SearchArrayModel[] src = new SearchArrayModel[x];
{
//filling the fields
}
And on the end of the ActionResult I have:
return View(src);
//Visual Studio tells me that it is a "SearchArrayModel[] src"
I have also View, where I want to get and display the data:
#model allegrotest.Models.SearchArrayModel
#using (Html.BeginForm())
{
<h2>#Model.AttribStructTable[1].AttribName</h2>
<h3>#Model.AttribStructTable[1].AttribValues[1]</h3>
//indexes of arrays are just for testing, if all will be good I will write a loop
}
But when I'm starting the app I have an error:
The model item passed into the dictionary is of type
'allegrotest.Models.SearchArrayModel[]', but this dictionary requires
a model item of type 'allegrotest.Models.SearchArrayModel
I know that this is a complex array and I don't know how to pass this array from Controller to View.
I tried to write in View:
#model allegrotest.Models.SearchArrayModel[]
but then I can't get into the fields inside the #Model
Starting from assumption that "filling process is okay" is wrong.
Note: I made this assumption because I see that you are not interested in Model[index] and I noticed in SearchArrayModel[x]; { } the ;.
SearchArrayModel src = new SearchArrayModel
{
AttribStructTable = new SearchArrayModel.AttribStruct[]
{
new SearchArrayModel.AttribStruct{AttribName = "0Nume", AttribValues = new []{"0one", "0two"}},
new SearchArrayModel.AttribStruct{AttribName = "1Nume", AttribValues = new []{"1one", "1two"}},
},
Name = "SearchArrayName"
};
Your View is okay and is working
#model allegrotest.Models.SearchArrayModel
#using (Html.BeginForm())
{
#foreach(var attribStruct in #Model.AttribStructTable)
{
<h2>#attribStruct.AttribName</h2>
#foreach(var attribValue in attribStruct.AttribValues)
{
<h3>#attribValues</h3>
}
}
}
Another solution will be to make the modelof View to be an IEnumerable and in the Action you can make return View(src.ToList());
Also I noticed, when I tested your code, that you have Model.AttribStructTable which is wrong because your Model is a collection and you have to specify which element from model you want to display Model[index] (not posible with IEnumerable), Model.First() or you can iterate through collection.
#model IEnumerable<allegrotest.Models.SearchArrayModel>
#using (Html.BeginForm())
{
#foreach(var attribStruct in #Model.First().AttribStructTable)
{
<h2>#attribStruct.AttribName</h2>
#foreach(var attribValue in attribStruct.AttribValues)
{
<h3>#attribValues</h3>
}
}
}
If you iterate through all items from Model will look like this
#model IEnumerable<allegrotest.Models.SearchArrayModel>
#using (Html.BeginForm())
{
#foreach(var searchArrayModel in Model)
{
#foreach(var attribStruct in #searchArrayModel)
{
<h2>#attribStruct.AttribName</h2>
#foreach(var attribValue in attribStruct.AttribValues)
{
<h3>#attribValues</h3>
}
}
}
}
#model allegrotest.Models.SearchArrayModel[]
which is an array. So you could try
#foreach (SearchArrayModel item in Model)
{
<h2>#item.AttribStructTable[1].AttribName</h2>
<h3>#item.AttribStructTable[1].AttribValues[1]</h3>
..
}
or
#for (int i = 0; i < Model.Length; ++i)
{
<h2>#Model[i].AttribStructTable[1].AttribName</h2>
<h3>#Model[i].AttribStructTable[1].AttribValues[1]</h3>
..
}
Related
I'm trying to pass a list of values from the controller to the view, but apparently I got this issue where the list cannot be passed. I already tried passing one value and it has no problem. But when I try to pass list, it show the following error -
The model item passed into the dictionary is of type
'System.Collections.Generic.List`1[vidly.Models.pelanggan]', but this
dictionary requires a model item of type 'vidly.models.pelanggan'.
I have this model -
public class pelanggan
{
public string Nama { get; set; }
}
The controller code -
// GET: Pelanggan
public ActionResult Index()
{
var name = new List<pelanggan> {
new pelanggan {Nama = "Paidi" },
new pelanggan {Nama = "Budi" }
};
return View(name);
}
This is my view file
#model vidly.Models.pelanggan
#{
ViewBag.Title = "index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Customer</h2>
#foreach(var Nama in Model.pelanggan)
{
<li>#Nama.Nama</li>
}
I already tried to create a ViewModel but it also showing same error. Can you point where the error is?
You are returning a List<T> from the controller. So, the model declared in your view should be able to receive a list and iterate over it.
Replace the model declaration in the view file with following -
#using vidly.Models;
#model IEnumerable<pelanggan>
Then you can iterate/loop over the model like -
#foreach(var p in Model) // p represents a "pelanggan" object in the list
{
<li>#p.Nama</li>
}
The problem is the missmatch of types between what you return return View(name); and what te view expects #model vidly.Models.pelanggan You could change to #model List<vidly.Models.pelanggan> but instead I'd say:
public class pelanggan
{
public List<string> Namas { get; set; }
}
Then
public ActionResult Index()
{
var model = new pelanggan {
name = new List<string> {
"Paidi",
"Budi"
}
};
return View(model);
}
And finally in your view
#foreach(var Nama in Model.Namas)
{
<li>#Nama</li>
}
I have a project to make an online shop between users (post a product, buy, etc.) using a database. In this project I have a view called "ShoppingCart":
#model IEnumerable<MyFirstProject.Models.Product>
#{
ViewBag.Title = "ShoppingCart";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Your Shopping Cart</h2>
#if (Model == null)
{
<div style="float:left">Your cart is empty.</div>
<div>
Total payment: 0
</div>
}
else
{
decimal tPrice = 0;
<div>
<table style="float:left">
#foreach (var product in Model)
{
tPrice = tPrice + product.Price;
{ Html.RenderPartial("ProductLine", product);}
}
</table>
</div>
<div>
Total payment: #tPrice
</div>
}
It receives a list of products which the user decided to buy and displays them (not the important part). I need to add a button which will send the list to an action result in the "ShoppingController":
[HttpPost]
public ActionResult ShoppingCart(List<Product> bought)
{
if (ModelState.IsValid)
{
foreach (var listP in bought.ToList())
{
foreach (var databaseP in db.Products.ToList())
{
if (listP.ProductID == databaseP.ProductID)
{
databaseP.State = 1;
db.SaveChanges();
break;
}
}
}
return RedirectToAction("Index");
}
else
{
return View(bought);
}
}
"State" indicates if the product was bought or not (0=not bought, 1=bought), db is the database
If you wan't to post any data from a view to an action method, you should keep that data in form elements and keep that in a form. Since you want to post a collection of items, You may use Editor Templates.
Let's start by creating a view model.
public class ShoppingCartViewModel
{
public decimal TotalPrice { set; get; }
public List<Product> CartItems { set; get; }
}
public class Product
{
public int Id { set; get; }
public string Name { set; get; }
}
Now in your GET action, you will create an object of the ShoppingCartViewModel, load the CartItems property and send to the view.
public ActionResult Index()
{
var cart = new ShoppingCartViewModel
{
CartItems = new List<Product>
{
new Product { Id = 1, Name = "Iphone" },
new Product { Id = 3, Name = "MacBookPro" }
},
TotalPrice = 3234.95
};
return View(cart);
}
Now i will create an EditorTemplate. To do that, Go to your ~/Views/YourControllerName folder, and Create a directory called EditorTemplates and add a view with name Product.cshtml
The name of the file should match with the name of the type.
Open this new view and add the below code.
#model YourNamespace.Product
<div>
<h4>#Model.Name</h4>
#Html.HiddenFor(s=>s.Id)
</div>
You can keep the display however you want. But the important thing is, We need to keep a form field for the productId. We are keeping that in a hidden field here.
Now let's go back to our main view. We need to make this view strongly typed to our ShoppingCartViewModel. We will use the EditorFor html helper method in this view to call our editor template
#model ReplaceYourNamespaceHere.ShoppingCartViewModel
#using (Html.BeginForm())
{
#Html.EditorFor(x => x.CartItems)
<p>Total : #Model.TotalPrice</p>
<input type="submit" />
}
And in your HttpPost action method, We will have a paramer of type ShoppingCartViewModel. When the form is submitted, MVC Model binder will map the posted form values to an object of ShoppingCartViewModel.
[HttpPost]
public ActionResult Index(ShoppingCartViewModel model)
{
foreach (var item in model.CartItems)
{
var productId = item.Id;
// to do : Use productId and do something
}
return RedirectToAction("OrderSucessful");
}
You can iterate through the CartItems collection and get the Id of the Products and do whatever you want.
If you wan't to allow the user to edit the items (using a check box) in this page, Take a look at this answer. It is basically same, but you add a boolean property to Product class and use that for rendering a checkbox.
How would you add #Html.ValidationMessageFor() for each item in a collection? Say,
public class FooVm
{
// some property
public ICollection<BarVm> Bars { get; set; }
}
public class BarVm
{
// some property
[Range(1, int.Max, ErrorMessage = "Must be greater than 1")
public float? Fox { get; set; }
}
Then in a view
#model namespace.here.FooVm
<div class="container"></div>
Populate
<script>
$(function() {
var i = 0;
var populate = function() {
var strBuilder = '<input type="text" name="Bars[i].Fox" />';
$(".container").append(strBuilder);
return false;
};
$(".trigger").click(populate);
});
</script>
It's all working. But how can I add the validation in every textbox? I'm using ASP.NET MVC 4 still practicing. I'm also utilizing unobtrusive validation for client validation. Any you-should-do-something-like-this suggestions or tips, sample code would be great. Thanks.
Actually, using Javascript to populate a View is not the way MVC should be used. Instead, you can render all textboxes like this:
First the code for the class:
public class FooVm
{
// some property
public List<BarVm> Bars { get; set; }
public FooVm()
{
// Make sure the collection exists to prevent NullReferenceException
this.Bars = new List<BarVm>();
}
}
public class BarVm
{
// some property
[Range( 1, Int32.MaxValue, ErrorMessage = "Must be greater than 1" )]
public float? Fox { get; set; }
}
Now the code for the View:
#model WebApplication2.Models.FooVm
<h2>Sample View</h2>
#using ( Html.BeginForm( "YourAction", "YourController" ) )
{
<div class="container">
#for ( int i = 0; i < Model.Bars.Count; i++ )
{
#Html.TextBoxFor( m => m.Bars[i].Fox )
#Html.ValidationMessageFor( m => m.Bars[i].Fox );
}
</div>
}
This will render the necessary tags - and of course the validationmessage-bits. However, it's also possible to combine all error messages in one place by using
#Html.ValidationSummary()
If you really want to display the stuff only after clicking a button, consider using a partial view and loading that one. That's a much better approach than trying to create all necessary tags and attributes for validation using javascript.
Regards,
Frank
I've been struggling to research an answer to this question as I cannot come up with the correct search terms.
Basically I have 2 IEnumerable<T>'s in my controller, below is the code for the attempt I made.
Controller:
IEnumerable<Room> allRooms = RoomHelper.FindAllRooms();
foreach (var room in allRooms)
{
IEnumerable<Bunk> associatedBunks = RoomHelper.FindAssociatedBunksByRoom(room);
if (associatedBunks.Count() > 0)
{
ViewData["Room_"+room.RoomId] = associatedBunks;
}
}
And I'm trying to send them to the view in a way that I can do two foreach loops that will cycle through one set of data (in this case the Room objects and will then using the Room.RoomId key cycle through another IEnumerable which contains the associated data.
My view looks like this but is showing parse errors:
#foreach (var room in ViewBag.Rooms)
{
<h2>#room.RoomName</h2>
#if (ViewData["Room_" + room.RoomId].Count() > 0)
{
<ol>
#foreach (var bunk in ViewData["Room_" + room.RoomId])
{
<li>#bunk.BunkId</li>
}
</ol>
}
}
The end result I'm looking for in the HTML is something like:
<h2>Room 1</h2>
<ol>
<li>Bunk 1</li>
<li>Bunk 2</li>
</ol>
<h2>Room 2</h2>
<ol>
<li>Bunk 3</li>
<li>Bunk 4</li>
</ol>
What is the best practice in ASP.NET MVC 4 with EF5 to achieve this kind of result when passing "multidimensional" (is this multidimensional?) data?
Don't rely on ViewData. Store the data that you want to pass on to your view in a proper ViewModel:
public class RoomViewModel
{
List<Room> Rooms { get; set;}
...
}
Store your data in one of those.
Your Controller method then returns an instance of it:
public RoomViewModel GetRooms(int someParameter)
{
RoomViewModel result = new RoomViewModel();
result.Rooms = RoomHelper.Something(someParameter);
...
return result;
}
Your View declares its model on top:
#model MyApplication.ViewModels.RoomViewModel
and hence you use it in your View.
<h2>#Model.Rooms.Count rooms found</h2>
etc.
Use a code block in your view instead of adding an '#' in front of each C# statement:
#{
foreach (var room in ViewBag.Rooms)
{
#Html.Raw("<h2>" + room.RoomName + "</h2>");
if (ViewData["Room_" + room.RoomId].Count() > 0)
{
#Html.Raw("<ol>");
foreach (var bunk in ViewData["Room_" + room.RoomId])
{
#Html.Raw("<li>" + bunk.BunkId + "</li>");
}
#Html.Raw("</ol>");
}
}
}
You should avoid the use of #HtmlRaw("") as far as possible as it has a XSS vulnerability. But this should put you on the right track.
As per description given by you it seems that Bunk is associated with rooms. If that's the case then Bunk may have some id for pointing to room it belongs. Now you can create a ViewModel like this
public class BunkViewModel:Bunk
{
public BunkViewModel()
{
Mapper.CreateMap<Bunk,BunkViewModel>();
}
//I'm assuming that you already have some id in bunk to point to room it belongs.
//But writing it here to make it clear
public int RoomId { get; set; }
public string RoomName { get; set; }
//Use AutoMapper to Map
public static BunkViewModel Map(Bunk source)
{
return Mapper.Map<Bunk,BunkViewModel>(source);
}
}
Now in your controller
public ActionResult ActionName()
{
var result = new List<BunkViewModel>();
var rooms = RoomHelper.FindAllRooms();
var bunks = BunkHelper.GetAllBunks();
foreach(var bunk in bunks)
{
var bunkViewModel = BunkViewModel.Map(bunk);
var room = rooms.FirstorDefault(r=>room.RoomId == bunk.RoomId);
bunkViewModel.RoomId = room.RoomId; //No need to set if you already have this id in bunk
bunkViewModel.RoomName = room.RoomName;
result.Add(bunkViewModel);
}
return View(result.);
}
Now in your view you can do like this
#model List<MyApplication.ViewModels.RoomViewModel>
#foreach(var bunk in Model)
{
//Print it here as you want..
}
I have populated a dropdown list with values from Database Table. The list gets populated with correct table data but all values have ZERO index in the list. Here is the code to fill dropdown list:
//Get
public ActionResult NewBooking()
{
var db = new VirtualTicketsDBEntities2();
IEnumerable<SelectListItem> items = db.Attractions
.ToList()
.Select(c => new SelectListItem
{
Value = c.A_ID.ToString(),
Text = c.Name
});
ViewBag.Attractions = items;
return View();
}
And on Dropdown View Page:
<div class="editor-label">
#Html.LabelFor(model => model.Attraction)
</div>
<div class="editor-field">
#Html.DropDownList("Attractions")
</div>
For example if table have 3 values A,B, and C. These values are appearing in dropdown list but when I get its selected index in POST request function, it always returns ZERO. Here is the POST submit function:
//Post
[HttpPost]
public ActionResult NewBooking(BookingView booking)
{
try
{
BookingManager bookingManagerObj = new BookingManager();
bookingManagerObj.Add(booking);
ViewBag.BookingSavedSucess = "Booking saved!";
return View("WelcomeConsumer","Home");
}
catch
{
return View(booking);
}
}
booking.Attraction is always ZERO even user selected greater than ZERO index item.
Any suggestions?
I would guess that it is because you are getting a collection of SelectListItems back and not an actual SelectList. Try something like:
<div class="editor-field">
#Html.DropDownListFor(model => model.Attraction, new SelectList(ViewBag.Attractions, "Value", "Text");
It's best not to use ViewBag, you should always use a ViewModel.
Say you have a ViewModel like this:
public class AttractionViewModel
{
public int AttractionId { get; set; }
public SelectList Attractions { get; set; }
}
and modify your view like this - I presume you already have a form in there, the relevant bit is the #Html.DropDownListFor(...) and making sure you have the full namespace to the ViewModel if you haven't already included it in the Views web.config file:
#model AttractionViewModel
#using(Html.BeginForm("NewBooking", "ControllerName"))
{
<div class="editor-label">
#Html.LabelFor(model => model.AttractionId)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.AttractionId, Model.Attractions)
</div>
<input type="submit" value="Submit">
}
and modify your HttpGet like this:
//Get
public ActionResult NewBooking()
{
var db = new VirtualTicketsDBEntities2();
var items = db.Attractions.ToList();
var attractionIdDefault = 0;// default value if you have one
var vm = new AttractionViewModel {
AttractionId = attractionIdDefault,// set this if you have a default value
Attractions = new SelectList(items, "A_ID", "Name", attractionIdDefault)
}
return View(vm);
}
and create an HttpPost ActionResult like this:
// Post
public ActionResult NewBooking(AttractionViewModel vm)
{
var attractionId = vm.AttractionId; // You have passed back your selected attraction Id.
return View();
}
Then it should work.
I know that you have already selected your answer but here is an alternative way of doing what you did. When I started off with ASP.NET MVC I struggled with SelectListItem and found another way of populating my drop down list. I have stuck to this way ever since.
I always have a view model that I bind to my view. I never send through a domain model, always a view model. A view model is just a scaled down version of your domain model and can contain data from multiple domain models.
I have made some modifications to your code and tips, but like I mentioned, it's just an alternative to what you already have.
Your domain model could look like this. Try and give your property names some meaningful descriptions:
public class Attraction
{
public int Id { get; set; }
public string Name { get; set; }
}
You view model could look something like this:
public class BookingViewModel
{
public int AttractionId { get; set; }
public IEnumerable<Attraction> Attractions { get; set; }
// Add your other properties here
}
Do not have your data access methods in your controllers, rather have a service layer or repository expose this functionality:
public class BookingController : Controller
{
private readonly IAttractionRepository attractionRepository;
public BookingController(IAttractionRepository attractionRepository)
{
this.attractionRepository = attractionRepository;
}
public ActionResult NewBooking()
{
BookingViewModel viewModel = new BookingViewModel
{
Attractions = attractionRepository.GetAll()
};
return View(viewModel);
}
[HttpPost]
public ActionResult NewBooking(BookingViewModel viewModel)
{
// Check for null viewModel
if (!ModelState.IsValid)
{
viewModel.Attractions = attractionRepository.GetAll();
return View(viewModel);
}
// Do whatever else you need to do here
}
}
And then your view will populate your drop down like this:
#model YourProject.ViewModels.Attractionss.BookingViewModel
#Html.DropDownListFor(
x => x.AttractionId,
new SelectList(Model.Attractions, "Id", "Name", Model.AttractionId),
"-- Select --"
)
#Html.ValidationMessageFor(x => x.AttractionId)
I hope this helps.