Pass tuple with two object collections to view? - c#

I want to pass two collections of objects. First is Post, second is Gallery. However I get error and I don't know how to fix this.
I've done this when passing two single objects and it is working fine, but now I need to pass two collections of those objects and it gives me error.
Error
The model item passed into the dictionary is of type
'System.Tuple2[System.Linq.IQueryable1[photoBlog.Models.Gallery],System.Linq.IQueryable1[photoBlog.Models.Post]]',
but this dictionary requires a model item of type
'System.Tuple2[System.Collections.Generic.IEnumerable1[photoBlog.Models.Gallery],System.Collections.Generic.IEnumerable1[photoBlog.Models.Post]]'.
Controller
public ActionResult Index()
{
photoBlogModelDataContext _db = new photoBlogModelDataContext();
var posts = _db.Posts.OrderByDescending(x => x.DateTime).Take(4);
var galleries = _db.Galleries.OrderByDescending(x => x.ID).Take(4);
return View(Tuple.Create(galleries, posts));
}
View
#model Tuple<IEnumerable<photoBlog.Models.Gallery>, IEnumerable<photoBlog.Models.Post>>
#foreach (var item in Model.Item1)
{
#item.Name
}

I think you should modify your controller method to this:
public ActionResult Index()
{
photoBlogModelDataContext _db = new photoBlogModelDataContext();
var posts = _db.Posts.OrderByDescending(x => x.DateTime).Take(4).ToArray();
var galleries = _db.Galleries.OrderByDescending(x => x.ID).Take(4).ToArray();
return View(Tuple.Create(galleries, posts));
}
From your error message, it appears that the queries are not resolved yet when your view is rendered. By also doing ToArray or ToList, you will force the query to hit the database before you return from the controller method.

You need to create a new Entity in your domain.Model
Here is an example I provided today :
MVC3: button to send both form (model) values and an extra parameter
Later Edit :
namespace App.Domain.Model
{
public class Tuple
{
public IEnumerable<photoBlog.Models.Gallery> Gallery{ get; set; }
public IEnumerable<photoBlog.Models.Post> Post{ get; set; }
}
}
you will send the Tuple object to your view as follows :
#model: (Domain.Model.)Tuple <--- (your exact model path )
#foreach (var item in Model.Gallery)
{
#item.Name
}
Another posibility would be using viewbag ... but i strongly suggest you do it the correct way,i.e. the MVC way

Just try this
var posts = _db.Posts.OrderByDescending(x => x.DateTime).Take(4).ToList();
var galleries = _db.Galleries.OrderByDescending(x => x.ID).Take(4).ToList();

Related

In Umbraco, how can I bind a generic list of Objects to my View?

