Value cannot be null error in processing OData from Web API - odata

I have a WebAPI method that returns OData:
[HttpGet]
public PageResult<Students> GetStudents(ODataQueryOptions<Students> queryOptions, string ClassId)
{
var allStudents = (from s in new OStudentContext(Convert.ToInt64(ClassId)).Student select s).ToList();
var results = queryOptions.ApplyTo(allStudents.AsQueryable());
return new PageResult<Students>(results as IEnumerable<Students>, Request.GetNextPageLink(), Request.GetInlineCount());
}
The request URL is like so:
http://localhost:3333/api/odata/GetStudents?StudentId=40932&$inlinecount=allpages&$filter=((IsDeleted%20eq%20null)or(IsDeleted%20eq%20false))&$select=StudentId,FirstName,LastName,EmailID
The value in results is there and I can see the records returned. The count is actually 7 to be precise.
The problem is the the return statement throws this exception:
Value cannot be null.Parameter name: data
Request.GetNextPageLink() is null as there is no next page link.
Request.GetInlineCount() is 7.
So what is null & what does the error mean about data?
Thanks in advance.

The element type after applying ODataQueryOptions is no longer Students if there is some $select or $expand.
It changes to SelectExpandWrapper instead.
So the result can't be cast to IEnumerable<Students>.
For the structure of SelectExpandWrapper, you can refer to https://aspnetwebstack.codeplex.com/wikipage?title=%24select%20and%20%24expand%20support

Related

Adding new parameter to a web API action method without disturbing the existing contract

I have an action method already written in my web api 2.0 project. I would like to add a new parameter without disturbing the existing contract. What is the best way to do that? Appreciate any best practice hints on this :)
Here's the code sample of what I intend to do:
Existing code:
[Route("{myId}",Name="MyId")]
Public IHttpActionResult Get(String myId)
{
//Some more code here
}
Url: http://localhost:8888/webapi/1111
Expecting to do something like the below:
//I want to keep the route name same for backwards compatibility.
[Route("{myId}/{myName}",Name="MyId")]
Public IHttpActionResult Get(String myId,string? myName)
{
//Some more code here
}
Url: http://localhost:8888/webapi/1111/John
The Url mentioned above hits the method rightly, but I never get the second parameter (myName) populated with John.
Thanks everyone for any help towards this.
Sree.
In your example you have myName as string? which is not allowed as:
The type 'string' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'System.Nullable'
A test controller was created to implement you action
[RoutePrefix("webapi")]
public class TestsController : ApiController {
[HttpGet]
[Route("{myId}/{myName}", Name = "MyId")]
public IHttpActionResult Get(string myId, string myName) {
//Some code to show the values of the parameters
return Ok(new { myId = myId, myName = myName });
}
}
When tested with webapi/1111/John the following response is returned
{"myId":"1111","myName":"John"}
which does include the value for MyName as John
If backwards uri webapi/1111 is tried, a NotFound response is returned as the template does not match the new action.
To fix this you need to make the myName parameter optional. To learn more about that check
Optional URI Parameters and Default Values
The new route will look like
//NOTICE THE `?` ON THE {myName} TEMPLATE
[Route("{myId}/{myName?}", Name = "MyId")]
public IHttpActionResult Get(string myId, string myName = null) {...}
You will notice that myName was made optional in the route {myId}/{myName?} and in the action parameter (string myId, string myName = null)
Now when tested with webapi/1111 the following response is returned
{"myId":"1111","myName":null}
Which would match your expected result for backwards compatibility.
String is a reference type so you don't need to make it nullable, it already is. Remove the '?' and remove the Name from the attribute. What happens then?

Selecting only part of the DbSet in Read method of Kendo grid

I'm trying to use a Kendo UI grid in MVC and remote data. I want to only grab and display data from the DbSet, onload, where one of the fields, "Status", equals '1'. I thought this should be able to be accomplished in the controller:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Read([DataSourceRequest] DataSourceRequest request)
{
using (var db = new MyData(false))
{
var data = db.Training.Where(d => d.Status == '1').Select(d => new Training {
Id = d.Id,
Name = d.Name,
Status = d.Status
}).ToDataSourceResult(request);
return Json(data, JsonRequestBehavior.AllowGet);
}
}
The above code gives me the error that "The entity or complex type 'Training' cannot be constructed in a LINQ to Entities query". Any suggestions on how to rewrite the Linq statement so it'll work, or maybe a way to do it within the grid to suppress any that do not have a Status of '1'?
Your code is trying to project to a mapped entity which is not allowed. Additionally it's redundant to do that as you already have your entities. Remember that .Select() is for mapping one type to another but the .Where() method is already returning a list of your entities (Training).
Remove the .Select() and the query should work:
var data = db.Training.Where(d => d.Status == '1').ToDataSourceResult(request);

Unexpected result with $select statement using WebApi 2

