I need to unit test the controllers method results with the following code
StoreController test = new MvcMusicStore.Controllers.StoreController();
ActionResult result = test.Index();
ViewResult p = (ViewResult)result;
result and p shows 10 items in the model when debugging but it seems that I can not do Count or foreach to check how many records are returned as both objects are dictionary.
How can this be done?
Thanks
ViewResult.Model is of type object you have to cast it to the collection type first to get a count.
For example, if you're passing the list of integers as a model to your view, you can get the count this way:
var model = p.Model;
var list = model as List<int>();
var count = list.Count;
Assuming you have a model of IEnumerable<DataType> and you want to get this model in a unit test, you have to cast the result.ViewData.Model to your type and then do your Assertions:
var test = new StoreController();
var result = test.Index() as ViewResult;
var data = (IEnumerable<ModelType>) result.ViewData.Model;
Assert.AreEqual(10, data.Count());
Reference: https://www.asp.net/mvc/overview/older-versions-1/unit-testing/creating-unit-tests-for-asp-net-mvc-applications-cs (Listing 4)
Related
I cant seem to figure this out, I'm not that new to the MVC model but perhaps my brain is just tired.
View Model
Public Class CategoriesViewModel
Public Property Categories As List(Of cihCategoryOrgDef)
Public Property Category As cihCategoryOrgDef
Public Property SelectedItem As String
Public Sub New(organizationId As Guid, codId As Guid)
Categories = lists.organizationClubCategories
Category = From x In Categories
Where x.codId = codId
Select x
End Sub
End Class
CategoriesController
Function Edit(codId As Guid) As ActionResult
Dim model As CategoriesViewModel = New CategoriesViewModel(ZenCommon.CurrentOrgId, codId)
Return View(model)
End Function
When I run this, I get a "invalid cast exception" on the Category = From x.... line
Unable to cast object of type 'WhereSelectListIterator`2[Library.cihCategoryOrgDef,Library.cihCategoryOrgDef]' to type 'Library.cihCategoryOrgDef'.
I'm trying to get a single Category to use in my View. So I can have an edit page for that specific category. Am I going about this all wrong?
Since Category is a single category, you need to make sure your LINQ returns a single result. If you're sure that the query will only return one value, use SingleOrDefault:
Category = (From x In Categories
Where x.codId = codId
Select x).SingleOrDefault()
If your query could return more than one result and you only want to take the first result, use FirstOrDefault:
Category = (From x In Categories
Where x.codId = codId
Select x).FirstOrDefault()
Your select actually returns a collection, that's why you get invalid cast exception. To get only one item you need something like this (sorry, I'm not very much familiar with vb, so here is c# code):
Category = (From x In Categories
Where x.codId = codId
Select x).DefaultIfEmpty(null).FirstOrDefault();
Below is the code in question. I receive Object reference not set to an instance of an object. on the where clause inside the Linq query. However, this only happens after it goes through and builds my viewpage.
Meaning: If I step through using debugger, I can watch it pull the correct order I am filtering for, go to the correct ViewPage, fill in the model/table with the correct filtered item, and THEN it comes back to my Controller and shows me the error.
public ActionResult OrderIndex(string searchBy, string search)
{
var orders = repositoryOrder.GetOpenOrderList();
if (Request.QueryString["FilterOrderNumber"] != null)
{
var ordersFiltered = from n in orders
where n.OrderNumber.ToUpper().Contains(Request.QueryString["FilterOrderNumber"].ToUpper().ToString())
select n;
return View(ordersFiltered);
}
return View(orders);
}
its always better to manipulate your strings and other things outside the linq query ,
please refer : http://msdn.microsoft.com/en-us/library/bb738550.aspx
from the readability point of view also its not good ,
public ActionResult OrderIndex(string searchBy, string search)
{
var orders = repositoryOrder.GetOpenOrderList();
var orderNumber = Request.QueryString["FilterOrderNumber"];
if (!string.IsNullOrEmpty(orderNumber))
{
orderNumber = orderNumber.ToUpper();
var ordersFiltered = from n in orders
where n.OrderNumber.ToUpper().Contains(orderNumber)
select n;
return View(ordersFiltered);
}
return View(orders);
}
Your query is not being executed in your Action method because you don't have a ToList (or equivalent) added to your query. When your code returns, your query will be enumerated somewhere in your view and that's the point where the error occurs.
Try adding ToList to your query like this to force query execution in your action method:
var ordersFiltered = (from n in orders
where n.OrderNumber.ToUpper().Contains(Request.QueryString["FilterOrderNumber"].ToUpper().ToString())
select n).ToList();
What's going wrong is that a part of your where clause is null. This could be your query string parameter. Try moving the Request.QueryString part out of your query and into a temporary variable. If that's not the case make sure that your orders have an OrderNumber.
You both were right. Just separately.
This fixed my problem
var ordersFiltered = (from n in orders
where !string.IsNullOrEmpty(n.OrderNumber) && n.OrderNumber.ToUpper().Contains(Request.QueryString["FilterOrderNumber"].ToUpper().ToString())
select n);
have looked at Phil Haacks project on books at
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
which has been useful, but I have a mix of data types.
I use a modelview so that i can have a mix of objects, in this case: Order (ie order.id, order.date etc), Customer, SoilSamplingOrder and a list of SoilSamplingSubJobs which is like this [0].id, [0].field, [1].id, [1].field etc
Perhaps I should be using ICollection instead of List? I had problems getting UpdateModel to work so I used an extract from collection method. the first 4 method calls : orderRepository.FindOrder(id); etc give the model the original to be edited. but after this point i'm a little lost in how to update the subjobs. I hope i have delineated enough to make sense of the problem.
[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
Order order = orderRepository.FindOrder(id);
Customer cust = orderRepository.FindCustomer(order.customer_id);
IList<SoilSamplingSubJob> sssj = orderRepository.FindSubOrders(id);
SoilSamplingOrder sso = orderRepository.FindSoilSampleOrder(id);
try
{
UpdateModel(order, collection.ToValueProvider());
UpdateModel(cust, collection.ToValueProvider());
UpdateModel(sso, collection.ToValueProvider());
IList<SoilSamplingSubJob> sssjs = orderRepository.extractSSSJ(collection);
foreach (var sj in sssjs)
UpdateModel(sso, collection.ToValueProvider());
orderRepository.Save();
return RedirectToAction("Details", new { id=order.order_id});
}
catch
{
return View();
}
}
I think you should work on developing a view model that reflects the data that you need to get back and create display/edit templates for that model that renders the view model using Phil Haack's methods for your lists of objects -- in this case, arrays of submodel classes. Let the model binding framework build the returned model (as a parameter) to your action, then reconstitute your domain models from the view model data. Brad Wilson has an excellent series of articles on templating that should be helpful.
I use IModelBinder on my complex classes. You don't need IModelBinder, but it will make your controller post codeblock look much cleaner. I'm using VB at the moment, but my class looks something like this for example:
Public Class CombinedRulesAndXmlRules : Implements IModelBinder
Public Rules As New Rules()
Public XmlRules As New XmlRules()
Public RequiredTemplates As New List(Of RequiredTemplates)
Public SearchCriteria As New List(Of SearchCriteriaList)
Public OptionalTemplates As New List(Of OptionalTemplates)
Public Questions As New List(Of Questions)
Public QATemplates As New List(Of QATemplates)
**Public Answers As New List(Of Answers)**
Now I don't use editor templates in my views, so to have your lists appear in the formcollection you have to add something like this in your view:
#For x As Integer = 0 To Model.Answers.Count - 1
Dim incr As Integer = x
#Html.HiddenFor(Function(model) model.Answers(incr).Answer)
#Html.HiddenFor(Function(model) model.Answers(incr).AnswerId)
#Html.HiddenFor(Function(model) model.Answers(incr).AnswerTemplateTag)
#Html.HiddenFor(Function(model) model.Answers(incr).Tag)
Next
When the view is submitted/posted, the model binder takes over before hitting the first line of code in your mvc post controller method. I then iterate through the actual formcollection and strip out the [#] using regex, because your formcollection will show your list items like this: Answers[0].Answer, Answers[0]AnswerId ,etc.:
For x As Integer = 1 To request.Form.Count - 1
keyname = request.Form.Keys(x)
Debug.Write(keyname)
val = request.Form(x).ToString()
'If keyname contains [#] strip it. it's a list item.
Dim pattern As String = "\[(\d+)\]"
Dim iterpattern As String = "\d+"
Dim rgx As New Regex(pattern)
Dim rgxiter As New Regex(iterpattern)
If Regex.IsMatch(keyname, pattern) Then
Dim match As Match = rgxiter.Match(keyname)
ListIteration = CInt(match.Value)
Dim result As String = rgx.Replace(keyname, "")
keyname = result
End If
The Select Case codeblock is next. So you already know you have a strong typed class in your model, so your select can look like this:
Select Case keyname
Case "Answers.Answer"
'add code here to add to your return list. What you
'get in the post controller is a fully populated class.
1) I've a Product table with 4 columns: ProductID, Name, Category, and Price. Here's the regular linq to query this table.
public ActionResult Index()
{
private ProductDataContext db = new ProductDataContext();
var products = from p in db.Products
where p.Category == "Soccer"
select new ProductInfo { Name = p.Name, Price = p.Price}
return View(products);
}
Where ProductInfo is just a class that contains 2 properties (Name and Price). The Index page Inherits ViewPage - IEnumerable - ProductInfo. Everything works fine.
2) To dynamicaly execute the above query, I do this:
Public ActionResult Index()
{
var products =
db.Products
.Where("Category = \"Soccer\"")
.Select(/* WHAT SOULD I WRITE HERE TO SELECT NAME & PRICE?*/)
return View(products);
}
I'm using both 'System.Lind.Dynamic' namespace and the DynamicLibrary.cs (downloaded from ScottGu blog).
Here are my questions:
What expression do I use to select only Name and Price?
(Most importantly) How do I retrieve the data in my view? (i.e. What type the ViewPage inherits? ProductInfo?)
===================
EDIT
When I write .Select("new(Name, Price)"), I'm able to pass an object to the ViewData's Model property. Unfortunately, in order to use the Viewdata object, I'm asked to cast the Viewdata to a type. But, I do not know how to determine the type to do the casting.
====================
EDIT
Instead of the ViewData's Model property, I'm using simply the ViewData["products"]. To retrieve the content, I just place a IEnumerable cast before the ViewData, like this:
<% foreach(var item in (IEnumerable)ViewData["products"]){%>
<p><% = Html.Encode(item)%><p>
<%}%>
There are 2 situations:
1) If I select only one column (for instance, Name), everything work fine.
2) If I select more than 1 more columns (Name, Price), I get something like this
{Name=Soccer, Price=19.50}
{Name=Shin Pads, Price=11.59}
Why I just don't get something like
Soccer, 19.50
Shin Pads, 11.59
=================================
EDIT April 02 - 05h47 AM
I've define the GetPropertyValue Method (as your response suggets) as static in a static Class that I called 'HelperClass'. Now, this is the way I try to access the value of Name from my object.
<% = Html.Encode(HelperClass.GetPropertyValue(ViewData["product"], "Name")) %>
I get the following Exception:"Object reference not set to an instance of an object". And, the following line from the inside GetPropertyValue() his highlight.
Line 22: return propInfo.GetValue(obj, null);
Do I need to use new keyword? (where?)
Thanks for helping
Private Sub filter()
Dim coll = db.Products.Where(Function(x) x.Category.Equals("Soccer")) _
.Select(Function(x) GetObject(x.Name, x.Price))
End Sub
Private Function GetObject(ByVal name As String, ByVal price As String) As Object
Return new ProductInfo(name, price)
End Function
1) To generate a new projection type at runtime you can:
.Select("new(Name, Price)")
2) To read values from the object, you need to use reflection:
string name = GetPropertyValue(someObject, "Name");
...
public static object GetPropertyValue(object obj, string propName)
{
System.Reflection.PropertyInfo propInfo = obj.GetType().GetProperty(propName);
return propInfo.GetValue(obj, null);
}
I am using MVC RC2.
I have Two tables
1)Product (PID, PName, CIDfk);
2)Category(CID, CName);
So i query like these
var Product = from p in dc.Product
from C in dc.Category
where p.CIDfk == c.CID
select new { ProductName = p.PName, ProductCategory = c.CName };
return view();
where dc is database context of LINQ-to-SQL class (.dbml);
How do i display in view? where i pass Product? (in viewdata or in 'return view()')
Please help me out...
You can both use:
- ViewData["MyName"] = product.SingleOrDefault();
This way from the view you'd do:
<% Product p = (Product)ViewData(p) %>
or populate the model:
ViewData.Model = product.SingleOrDefault();
This way from the view you'd do:
<%Product p = ViewData.Model%> //in case of a Strongly typed view
<%Product p = (Product)ViewData.Model%> //otherwise
After populating either ViewData or the Model you can call:
return View();
Another approach is calling the View overload that accepts the model as parameter, as tvanfosson said.
You want to have a strongly typed view and pass the product as the view model
var product = from p in dc.Product
from C in dc.Category
where p.CIDfk == c.CID
select p;
return View( product );
where your view is of type ViewPage<Product>.