ASP.NET MVC: How to transfer more than one object to View method? - asp.net

I finished NerdDinner tutorial and now I'm playing a bit with project.
Index page shows all upcoming dinners:
public ActionResult Index()
{
var dinners = dinnerRepository.FindUpComingDinners().ToList();
return View(dinners);
}
In DinnerRepository class I have method FindAllDinners and I would like to add to above Index method number of all dinners, something like this:
public ActionResult Index()
{
var dinners = dinnerRepository.FindUpComingDinners().ToList();
var numberOfAllDinners = dinnerRepository.FindAllDinners().Count();
return View(dinners, numberOfAllDinners);
}
Of course, this doesn't work. As I'm pretty new to OOP I would need help with this one.
Thanks,
Ile

Create view model:
public class DinnerViewModel
{
public List<Dinner> Dinners { get; set; }
public int NumberOfAllDinners { get; set; }
}
public ActionResult Index()
{
var dinners = dinnerRepository.FindUpComingDinners().ToList();
var numberOfAllDinners = dinnerRepository.FindAllDinners().Count();
return View(new DinnerViewModel { Dinners = dinners, NumberOfAllDinners = numberOfAllDinners } );
}

You need to create a "wrapper" object that contains the objects you wish to pass as public properties of it. For instance, create an object called DinnerViewModel and give it two properties and set these with two properties, one a List called Dinners and one an int called DinnerCount. Then pass the DinnerViewModel to the view and you can then access Model.Dinners and Model.DinnerCount

In your case I would prefer the solution mentioned by LukLed.
In general you could of course also transfer multiple values from your controller to your view using ViewData:
ViewData["dinners"] = dinners;
ViewData["numberOfAllDinners"] = 150;
...
For more information also take a look at this link.

Just simply use dinners.Count property instead.

Remember, you start off using the ViewData inherts in you .aspx filesand returning the same in you return statements. Because of that, I figure that it was an issue with the Inherits attribute on the top of the ASP.NET files. But, if you are getting the error when trying to create or edit a new Dinner when you are on the 'Upcoming Dinners' page (generated from the Details.aspx and the LINQ file that gets all Dinners that are after todays date), go into your 'Controllers' directory, specifically the DinnerController.cs. Then look at the Edit and or Create methods. the answer lies right here. If you put breakpoints on these methods, you should be able to figure it out. If not, continue reading:
Look where it fails, the 'return...' line. Maybe I am the only person who forgot to change this, but my error is the same as people are getting in this page and this os how I fixed it.....the 'return(dinner)' line, in Create and Edit (and any others that you are having issues with), they are using the NerDinner.Model.Dinner / ViewData method. However, if you change it to the ViewModel return method instead, it should fix it, For example: 'return(new DinnerFormViewModel(dinner));', it should work for you. I hope this helps, as it was what my issue was. Just a simple overlook.

Related

Too many select lists polluting my controller actions

I have an ASP.Net MVC application and some of the controller actions have about 10 select lists (others have more). Currently my code looks like this:
public ActionResult Edit(int carId)
{
CreateCar model = new CreateCar();
model.Makes = Helper.Makes();
model.Models = Helper.Models();
model.Colors = Helper.Colors();
model.EngineSizes = Helper.EngineSizes();
model.Materials = Helper.Materials();
model.FuelTypes = Helper.FuelTypes();
model.WheelSizes = Helper.WheelSizes();
model.BodyTypes = Helper.BodyTypes();
//more select lists below this
return View(model)
}
In my views i setup select lists like this:
#Html.DropDownListFor(x => x, Model.Makes)
I have code that looks like this in a number of actions and i feel there is a better way of doing this so my actions are not polluted with these select lists.
The only option i can think of to get around this is to actullly call the Helper class in the views e.g.
#Html.DropDownListFor(x => x, Helper.Makes())
Is this approach considered bad practice and are there any other approaches to deal wi this issue?
Generally, yes, it's bad practice to do things like that in your view. The controller is responsible for wiring everything up, so your code should go there. Now, it depends a lot on what your Helper class is doing. If the select lists are just generated via some bit of code, it's probably not that bad for that to happen in the view, but what you don't want to be doing is issuing database queries while the view is being rendered. If your helper is interacting with a database, then keep it in the controller.
That said, what is the real issue here? Sure that's a lot of select lists, but I wouldn't go so far as to say it's "polluting" your action. It's very clear what your doing. The action is responsible for creating the model for your view, and that's what it's doing. Just because there may be a lot of lines in your code, doesn't necessarily mean it's "bad" or "wrong".
However, if you're repeating this in a lot of places, I would recommend factoring it out into a private or protected method on your controller. For example:
public ActionResult Edit(int carId)
{
...
PopulateSelectLists(model);
return View(model);
}
[HttpPost]
public ActionResult Edit(Foo model, int carId)
{
...
PopulateSelectLists(model);
return View(model);
}
private void PopulateSelectLists(Foo model)
{
model.Makes = Helper.Makes();
model.Models = Helper.Models();
model.Colors = Helper.Colors();
model.EngineSizes = Helper.EngineSizes();
model.Materials = Helper.Materials();
model.FuelTypes = Helper.FuelTypes();
model.WheelSizes = Helper.WheelSizes();
model.BodyTypes = Helper.BodyTypes();
//more select lists below this
}
Then, everything is clean and tidy.

