How to pass extra identifier to OData Controller - c#

I'm having a OData controller named JobsController.
This controller represents the Job entity
public class Job
{
int Id {get;set;}
int Name {get;set;}
...
}
In order to make a query I need the client to send me a contextId (database identifier).
By checking the contextId the correct database is initialized.
Obviously, contextId is not part of the Job model...
I wish my controller will look like that:
[HttpGet]
[ODataRoute("Jobs")]
[EnableQuery]
public IHttpActionResult Get()
{
var unitOfWork = UnitOfWorkFactory.Create(contextId);
if (unitOfWork == null)
return InternalServerError(new Exception("Unit of work not found"));
return Ok(unitOfWork.Jobs.GetAll());
}
I wish I could query something like this: http://localhost:38483/odata/Jobs('localDb') but it's not possible
or even http://localhost:38483/odata/Jobs/localDb

Firstly, this seems like a very odd way to be handling this, having one service that can connect to two different databases seems confusing, can you just setup two different endpoints instead?
If you do require this to all be in one endpoint then OData has quite strict definitions for URLs based on the model/metadata that you setup and anything that you should be able to filter on etc should be included in the metadata so that a client can use the services by just consuming the metadata. If you want to include this within the URL then you will have to think about how that would apply to the metadata. Alternatively and preferably, you could use a custom header to achieve this.
Using a custom header would just be a case of sending the header and retrieving this in your controller from the Request object
To include this in your model, the best thing that I can think of is to setup contained entities so that you have http://localhost:38483/odata/localDb/Jobs/. You can find more information here: https://learn.microsoft.com/en-us/aspnet/web-api/overview/odata-support-in-aspnet-web-api/odata-v4/odata-containment-in-web-api-22

Related

How to Validate Request Body by allowing only attributes assigned in the Model of an asp.net core web api app

