Possible to manually apply OData parameters to result of `.AsQueryable()`? - asp.net

I have a MVC4 WebAPI controller that returns an IQueryable, and therefore I can use $filter and friends in the URL to manipulate the result from the REST endpoint. Here's my controller:
public class EnrollmentController : ApiController
{
[Queryable]
public IQueryable<tblEnrollment> Get()
{
var context = new ProjectEntities();
context.ContextOptions.LazyLoadingEnabled = false;
return context.tblEnrollment.AsQueryable();
}
}
But, like this poster, I'm wanting to make the JSON output format a little different to be friendlier with Ember Data's expected format. So I'd like to return this instead:
return new { enrollment = context.tblEnrollment.AsQueryable() };
However, that breaks OData capability because I'm not returning the IQueryable to the WebAPI layer. So, I'm wondering if there's a way to do something like this:
return new { enrollment = context.tblEnrollment.AsQueryable().ApplyOData() };
Which I'm sure would be way to good to be true...but is there some way to explicitly process the OData parameters against an IQueryable instead of letting the WebAPI layer do it implicitly on the result set returned from a Get method? Or is there another way to accomplish what I want here?
Incidentally, I'm stuck on EF4 for the time being, because I can't upgrade to VS2012 (and hence to .NET4.5 and hence EF5). I could theoretically upgrade to EF 4.3.1, if it would help.

Instead of marking your action as [Queryable], you can add a parameter of type ODataQueryOptions and apply it manually. Here's what it might look like:
public class EnrollmentController : ApiController
{
public object Get(ODataQueryOptions<tblEnrollment> query)
{
var context = new ProjectEntities();
context.ContextOptions.LazyLoadingEnabled = false;
var queryResults = query.ApplyTo(context.tblEnrollment.AsQueryable());
return new { enrollment = queryResults };
}
}

Related

NET Core: Filtering with HttpGet

I would like to filter a list of vehicles, by their makeId using httpGet. The URL I would expect to use is:
https://localhost:5001/api/vehicle?makeId=2
Below, I will define the DTO and controller methods I used for this task:
FilterDto
public class FilterDTO
{
public int? MakeId { get; set; }
}
Below are the 2 HTTPGet methods in my controller class. I expect the first method to be called.
[HttpGet]
public async Task<IEnumerable<VehicleDTO>> Get(FilterDTO filterDto)
{
var filter = _mapper.Map<Filter>(filterDto);
var vehicles = await _vehicleRepository.GetAll(filter);
return _mapper.Map<IEnumerable<VehicleDTO>>(vehicles);
}
[HttpGet("{id}")]
public async Task<ActionResult<VehicleDTO>> Get(long id)
{
var vehicle = await _vehicleRepository.GetWithRelated(id);
if (vehicle == default)
{
return BadRequest("Vehicle not found");
}
var result = _mapper.Map<VehicleDTO>(vehicle);
return Ok(result);
}
With the above code, when I call the URL above, in Postman I get a 400 Error, saying "The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true. Path: $ | LineNumber: 0 | BytePositionInLine: 0."
I get the same result for https://localhost:5001/api/vehicle
If I change the first Get method like below, I am able to get a response:
[HttpGet]
public async Task<IEnumerable<VehicleDTO>> Get(int? makeId)
{
var filter = new Filter { MakeId = makeId};
var vehicles = await _vehicleRepository.GetAll(filter);
return _mapper.Map<IEnumerable<VehicleDTO>>(vehicles);
}
After this (lengthy) introduction, my questions are:
Why does HttpGet support 'int?' but not the data transfer object 'FilterDto'?
Should I be using a different verb instead of HttpGet?
I might have to filter in the future for some other types (say customerId). Is there any way I can change the method to support custom objects, like FilterDto, ideally without changing the verb?
Change your code as follow:
[HttpGet]
public async Task<IEnumerable<VehicleDTO>> Get([FromQuery] FilterDTO filterDto)
{
var filter = _mapper.Map<Filter>(filterDto);
var vehicles = await _vehicleRepository.GetAll(filter);
return _mapper.Map<IEnumerable<VehicleDTO>>(vehicles);
}
and call it like:
baseUrl/Controller/Get?MarkId=1
Take a look at the docs.
Basically the primitive types are supported, but the controller has no idea how to convert your web request data into C# object. You need to explicitly tell it how you want this custom object to be created out of web request.
You may have in mind that HttpGet methods are only able to receive primitiveTypes (string, int, short, datetime -using a specific format-) because the arguments are being sent through query string, for example:
myAddres.com/api/mymethod?id=5&filter1=value1&filter2=value2
Having this consideration in mind you'll notice there's no way to send any object because you need to use a json or another notation, remember querystring has a limit and because of that is better using "argument=value" notation.
On the other hand PUT and POST are able to send their data through a "body" property where you may use a json notation and this way you may create almost any object on your Backend side.
If you need to use an object as an argument it is a better idea using POST or PUT (better POST than PUT).