2 Different Models Need to Utilize Same View

Let me premise this by saying I am transitioning from Classic ASP to .net so my .net knowledge is all through books, not use. I am working on a shopping cart platform and have been tasked with creating 3 different display types (horizontal slider, vertical slider and grid) that will be able to be utilized by 2 different models.
I got the new views to work with my first model and thought I was in the clear and plugged in the second, but then I started getting an error about the models not being the same. I am trying to figure out what the best way to handle this. The solutions I have come up with are
Using A View Model - this seems like it is supposed to only be used when you need to combine 2 models into one and that is really the opposite of what I need, I need to make 1 view work for 2 models.
Make separate views for each type. This seems logical but is going to result in 5 new view files that are almost identical to those I have already created, it seemed redundant.
Use renderPartial and convert the models - not really sure how I would even accomplish this, but my though was to renderpartial out the creation of the actual displays (horizontal slider, vertical slider and grid view)
I am having a hard time determining what the best course of action is. I know that with MVC and .net you are supposed to never code the same thing twice and I think making the separate views is doing exactly that. Any thoughts on how to best approach this would be greatly appreciated.
This is the way I'm doing it right now on my project, I don't know if it's the best way or not but for me works fine.
Basically on my website's homepage I need to have to 2 different models to handle 2 forms posting to the same Method in the controller
namespace Website.Models.ViewModels
{
public class HomePageModels
{
public SearchFlyModel SearchFly { get; set; }
public CarRequestModel CarRequest { get; set; }
public int form { get; set; }
public HomePageModels()
{
SearchFly = new SearchFlyModel();
CarRequest = new CarRequestModel();
}
}
}
Then on the controller's method I need to know which form was posted to know which form I need to validate, because Asp.net automatically validate the model , it will ,by default validate both models in your ViewModel , and this affects the ModelState and when you check if your model is valid doing ModelState.isValid , this will return false because you only posted one form and not the other which is completely empty.
To manage that, I do the following
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(HomePageModels model)
{
ModelState.Clear();
if (model.form == 0)
{
try
{
ValidateModel(model.SearchFly);
return this.SearchFly(model.SearchFly);
}
catch (Exception e)
{
}
}
else
{
try
{
ValidateModel(model.CarRequest);
return this.SearchCar(model.CarRequest);
}
catch (Exception e)
{
}
}
var modelHomePage = new HomePageModels()
{
SearchFly = model.SearchFly,
CarRequest = model.CarRequest,
form=model.form
};
return View(modelHomePage);
}
Both forms are posting to the same action because I need to return the form's validations error so the user can fix it.

Entity Framework error - nested model problem

I'm new to ASP.NET MVC and want to create a small order management tool. My database contains the tables Orders and Articles (and a few other ones), and I generated an EF Model from my database, so I can use the full power of the EF mappings (e.g. db.Orders.Articles)
My two main relations which I'm concerned about are Orders and Articles.
An order can have many articles
An article can only belong to one order.
I've created an OrdersController with an Create action to create an order:
//
// GET: /Orders/Create
public ActionResult Create()
{
Order order = new Order()
{
// filling some order columns, e.g. date
};
Article article = new Article()
{
// ... article columns
};
order.Articles.Add(article);
return View(order);
}
//
// POST: /Orders/Create
[HttpPost]
public ActionResult Create(Order order)
{
// i know i should care more about error handling, but now ommit it
db.Orders.AddObject(order);
db.SaveChanges();
return RedirectToAction("index");
}
So I'm directly binding an EF Object to a view (read somewhere not to do that and use a view model instead, but don't really know what that view model should look like)
My view contains the Order form as well as the article form (because i want to create a order and articles at the same time and not seperate). I used these greate EditorFor Methods to do that.
And now to my problem: If i hit the submit button, the app crashes as soon as it comes to the HttpPost Create Method (when mapping the order) with this error message:
Error Message: The EntityCollection
has already been initialized. The
InitializeRelatedCollection method
should only be called to initialize a
new EntityCollection during
deserialization of an object graph.
If i hit continue in VS2010, it will complete saving the order - so my question is how to solve this problem in a reliable way.
Thanks in advance and sorry for that long story :)
I solved my problem now by using a separate ViewModel like #Yakimych advised me. However I did not copy all the attributes from the EF models, instead I just refer to them. My ViewModel looks like this:
public class NewOrderViewModel {
public Order { get; set; }
public List<Article> { get; set; }
}

