2 Different Models Need to Utilize Same View - asp.net

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.

Related

How can I create more than one Model in a view? Preferably using Partial Views and a dynamic number of models

I have list of "skills" that I want a user to be able to go through, selecting appropriate levels from the list. So Skill looks something like this:
public class Skill
{
public String SkillName { get; set; }
public SkillLevel CurrentSkillLevel { get; set; }
public Boolean IsRequired { get; set; }
public Skill(String Name)
{
this.SkillName = Name;
this.IsRequired = true;
this.CurrentSkillLevel = SkillLevel.None;
}
}
There could be hundreds of skills - and the exact number is unknown at the moment, and certainly it needs to be dynamic, so I don't want to make the user go through the process of clicking on each skill, filling out the form, and submitting each time. I'd like to display all of the skills in the same page (one row each), allow the user to select all the values, then click submit once at the end.
Unfortunately, I've not had any experience with using MVC for anything other than singleton creation. I would normally use a separate ViewModel for several models bundled together, but I don't see how that would work with a dynamic list, and since they are all the same type ("Skill"), I guess the ViewModel would look exactly like a list of the model.
Does anyone have an appropriate solution?
Thanks
Building on DaveA's answer, you can use an editor template for Skill. You will need to add a folder named EditorTemplates to your Views\Controller folder. Now make a partial view in that folder named Skill.cshtml that looks something like this
#model Skill
#Html.TextBoxFor(s => s.SkillName)
#Html.TextBoxFor(s => s.Skills[i].Title)
// etc...
Now in your main view
using (Html.BeginForm("UpdateCart", "Orders"))
{
#Html.TextBoxFor(p=>p.Name)
#Html.TextBoxFor(p=>p.Title)
#Html.EditorFor(p => p.Skills)
}
Razor is smart enough that it will render each skill in Skills using the Skill.cshtml editor template.

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; }
}

Writing Useful Unit Tests

I have a simple page with a Grid that I'm binding a collection of objects to. I also have some simple functionality on the grid to edit and save rows. I would like to write unit tests for this page, but it's not really making sense to me.
For example:
Private Sub LoadGrid()
'Populate Collection
grid.datasource = MyCollection
grid.databind()
end sub
I guess a Sub really doesn't need a unit test, but what if this were a function that returned true when the grid had been loaded. How do you write a unit test for this? What other test should be done on a simple web page like this?
As always, thanks to everyone who give any sort of input.
How do you write a unit test for this?
The first step is actually making your form testable. Have a look at this page for separating UI and BL layers, there are about a bajillion different ways to implement MVC, MVP, and all of its variants, and there's no One True Way™ to do it. So long as your code is sane and consistent, other people will be able to work on your code.
I personally find the following pattern works in most cases for testing UIs:
Create an interface representing your Model.
Create a class for your controller which handles all the updates to the model.
Your view should listen to changes to the model.
So in the end, you end up with something like this (sorry, my VB-fu is rusty, writing this in C# instead):
interface IProductPageModel
{
int CurrentPage { get; set; }
int ItemsPerPage { get; set; }
DataSet ProductDataSet { get; set; }
}
class ProductPageController
{
public readonly IProductPageModel Model;
public ProductPageController(IProductPageModel model)
{
this.Model = model;
}
public void NavigateTo(int page)
{
if (page <= 0)
throw new ArgumentOutOfRangeException("page should be greater than 0");
Model.CurrentPage = page;
Model.ProductDataSet = // some call to retrieve next page of data
}
// ...
}
This is concept code, of course, but you can see how its very easy to unit test. In principle, you could re-use the same controller code in for desktop apps, silverlight, etc since your controller doesn't depend directly on any particular view implementation.
And finally on your form side, you'd implement your page similar to:
public class ProductPage : Page, IProductPageModel
{
ProductPageController controller;
public ProductPage()
{
controller = new ProductPageController(this);
}
public DataSet ProductDataSet
{
get { return (DataSet)myGrid.DataSource; }
set { myGrid.DataSource = value; myGrid.DataBind(); }
}
protected void NavigateButton_OnCommand(object sender, CommandEventArgs e)
{
controller.NavigateTo(Convert.ToInt32(e.CommandArgument));
}
}
Here there's no real distinction between view and model -- they're the same entity. The idea is to make your code-behind as "stupid" as possible, so that as much testable business logic is contained in the controller as possible.
What other test should be done on a
simple webpage like this?
You'd want tests for any sort of form validation, you want to make sure you're throwing exceptions in exceptional cases, ensuring that your controller methods update your model in an expected way, and so on.
Juliet is right.
The line of code where you said
'Populate Collection
that is the testable part. You can do assertions on if the collection is null, if it has items, if it has exactly 42 items. But that would be an integration test.
If you can isolate all the calls to the database (the part that returns a datareader), then return a empty, fake DbDataReader, then you can test everything inbetween the UI and the database.
Tests that spin up a browser and verify that a table is render, similarly is a integration test that will depend on having IIS up and working (and a DB up and working, unless you have a repository you can fake)
If you are just getting started, I would look for all the easy to test code first, such as methods that do have dependencies on the database, then move on the tricker tests that require mocking/stubbing/faking database servers, etc.

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

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.

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