I am working on a project using MVC 4 and WebApi 1 and it work well,
After I upgraded it to MVC 5 and WebApi 2, everything works great, except this scenario: when i use the $select statement in the query and specify some properties, the options.ApplyTo() method returns unexpected results:
System.Web.Http.OData.Query.TruncatedCollection`1[System.Web.Http.OData.Query.Expressions.SelectExpandBinder+SelectSome`1[MvcApp.Domain.Entities.Users]]
Why this change, and how to convert it to IQueryable< Users> as WebApi 1?
my code here:
public PageResult<Users> Get(ODataQueryOptions options)
{
ODataQuerySettings settings = new ODataQuerySettings() { PageSize = 10 };
IQueryable results = options.ApplyTo(Repository.Data, settings);
var items = results as IEnumarable<Users>;
// items will be null if you $select some properties!
return new PageResult<Users>(items, null, Request.GetInlineCount().Value);
}
Help please.

How to properly send action parameter along with query in BreezeJs

Currently I am calling all data queries as showed on BreezeJs docs / examples:
getEntityList = function (predicate) {
var query = new entityModel.EntityQuery().from("EntityList");
if (predicate)
query = query.where(predicate);
return manager.executeQuery(query);
}
But I want to pass additional parameter to controller action before any queryable result is returned:
[AcceptVerbs("GET")]
public IQueryable<Entity> EntityList(string actionParam) {
//here goes logic that depends on actionParam
//and returns IQueryable<Entity>
}
As we know from documentation:
Breeze converts the query into an OData query string such as this one:
?$filter=IsArchived%20eq%20false&$orderby=CreatedAt
This is where the problem starts. How should I build query to pass param to controller action?
getEntityList = function (predicate, actionParam) {
var query = new entityModel.EntityQuery().from("EntityList");
if (predicate)
query = query.where(predicate);
if(actionParam)
// ???
return manager.executeQuery(query);
}
I already tried setting route to:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{actionParam}",
defaults: new { query = RouteParameter.Optional }
);
and sending actionParam by applying it in a from section,
var query = new entityModel.EntityQuery()
.from("EntityList/" + encodeURIComponent(actionParam));
but encoding fails on some special chars and bad request is being thrown.
How can I properly send actionParam in such scenario? Please help.
As of v 0.76.1, you can use the EntityQuery.withParameters method to pass additional parameters to any service method. So you can now construct a query like the following that both passes parameters and uses breeze's IQueryable support.
EntityQuery.from("EmployeesFilteredByCountryAndBirthdate")
.withParameters({ BirthDate: "1/1/1960", Country: "USA" })
.where("LastName", "startsWith", "S")
.orderBy("BirthDate");
where your controller method would look something like this:
[HttpGet]
public IQueryable<Employee> EmployeesFilteredByCountryAndBirthdate(DateTime birthDate, string country) {
return ContextProvider.Context.Employees.Where(emp => emp.BirthDate >= birthDate && emp.Country == country);
}
The API docs have more information.
UPDATE: AS OF BREEZE v.0.76.1 THIS IS NO LONGER THE CORRECT ANSWER. BREEZE NOW SUPPORTS PARAMETERS ON QUERIES. SEE THE "withParameters" QUERY CLAUSE.
Support for parameterized queries was added to Breeze thanks in part to this question on SO. Thank you.
This answer used to describe a workaround which is no longer needed. I have revised my answer, eliminating my description of that workaround.

passing record to a view using entity framework

I am using .NET 4.0, MVC3 and entity framework 4.0
I need to pass a database record of type "statement" to a view. The code to fetch the record is as below:
public ActionResult pdfStatement(string InvoiceNumber)
{
ObjectParameter[] parameters = new ObjectParameter[1];
parameters[0] = new ObjectParameter("InvoiceNumber", InvoiceNumber);
return View(_db.ExecuteFunction<Models.Statement>("uspInvoiceStatement", parameters));
}
where "uspInvoiceStatement" is my stored procedure used to fetch "statement"
I have created a strongly typed view which receives the "statement"
< % # Page Language="C#" Inherits="System.Web.Mvc.ViewPage < InvoiceSearch.Models.Statement >" %>
When i run the application i get an exception saying
"The model item passed into the dictionary is of type 'System.Data.Objects.ObjectResult`1[InvoiceSearch.Models.Statement]',
but this dictionary requires a model item of type 'InvoiceSearch.Models.Statement'.
Help me to escape from this trouble.
"
The error is surprisingly helpful - it tells you, you are sending ObjectResult<Statement> to the view asi viewmodel, while you should send Statement.
Your _db.ExecuteFunction always returns ObjectResult<Statement>, so you need to take Statement from it, like this :
var statementResult = _db.ExecuteFunction<Models.Statement>("uspInvoiceStatement", parameters);
var statement = statementResult.SingleOrDefault();
if (statement != null)
{
return View(statement); // isnt that nicer when you can see what your viewmodel is?
}
else
{
return View("NotFound");
}

Resources