Return a data object with a BadRequestResult / BadRequestErrorMessageResult

I'd like to return a data object that contains the details of the error with a BadRequestErrorMessageResult or BadRequestErrorMessageResult object like so:
public IHttpActionResult Action(Model model)
{
var validationResult = model.Validate();
if (validationResult.Successful)
{
// this one's okay; it supports sending data with a 200
return Ok(validationResult);
}
else
{
// However, how do I return a custom data object here
// like so?
// No such overload, I wish there was
// return BadRequest(validationResult);
}
}
The only three overloads of the ApiController.BadRequest() method are:
1. BadRequest();
2. BadRequest(string message);
3. BadRequest(ModelStateDictionary modelState);
Even with #3, a model state dictionary is ultimate a deep collection with one layer upon another, at the bottom of which, though, is a bunch of KeyValuePair<string, ModelError> where each ModelError also only has either a string or an Exception object.
Therefore, even with #3, we are only able to pack a string to send and not a custom object like I want to.
I am really not asking how I may go about working a hack or a kludge around the situation. My question is: is there an overload or another way baked into the .NET API to send an object to the client with a Bad Request HTTP status code?
I am using ASP.NET Web API version 5.2.4 targeting .NET Framework version 4.6.1.
You can use the Content<T>(...) method to do this. It returns a NegotiatedContentResult, which is serialized depending on the request headers (e.g. json, xml), and allows you to specify a HttpStatusCode.
You can use it like this:
return Content(HttpStatusCode.BadRequest, myObject);
If you wanted to, you could create your own BadRequest<T>(T obj) method in the controller as a wrapper, so then you could call it as you wanted:
public IHttpActionResult BadRequest<T>(T obj)
{
return Content(HttpStatusCode.BadRequest, obj);
}
public IHttpActionResult Action()
{
// do whatever validation here.
var validationResult = Validate();
// then return a bad request
return BadRequest(validationResult);
}
You can build/format the string in JSON format, pass it as string in the BadRequest() parameter and convert it to JSON again or any object on the caller's backend.
Haven't tried that but that should work.

What kind of datatypes should I use for the return value of a Web API method?