Something strange is happening in my umbraco project where I have a repository set up like so;
public class HireItemsRepo:BaseGenericRepository<YouHireItContext,HireItem>
{
public List<HireItemViewModel> PopulateHireItemViewModel(RenderModel model)
{ List<HireItemViewModel> HireItems = new List<HireItemViewModel>();
foreach (var Hireitem in base.GetAll())
{
HireItems.Add(
new HireItemViewModel(model.Content)
{
Title = Hireitem.Title,
Price = Hireitem.Price
}
);
}
return HireItems;
}
}
which I'm using in my controller like this
public class HiresController : RenderMvcController
{
// GET: Hire
public override ActionResult Index(RenderModel model)
{
HireItemsRepo repo = new HireItemsRepo();
var VM = repo.PopulateHireItemViewModel(model);
return View("Hires",VM.ToList());
}
}
And using that model in the view like this;
#model List<You_Hire_It.Models.HireItemViewModel>
/*HTML starts here*/
It's strange because if I try to use that model as a List, Umbraco will blow up with the following error;
Cannot bind source type System.Collections.Generic.List`1[[You_Hire_It.Models.HireItemViewModel, You_Hire_It, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] to model type Umbraco.Web.Models.RenderModel.
However, if I refactor all the code to use the model on it's own as if I only have one set of values to use, it has no problem with it!
Could anybody point me in the right direction with this please?
Many thanks in advance!
You can inherit from RenderModel as DZL suggests. However, I generally prefer to use route hijacking which would enable me to keep my models simple.
Instead of the Index method in your RenderMvcController, you can create a method with the same name as your view. I note your view is called Hires. So change your controller code to this:
public class HiresController : RenderMvcController
{
// GET: Hire
public ActionResult Hires(RenderModel model)
{
HireItemsRepo repo = new HireItemsRepo();
var VM = repo.PopulateHireItemViewModel(model);
return CurrentTemplate(VM)
}
}
You now need to have your view inherit from UmbracoViewPage. So at the top of your view replace the #model line with the following:
#inherits UmbracoViewPage<List<HireItemViewModel>>
Your model in the view is now of type List<HireItemViewModel> which I think is what you want.
So to iterate the items you would use:
#foreach(var item in Model){
{
// etc
}
Additionally, as this view now inherits from UmbracoViewPage, you have access to the UmbracoContext - just use #Umbraco
For example:
#Umbraco.TypedContentAtRoot().Where(x=>x.DocumentTypeAlias == "HomePage")
or
#Umbraco.AssignedContentItem etc
That is because the model you return from the action need to be of type RenderModel or inherit from it and in your case you are returning a List.
So your model should look something like this:
public class ViewModel : RenderModel
{
public ViewModel(IPublishedContent content) : base(content) { }
public List<HireItem> HireItems { get; set; }
}
public override ActionResult Index(RenderModel model)
{
var vm = new ViewModel(model);
vm.HireItems = new HireItemsRepo().GetHireItems();
return View("Hires", vm);
}

ASP.NET MVC Razor View built on Viewmodel of two objects. Architecture advice How to update a calculated field referencing both objects

I working in an ASP.NET MVC5 project and have a viewmodel class called PlacementStudentIndexData which has four collections of Ienumerables,
The viewmodel class is declared as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Placementv2.Models;
using IdentitySample.Models;
namespace Placementv2.ViewModels
{
public class PlacementStudentIndexData
{
public IEnumerable<ApplicationUser> UserChoice1 { get; set; }
public IEnumerable<ApplicationUser> UserChoice2 { get; set; }
public IEnumerable<ApplicationUser> UserChoice3 { get; set; }
public IEnumerable<Placement> Placement { get; set; }
public double distance { get; set; }
}
Note the ApplicationUser is uses the user objects on the objects in Identity2.0.
The Placements Controller has a Details Action to pass ViewModel data to a razor view built on this ViewModel which performs a matching exercise between the two objects and returns three matched collections of Users and an collection with a single instance of Placement type based on the int value passed to the Action to indicate which Placement object to return)
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var viewModel = new PlacementStudentIndexData();
viewModel.Placement = db.Placements.Where(p => p.PlacementID== id.Value);
viewModel.UserChoice1 = db.Users.Where(p => p.Choice1 == viewModel.Placement.FirstOrDefault().PlacementOrganisation.PlacementTypeID).Where(p => p.Placed == false);
viewModel.UserChoice2 = db.Users.Where(p => p.Choice2 == viewModel.Placement.FirstOrDefault().PlacementOrganisation.PlacementTypeID).Where(p => p.Placed == false);
viewModel.UserChoice3 = db.Users.Where(p => p.Choice3 == viewModel.Placement.FirstOrDefault().PlacementOrganisation.PlacementTypeID).Where(p => p.Placed == false);
return View(viewModel);
Elsewhere in my Placement controller I have the following method and am looking for advice on how to update this distance into the individual user instances as calculated against the current placement instance. The calculation should be based on passing geocoordinates which are already stored in the database for the User object and the PlacementOrganisation (which is associated to the Placement)
public double CalculateDistance(double startlat, double startlong, double endlat, double endlong)
{
[Google MAPS API calculation and JSON deserialisation code here]
return distancekms;
}
My difficulty is where in my code to perform these calculations and I'm looking for advice on how to update the distance attribute for the existing three UserChoice1,UserChoice2, UserChoice3 collections (As a side note, I know these are ienumerables, is it wrong to describe them as collections?)
If so could someone give me an example of how I would extend out my Details controller action to include a call to this CalculateDistance method so that the updated distance attribute for each User instance is part of the ViewModel passed to the Razor View.
Note- I've already tried include a foreach block in the controller method which called the CalculateDistance method along the following lines
foreach (var item in viewModel.UserChoice1)
{
viewModel.UserChoice1.First().distance = Calculatedistance(viewModel.Placement.First().PlacementOrganisation.Latitude, viewModel.Placement.First().PlacementOrganisation.Longtitude, viewModel.UserChoice1.First().Latitude, viewModel.UserChoice1.First().Longtitude);
}
However, it's throwing the following exception:
"There is already an open DataReader associated with this Command which must be closed first."
Please let me know if you can see a way to achieve what I'm trying to do here within the Details action of my Placement controller. Alternatively, should I be looking to call this CalculateDistance method from the view as I iterate through the User objects, or performing the calculation via Javascript in the View itself.
Thanks in advance for any help/advice you can offer on this.
The Link that #Dandy provided did contain the solution in one of the lower voted answers on that link.
The solution is to append .ToList() to the Ienumerables and that seems to close the DataReader properly.
This eventually got me past the issue where my foreach loop was throwing an error
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var viewModel = new PlacementStudentIndexData();
viewModel.Placement = db.Placements.Where(p => p.PlacementID == id.Value).ToList();
viewModel.User = db.Users.Where(p => p.Placed == false).OrderBy(p=>p.distance).ToList();
foreach (ApplicationUser user in viewModel.User)
{
user.distance = Calculatedistance(user.Latitude,user.Longtitude, viewModel.Placement.FirstOrDefault().PlacementOrganisation.Latitude, viewModel.Placement.FirstOrDefault().PlacementOrganisation.Longtitude);
}
return View(viewModel);
}

Loop over ModelView in razor

I have created razor forms that do a #foreach to loop over the model and produce my rows. Now I am trying to to the same thing over a ViewModel, and I get the following error.
Unable to create a constant value of type 'MyProject.Models.SupportContact'.
Only primitive types or enumeration types are supported in this context.
My #model is being passed in as IQueryable(MyProject.Models.SupportContactFormView), and SupportContact is part of that ViewModel, but I can't seem to reference it in the model.
I can do this:
#foreach (var item in Model ) {
#Html.DisplayFor(modelItem => item.SupportContact.Contact)
But then I will get the error above when it actually try to process my cshtml. I have tried to do:
#foreach (MyProject.Models.SupportContact item in Model ) {}
But I can't drill down thru the model to get the SupportContacts. How can I get to a my SupportContact class inside my ViewModel?
Thanks
Edit:
Here is the ViewModel declaration:
public class SupportContactFormView
{
public SupportContact SupportContact { get; private set; }
public SelectList Priorities { get; private set; }
public SelectList ContactTypes { get; private set; }
public String Group_COde { get; private set; }
}
In your case, I would create a viewmodel specifically for that view and set it up like so
ViewModel Class
public class IndexViewModel
{
public IList<SupportContact > SupportContacts { get; set; }
}
Controller
public ActionResult Index()
{
var viewModel = new IndexViewModel();
viewModel.SupportContacts = IQueryableListOfContacts.ToList();
return View(viewModel)
}
View
#model IndexViewModel
#foreach (var contact in Model.SupportContacts) {
#Html.DisplayFor(_ => contact.Contact)
You need to play around a bit with the code, as I doubt I have the property names/action method name correct first time, but I hope you get the basic idea. Create a viewmodel class that the view file makes use of, and dump the contact details into a List. That will make it easier for you.
EDIT:
Actually, I think I got the wrong end of the stick here. The error "Unable to create a constant value of type 'MyProject.Models.SupportContact'.
Only primitive types or enumeration types are supported in this context." means that there is something wrong with your LINQ data.
Are you using Entity Framework or Linq2SQL? Ignore my answer about the viewmodel appraoch, your actual issue is LINQ related. Try iterating over the list of contacts in an ActionMethod instead e.g.
public ActionMethod Index()
{
foreach (var contact in whateveryourcontactslistis) {
}
// ....
}
If you still have a problem here, then it's your LINQ data at fault.

Convert Model to ViewModel without automapper

Hi this is my convert a Ilist of model to a Ilist of ViewModel method
public static IList<PostViewModel> ConvertToPostViewModelList(this IList<Post> posts)
{
return posts.Select(ConvertToPostViewModel).ToList();
}
and also this is the ConvertToPostViewModel
public static PostViewModel ConvertToPostViewModel(this Post post)
{
var blogPostViewModel = new PostViewModel
{
Id = post.Id,
Body = post.Body,
Summary = post.Summary,
Title = post.Title,
Category = post.Category,
CreationDate = post.CreationDate,
SelectedCategory = post.CategoryId,
SelectedTag = post.TagId,
Tag = post.Tag,
UrlSlug = post.UrlSlug
};
return blogPostViewModel;
}
what is the problem with this , I got this error View :
The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[Blog.Domain.Model.Post]', but this dictionary requires a model item of type 'System.Collections.Generic.IList`1[Blog.Web.UI.ViewModels.PostViewModel]'.
then ?? I convert the Ilist of Model to ViewModel via this :
return posts.Select(ConvertToPostViewModel).ToList();
then what is going on ??
what I have done in action
public ActionResult Posts()
{
var blogPost = _blogRepository.GetAllPost();
var blogPostViewModel = blogPost.ConvertToPostViewModelList();
return View("Posts", blogPostViewModel);
}
and in View
#model IList<Blog.Web.UI.ViewModels.PostViewModel>
Two possibilities:
The method you posted is not the one being matched. You can verify this easily by throwing an exception in your controller action and seeing if it gets thrown. This could be the result of either: (a) an overloaded Posts controller action, where the other is being matched; or, (b) a custom route that is intercepting the request.
You originally returned the domain object in testing, but after changing the controller action to set the model to the PostViewModel, you forgot to recompile your MVC project. Try recompiling your solution and see if the results change.