I have an ASP.NET CORE Web API app and placed a Model as:
public class SamplePayload
{
public string Attribute1 { get; set; }
public string Attribute2 { get; set; }
}
then my controller looks like:
[ApiController]
public class SampleController : ControllerBase
{
[HttpPost]
public async Task<string> Add(SamplePayload samplePayload)
{
if (!ModelState.IsValid)
{
//Throw Error
}
return "Hello";
}
}
However, the Action still accepts the payload if the Request Body had its payload like this (with additional attributes):
{
"Attribute1":"Value1",
"Attribute2":"Value2",
"EvilAttribute":"EvilValue"
}
As you see, EvilAttribute is not a valid attribute according to the Model, but still, the controller accepts it, and Model.IsValid also returns true despite the fact that I have [ApiController] assigned on top of the controller.
My question is how can I do validation to check that only attributes defined in the Model need to be passed in the Request body? Doesn't ASP.NET core offer simpler handling?
Note:
The reason for this requirement is, Under a Vulnerability assessment done by an independent assessor, highlighted that my Endpoint
accepts additional parameters in request body
The Assessment quoted as:
Test Category: Mass Assignment;
Test Conducted: Adding additional parameters to the requests
Findings: Additional parameters accepted with a request body
Recommendations:
If possible, avoid using functions that automatically bind a client's input into code variables or internal objects.
Whitelist only the properties that should be updated by the client.
If applicable, explicitly dene and enforce schemas for the input data payloads.
I see not much benefits from rejecting the requests that have more data in it.
If that is some sort of requirement, here is what you can try:
I think you may need to implement your own ModelBinder as per example described here:
https://learn.microsoft.com/en-US/aspnet/core/mvc/advanced/custom-model-binding?view=aspnetcore-5.0
The way default model binding works - is going from Model perspective and search for matching fields in the request and assigns values to related model properties.
Once the model that is required can be constructed and passes all of it's internal constraints - it is Valid.
Please check this article: Model Binding in ASP.NET Core
Model binding tries to find values for the following kinds of targets:
Parameters of the controller action method that a request is routed to.
Parameters of the Razor Pages handler method that a request is routed to.
Public properties of a controller or PageModel class, if specified by attributes.
For your scenario, if you set a break point in the Add method, you can see that the Model Binding will just find values based on the SamplePayload's properties.
So, I think there is no need to check whether the request body contains more data or not.
An [ApiController] or a parameter marked as [FromBody] will be serialised in MVC via an input formatter, rather than binding from a value provider.
Working backwards from the mvc source code. It looks like you can control MVC's serialisation settings if you are using System.Text.Json, though I don't see a way to reject extra values;
services.Configure<JsonOptions>(o =>
{
});
Or newtonsoft, which does seem to allow rejecting extra values;
services.Configure<MvcNewtonsoftJsonOptions>(o =>
{
o.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error;
});
(I haven't tested this myself though...)
I would still push back on this "security" assessment. If you don't have any extra fields available to bind, then rejecting requests doesn't improve security. Though it may help consumers to understand if they have mis-spelled an attribute name.

Is it possible to accept odata filter as a string rather than odataqueryoptions?

I have a controller that should accept some type of filter and then forward the request against a web api endpoint such as http://crmorg/v2/api/
public IHttpActionResult Get(ODataQueryOptions options)
{
//execute GET against http://crmorg/v2/api/options.Request.RequestUri.PathAndQuery;
}
Instead of doing the above, can I simply accept a string as an odata filter, such as:
public IHttpActionResult Get(string options)
{
//
}
Can I pass the odata filter directly into the controller as a string?
In theory you can do that. But:
You'd not be able to use the default property mapping
You'd need need to parse the query string and care for all the possible input variations (think about multi params, sorting, paging, filtering)
You'd need to cobble the database query and it's options
In short - you rarely need to do that. Try and look at your model and see if there is a way to use the features available in Web API.

RESTFull HTTP method to use when performing a copy of an object with Web API

I have a ProductsController and I want to create an action method that copies a product. It only needs one parameter which is an Id which temps me to want to use a simple GET Request. Something is telling me GET doesn't make sense here though. Do I really use POST and pass a single Id to this method?
Here is what I came up with:
// COPY: api/products/{id}/copy
[HttpPost("{id}/copy")]
public void Copy(int id)
{
_productManager.Copy(sourceProductId: id);
}
I'd basically handle the situation using one of the following approaches:
1. COPY
Do as per the WebDav specification and just use COPY http method. This, I believe, is the most semantically correct way of cloning/copying a resource in a RESTful manner. Note that REST do not restricts you in using just the default verbs:
[AcceptVerbs("COPY")]
[Route("{id}")]
public void Copy(int id)
{
_productManager.Copy(sourceProductId: id);
}
2. POST
Keep using the same method you are using for creating a new resource (because, after all, you are creating a new resource), but with an optional query string parameter that will determine if you are copying an existing resource.
You will then trigger the copy operation by sending a POST request with an empty body to a URI similar to the following one: http://myhost/api/products?sourceId=1. Here it is a code sample:
[HttpPost]
[Route("")]
public void CreateOrCopy([FromBody] ProductDTO dto, [FromUri] int? sourceId = null)
{
if(dto == null && sourceId.HasValue)
_productManager.Copy(sourceProductId: id); // copy behavior
else
// normal behavior
}
I think that both approaches are very usable from a client point of view, without breaking REST constraints or semantic meanings (absolutely avoid the use of GET for such a purpose).

REST Web Service - Different Types for Get and Create

With ASP.Net Web Api, is it ok / good practice for your GET endpoint to return a different type than your POST endpoint accepts?
In many cases, fields we want to return in a GET cannot be set in a POST, such as "LastUpdated", "Total", etc, necessitating different types?
Example, GET returns ReservationForGetModel, while POST accepts a ReservationForCreateModel:
public class ReservationController : ApiController {
...
public HttpResponseMessage Get(int id) {
Reservation reservation = _reservationService.Get(id);
//map Reservation to ReservationForGetModel
//return ReservationForGetModel
}
public HttpResponseMessage Post(ReservationForCreateModel reservation) {
//create reservation using ReservationForCreateModel here
//return 201 with location header set
}
}
GET and POST can use completely different media types. Consider HTML forms: you POST with application/x-www-urlencoded-form and GET returns text/html.
With GET and PUT it is more likely that the media-types are symmetric, however, that is not a hard rule.
You can, but probably shouldn't. Web API is designed as a platform for RESTful APIs, and that includes
Manipulation of resources through these representations
Let's say I have a mobile app consuming your webservice. To create an entity, I first make a ReservationForCreateModel, and POST it. Now I want to update it. I have to have client side code that can convert a ReservationForCreateModel to a ReservationModel, which will be a repetition of the same code on the server.
Use a DTO pattern, and send back the same type of object you take in. Perhaps internally to the server they'll be split, but to the outside world there should be a common language.
It sounds like your real issue is this
In many cases, fields we want to return in a GET cannot be set in a POST, such as "LastUpdated", "Total", etc, necessitating different types?
Why can't you set these values?

Routing by posted content type in ASP.NET MVC

I have a fixedURL to which I'd like to post different types of xml message, deserialized using DataContracts. Depending on the type of the deserialized message, I'd like to route to:
overloaded methods, e.g.
void Process(ContractType1 request) {}
void Process(ContractType2 request) {}
So at some point I need to deserialize this message and hopefully allow the default routing rules to match the correct method. Which extensibility point should I use for this? Or even better, can I make this work out of the box?!
If it makes any difference, I'm using MVC 3.
ASP NET MVC does not respect the overload if they are not decorated for different HTTP methods - e.g. one for POST, other for GET.
You need to use [ActionName(Name = "Process2")] to change the route name. And you will have to use different routes to access (if the HTTP methods are the same)
Have a look here.
Apart from the technical workaround, passing different contracts to the same URL is against the REST principles. Data could be in different format (XML, JSON, ...) but it must be the same. The URI defines a unique intention. Now it is possible to have a common dumpster where documents are all dumped to the same URI but then ASP NET MVC default model binder would not be able to help you and you need to create your own model binder.
Contrary to the other answer I say this is possible
Asp.net MVC is a great platform that can be easily extended. And so basically I've written a special action method selector that makes it possible to write overloads that can serve the same HTTP method but defer in parameters. By default you'd get runtime error that action method can't be resolved. But when you use this action method selector you get rid of this error.
Basically if your parameter classes have distinct parameter names, you can actually select methods by that.
Action method selector is called RequiresRouteValuesAttribute and a typical usage scenario would be with default route where id is optional in:
{controller}/{action}/{id}
This means that you either have to write
public ActionResult Index(int? id)
{
if (id.HasValue)
{
// display details view
}
else
{
// display master view
}
}
but by using my action method selector you can easily write two action methods:
public ActionResult Index()
{
// display master view
}
[RequiresRouteValues("id")]
public ActionResult Index(int id)
{
// display details view
}
The same could be applied to your action methods as long as your custom types have distinct property names or methods use different parameter names. So in your case it could be something like:
[RequiresRouteValues("first.Id")] // when you provide prefix with your form
// or
[RequiresRouteValues("Some.ContractType1.Distict.Property.Name")]
public ActionResult Process(ContractType1 first)
{
// do what's appropriate
}
[RequiresRouteValues("second.Id")] // when you provide prefix with your form
// or
[RequiresRouteValues("Some.ContractType2.Distict.Property.Name")]
public ActionResult Process(ContractType2 second)
{
// do what's appropriate
}
Read all the details about this action method selector and get the code as well.

Categories