I have a Web API controller that returns data to my client. The code looks like this:
[HttpGet]
[ActionName("Retrieve")]
public IEnumerable<Reference> Retrieve(int subjectId)
{
return _referenceService.Retrieve(subjectId);
}
Can someone tell me is it necessary to specify the ActionName?
Also should I return an IEnumerable, an IList or something else?
I believe if your ASP.NET routing is setup correctly you don't need to specify the ActionName, for example:
protected void Application_Start()
{
RouteTable.Routes.MapHttpRoute("0", "{controller}/{action}/{arg1}");
}
Will match /YourControllerName/Retrieve/132
What you return is based entirely on your media-type formatters, of which the default is XmlFormatter and JsonFormatter. These can be found in GlobalConfiguration.Configuration.Formatters and will be chosen based on the Accept header provided by the client.
We, for example, use JSON.Net for our response formatting, configured by:
protected void Application_Start()
{
RouteTable.Routes.MapHttpRoute("0", "{controller}/{action}/{arg1}");
MediaTypeFormatterCollection formatters = GlobalConfiguration.Configuration.Formatters;
formatters.Remove(formatters.XmlFormatter);
var jsonFormatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings;
jsonFormatter.Formatting = Formatting.Indented;
jsonFormatter.ContractResolver = new CamelCasePropertyNamesContractResolver();
}
This tells WebApi to disallow any XML formatting and only return JSON using the provided JSON.Net contract resolver. JSON.Net supports serializing IEnumerable.
I would, however, recommend returning a HttpResponseMessage instead. This allows you to set the status code as well (This still uses the media type formatter, it's just a cleaner wrapper). You can use this like so:
[HttpGet]
public HttpResponseMessage Retrieve(int subjectId)
{
var response _referenceService.Retrieve(subjectId);
return Request.CreateResponse(HttpStatusCode.OK, response);
}
You should return HttpStatusCode instead of data if have not requirement, like POST method should return OK or whatever.
or if want record like Get method should return type of record.
also you no need to add attribute on method like Get,Put,Delete etc because webapi automatically detect method according to action like if you are getting data then your method name should be start with Get like GetEmployee etc.

Can't use ODataQueryOptions with a (regular - not webapi) Controller derived class?

public class XYZController : Controller
{
public ActionResult Index(ODataQueryOptions<Security> options = null)
{
var xyzs= GetXYZs().AsQueryable();
var results = options == null ? xyzs: options.ApplyTo(xyzs);
return View(xyzs);
}
}
This results in "No parameterless constructor defined for this object" error.
I essentially want to pass odata compliant parameters in to a regular controller.
Can this not be done?
I temporarily (until regular controllers can use ODataQueryOptions) solved this through the use of Linq2Rest (NuGet: install-package Linq2Rest)
This quite powerful library allowed me to accomplish what I am looking for with one line of code:
using Linq2Rest;
public ActionResult Index()
{
var filteredSource = GetXYZs().AsQueryable().Filter(Request.Params);
return View(filteredSource);
}
Now you can hit this Controller's Index Action like this:
xyz.com?$filter=something eq 'foo' and another gt 3&$orderby another
ODataQueryOptions<T> is only supported with web API now. That said, this is an interesting scenario. I have opened this issue on codeplex to track it.

Proper way of using FormCollection in ASP.NET MVC2 Create Method?

I am currently developing an application with the new ASP.NET MVC2 framework. Originally I started writing this application in the ASP.NET MVC1 and I'm basically just updating it to MVC2.
My problem here is, that I don't really get the concept of the FormCollection object vs. the old Typed object.
This is my current code:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(FormCollection collection)
{
try
{
Member member = new Member();
member.FirstName = collection["FirstName"];
member.LastName = collection["LastName"];
member.Address = collection["Address"];
// ...
return RedirectToAction("Details", new { id = member.id });
}
catch
{
return View("Error");
}
}
This is the Code from the MVC1 application:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Member member)
{
try
{
memberRepository.Add(member);
memberRepository.Save();
return RedirectToAction("Details", new { id = member.id });
}
catch
{
}
return View(new MemberFormViewModel(member, memberRepository));
}
What are the benefits of switching to FormCollection in MVC2 and more importantly - how is it used properly?
You had the FormCollection object in v1 as well. But it is more preferred to use a typed object. So if you are already doing that, then continue doing so.
By using FormCollection, you wind up manually matching your post data or query string key/values into values to use in your code using string typing (resulting in stringly-typed code), when instead the built-in Model Binding can do this for you if you use form models, aka "typed objects."
I think by using the FormCollection, you would probably also lose the ability to use the handy Data Annotation (slash Validation) attributes on your model objects as well, which are designed for use with typed object model binding.
Additionally, unit testing can become much more cumbersome once you start touching your controller.Request.Form. You might find yourself having to mock and setup an HttpContextBase, and an HttpRequestBase just to have that mock request's .Form property return the NameValueCollection that you are wanting your test to see. Contrast this to letting model binding do the work for you such as:
// Arrange
var myModel = new MyModel( Property1 = "value1", Property2 = "value2");
// Act
var myResult = myController.MyActionMethod(myModel);
// Assert
// whatever you want the outcome to be
In summary, I would recommend against using FormCollection to the maximum extent possible.

Resources