How do I transfer ViewModel data between POST requests in ASP.NET MVC?

I have a ViewModel like so:
public class ProductEditModel
{
public string Name { get; set; }
public int CategoryId { get; set; }
public SelectList Categories { get; set; }
public ProductEditModel()
{
var categories = Database.GetCategories(); // made-up method
Categories = new SelectList(categories, "Key", "Value");
}
}
Then I have two controller methods that uses this model:
public ActionResult Create()
{
var model = new ProductEditModel();
return View(model);
}
[HttpPost]
public ActionResult Create(ProductEditModel model)
{
if (ModelState.IsValid)
{
// convert the model to the actual entity
var product = Mapper.Map(model, new Product());
Database.Save(product);
return View("Success");
}
else
{
return View(model); // this is where it fails
}
}
The first time the user goes to the Create view, they are presented with a list of categories. However, if they fail validation, the View is sent back to them, except this time the Categories property is null. This is understandable because the ModelBinder does not persist Categories if it wasn't in the POST request. My question is, what's the best way of keeping Categories persisted? I can do something like this:
[HttpPost]
public ActionResult Create(ProductEditModel model)
{
if (ModelState.IsValid)
{
// convert the model to the actual entity
var product = Mapper.Map(model, new Product());
Database.Save(product);
return View("Success");
}
else
{
// manually populate Categories again if validation failed
model.Categories = new SelectList(categories, "Key", "Value");
return View(model); // this is where it fails
}
}
But this is an ugly solution. How else can I persist it? I can't use a hidden field because it's a collection.
I would use the repository to fetch whatever data is needed and don't think it's an ugly solution:
[HttpPost]
public ActionResult Create(ProductEditModel model)
{
if (!ModelState.IsValid)
{
// manually populate Categories again if validation failed
model.Categories = Repository.GetCategories();
return View(model);
}
// convert the model to the actual entity
var product = Mapper.Map(model, new Product());
Database.Save(product);
// I would recommend you to redirect here
return RedirectToAction("Success");
}
To further refactor this I would recommend you watching the excellent Putting Your Controllers on a Diet video presentation by Jimmy Bogard.
I typically implement my lists (for drop downs) as a readonly property. When the View gets the value the property is self contained on what it needs to return the values.
public SelectList Categories
{
get
{
var categories = Database.GetCategories(); // made-up method
return new SelectList(categories, "Key", "Value");
}
}
If necessary you can grab the currently selected item (i.e. validation failed) from the property containing the id that was posted and bound to the instance of your class.
In my case I have a BaseModel class where I keep all those property list as class attributes.
Like in the following sample:
public IEnumerable<SelectListItem> CountryList
{
get
{
return GetCountryList().Select(
t => new SelectListItem { Text = t.Name, Value = Convert.ToString(t.CountryID) });
}
}
GetCountryList() is a function that ask a Singleton for data. This would only happen once in the app lifecycle
Another way for doing this, and if those lists are pretty big, would be to have a static utility class with the lookup table that returns the SelectListItem.
If you need to access a list that change from time to time then simply dont use a Singleton class.

Categories

Resources