Public variables in MVC 3 Razor _ViewStart

I'm building a site on the new Razor engine that comes with MVC 3 (and loving the new syntax!). However, I am at a loss about using public properties / constants with it. I know that with WebForms we could add a public property in code behind:
public string ImageFolder { get; set; }
I would like to define important variables in one global place that my views can access, starting with paths to CSS files and images:
#{
Layout = "~/Views/Shared/_Layout.cshtml";
var ContentFolder = "~/Content";
var CssFolder = ContentFolder + "/Stylesheets";
var ImageFolder = ContentFolder + "/Images";
}
I have tried putting the above code block in _Layout, as well as inside _ViewStart. However, accessing them from child views fails miserably. I thought of defining a public property in the above code block but it doesn't compile.
Solutions?
As far as I have seen, noone uses code behind with Razor.
I guess I should be able to inherit from the default view and define my properties there (as described on Stack).
But I'm strongly hoping that there should be an easier way to do something so simple?
I decided to follow yet another path, and extended UrlHelper to provide paths to all three folders I think I might need:
public static class ExtensionMethods
{
private const string ImagesFolder = "~/Images";
private const string StylesheetsFolder = "~/Stylesheets";
private const string ScriptsFolder = "~/Scripts";
public static string Images(this UrlHelper url)
{
return url.Content(ImagesFolder);
}
public static string Stylesheets(this UrlHelper url)
{
return url.Content(StylesheetsFolder);
}
public static string Scripts(this UrlHelper url)
{
return url.Content(ScriptsFolder);
}
}
All good to go... almost :-) I'm now wondering if there's a place where I would be able to define the using MyNamespace.Helper statement would go in order for these extension methods to be available application-wide. In the old days we would add an entry in web.config:
<system.web>
<pages>
<namespaces>
<add namespace="MyNamespace.Helper"/>
</namespaces>
</pages>
</system.web>
This doesn't seem to work with Razor :-( I tried adding a using statement in _ViewStart.cshtml but no luck either - the only way for my extension methods to be visible is to add a using statement on a particular page, which again isn't ideal.
Any suggestions? Have any of you seen an explanation of Razor's order of page parsing & delivery?
Your can create a folder "App_Code" and create a file "GlobalVal.cshtml".
bellow is a sample code in the file:
#functions{
public static readonly string __siteHome = "http://www.example.com";
public static readonly string __siteResource = "http://resource.example.com";
}
and bellow is a sample using it:
#GlobalVal.__siteHome
Use the PageData property:
#{
Layout = "~/Views/Shared/_Layout.cshtml";
PageData.Add("ContentFolder", "~/Content");
}
and inside _Layout.cshtml:
<%=PageData["ContentFolder"]%>
In _layout view
#{
App.AnyName = "abc";
}
In Inherit view
#{
var anyVariable = App.AnyName;
}
Just place the constants in a public module inside your app_code folder, or if you don't want to do that just create a clasas in app_code and use the using (imports) keyword to import the namespace (class name) in each view and you can use it that way.
Alternatively, if it makes sense to do so, just add them in your view model - remember, it might not make sense to add those vars to your model, but it can make sense to add them to your view model! This is what the view model is for, and this view model can grab the constant values from a public module or class or you can even set it in your actual view model itself, this way you will only define the values in one place and you don't need to use any namespace imports into each view :)
Let me know how it goes and if there is anything else I can do to help you out.
In vb.net but same as csharp and its easy to understand since it's vb.
Public class YourModel
// this is where you have the normal model you have... No big deal
End Class
...
// now you make the view model urself
...
Public class MyViewModel
Public MyNormalModel as YourModel
//notice we r declaring ur normal model as a variable, u can use a property instead
Public MyPathConstant1 as string = "abc"
Public MyPathConstant2 as string = "abc"
Public MyPathConstant3 as string = "abc"
End Class
Now, you gotta set the value of MyNormalModel to ur current model instance, although you can do that in ur controller, it's best practice to create a method inside the MyViewModel class that takes a copy of ur current model as argument and does the setting of MyNormalModel to the current model we just passed in the argument.
You can still make that call in your controller, but on another note, what people prefer to do is, instead of passing the whole normal model as a property, the just take the bits and pieces they need from the normal model and place them into the view (ie: you might just need half the properties in the normal model to be in the view model). This is because, remember, the view model will be passed to the view and they don't wanna pass things they wont use :). But this means you are going to need to set each of those properties one by one most likely (unless those exact ones are encapsulated in a sub class which usually doesn't happen by chance lol).
I kept it in one so you can just copy the normal model over in one shot for simplicity.
Now when you pass the view model to your view (MyViewModel) you will be able to use and access the normal model through the object notation and it's properties, eg... Model.MyNormalModel.Property1. Etc and do whatever you want with it in the view... Also, you can access the rest of your view model (the const values that we set) like this... Model.MyPathConstant1 and Model.MyPathConstant2 etc... So you have access to practically everything you want, ur normal model and whatever else you added later on all through what is now called your view model.
Please excuse typos -writing from and ipad lol. Let me know if this is making more sense.
You could use the built-in property of UrlHelper Content:
#Url.Content("~/Content/Stylsheets")
#Url.Content("~/Content/Images")

RenderView with MVC2

I am using MVC preview 2 framework to develop web sites and I am following MVCStorefront tutorials to get a good feel on MVC.
Can you tell me why I can't use RenderView() method ?
Am I missing something or can I use View() instead ?
What's the difference between these methods..
Thanks
Here is where Rob is using RenderView in his tutorial.
[TestMethod]
public void CatalogController_IndexMethod_ShouldReturn_Categories_And_Data_For_Parent1() {
CatalogController c = new CatalogController(_repository);
RenderViewResult result = (RenderViewResult)c.Index("Parent1", "Sub10");
CatalogController.CatalogData data = (CatalogController.CatalogData)result.ViewData;
Assert.IsNotNull(data.Category);
Assert.IsNotNull(data.SubCategory);
Assert.IsNotNull(data.SubCategory.Products);
Assert.IsTrue(data.SubCategory.Products.Count() > 0);
Assert.IsNotNull(result);
}
I can't use RenderView. It says " the name 'RenderView' does not exist in the current context
Here's the link :
http://www.asp.net/learn/mvc-videos/video-357.aspx
Here is an index method from the CatalogController class :
public ActionResult Index(string category, string subcategory) {
//instantiate the service
CatalogService svc = new CatalogService(_repository);
//the ViewData class
CatalogData data = new CatalogData();
//pull all the categories for the navigation
data.Categories = svc.GetCategories();
//pull the category based on subcategory name
data.Category = data.Categories.WithCategoryName(category);
//catch for bad data
if (data.Category == null) {
data.Category = data.Categories.DefaultCategory();
data.SubCategory = data.Category.SubCategories[0];
} else {
data.SubCategory = data.Categories.WithCategoryName(subcategory);
//catch for bad SubCategory
data.SubCategory= data.SubCategory ?? data.Category.SubCategories[0];
}
return RenderView("Index",data);
}
I am also having a problem with the casting of result.ViewData in CatalogData type which is class that contains data. It says : Cannot convert type System.Web.Mvc.ViewDataDictionary to Commerce.MVC.Web.Controllers.CatalogController.CatalogData
The video you are watching is unfortunately outdated - it is from ASP.NET MVC 1.0 Preview 2. Since then ASP.NET MVC 1.0 RTM has shipped and there are previews of ASP.NET MVC 2 available.
In ASP.NET MVC 1.0 Preview 2 and earlier action methods returned 'void' so they had to explicitly perform a result, such as render a view:
public void Index() {
// do some work...
RenderView("Index");
}
In ASP.NET MVC 1.0 Preview 3 (Refresh?) and newer, action methods return a result object, which then actually performs the result:
public ActionResult Index() {
// do some work...
return View("Index");
// or you could also just say "return View();" and MVC figures out the view name
}
The main reason this changed is that it allows for much better unit testing. Action methods now only perform the "application logic" and don't worry about exactly how to render a view. The unit test can simply inspect the results of the application logic and then verify that the next desired step was "render a view."
A lot of type names and method names have changed as well to make them shorter and simpler to use. For example, RenderView is just View and RenderViewResult is just RenderView.
My apologize if I resurrect a dead topic like this, but I was having the same issues as OP and I found my solution. Therefore I would reply here in case anybody who is following Rob's StoreFront series would find the solution also.
[TestMethod]
public void CatalogController_IndexMethod_ShouldReturn_Categories_And_Data_For_Parent1() {
CatalogController c = new CatalogController(_repository);
ViewResult result = c.Index("Parent1", "Sub10") as ViewResult;
CatalogController.CatalogData data = result.ViewData.Model as CatalogController.CatalogData;
Assert.IsNotNull(data.Category);
Assert.IsNotNull(data.SubCategory);
Assert.IsNotNull(data.SubCategory.Products);
Assert.IsTrue(data.SubCategory.Products.Count() > 0);
Assert.AreEqual("Parent1", data.Category.Name);
Assert.AreEqual("Sub10", data.SubCategory.Name);
Assert.IsNotNull(result);
}

Resources