This OData function does not deserialize the model parameter from the body. It deserializes as null as seen from response. Is there support for FromBody parameters in OData V4?
ConfigV1.cs
builder.Function("CreateTestModel").Returns<TestModel>();
var edmModel = builder.GetEdmModel()
config.MapODataServiceRoute("ODataRouteV1", "v1", edmModel);
TestController.cs
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.OData;
using System.Web.OData.Query;
using System.Web.OData.Routing;
public class TestController : ODataController
[HttpPost]
[ODataRoute("CreateTestModel")]
public TestModel CreateTestModel([FromBody]TestModel model)
{
return model;
}
}
TestModel.cs
public class TestModel
{
public string Value { get; set; }
}
Request
POST /v1/CreateTestModel HTTP/1.1
Host: localhost:8090
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: 4810cdc0-d92b-b7b5-4328-8b87e0222854
{
"Value": "test"
}
Response
{
"#odata.context":"http://localhost:8090/V1/$metadata#Edm.Null","#odata.null":true
}
OData Functions should be called with an HTTP GET and shouldn't affect the server. Your method here CreateTestModel sounds like it will affect the server so I would say that it is probably more suited to an OData Action. This may seem like it isn't relevant but I think that it will actually fix your issue as well because Actions are setup to have parameters in the body whereas Functions typically get parameters from the URL
In V4 we are using ODataActionParameter in the controller method, you can refer to this page to get detail information, and there are more V4 features.
http://odata.github.io/WebApi/#04-07-action-parameter-support
Related
I have been attempting to follow this post to enable attribute routing in OData 8.0.10:
Attribute Routing in ASP.NET Core OData 8.0 RC
During development of v8 ODataRouteAttribute and ODataRoutePrefixAttribute have been removed and routing is supposed to follow regular ASP.NET Core attribute routing, however I cannot get this to work as described.
I register OData as follows:
// build edm:
model = builder.EntitySet<Stuff.PersonProfile>("personProfiles");
// startup.cs
odataOptions.Count().Filter().Expand().Select().OrderBy().SetMaxTop(3).AddRouteComponents("", model)
// person profiles controller:
[Route("personProfiles")]
public class PersonProfilesController : ODataController
{
[HttpGet("Person")]
IActionResult GetPerson(ODataQueryOptions<Stuff.PersonProfileService.Models.PersonProfile> options)
{
}
}
This creates the endpoint correctly and I can reach it:
APIStuff.Controllers.PersonProfilesController.GetPerson (Stuff.API)
GET personProfiles/Person
However no OData endpoint mapping is created. If I remove the attribute route on the GetPerson method, then it DOES. i.e.: I get OData returned in the payload of the personProfiles endpoint that it creates.
It appears this was possible in the 8.0 preview as described in the following:
Routing in ASP NET Core 8.0 Preview
Where clearly there are examples of using attribute routing on the controller and the method. e.g.:
[ODataRoutePrefix("Customers({id})")]
public class AnyControllerNameHereController : ODataController
{
[ODataRoute("Address")]
public IHttpActionResult GetAddress(int id)
{
//......
}
[ODataRoute("Address/City")]
public IHttpActionResult GetCity(int id)
{
//......
}
}
I can only assume this has been removed or I am missing a very big elephant in the room.
Since 8.0 RC, attribute routing is changed to use [Route] and [HttpGet], etc
From your description, ‘personProfiles’ is an entity set, and “Person” looks like a property defined by the type of “personProfiles”, right?
If that’s the case, based on OData spec, you should query a property from an entity (a single entity). It means you should specify the key/id.
You can put the key in [Route] or in [HttpGet].
// person profiles controller:
[Route("personProfiles")]
public class PersonProfilesController : ODataController
{
[HttpGet("{key}/Person")] // this will generater route as: ‘personProfiles/{key}/Person’. It’s key as segment.
IActionResult GetPerson(ODataQueryOptions<Stuff.PersonProfileService.Models.PersonProfile> options)
{
}
}
// person profiles controller:
[Route("personProfiles({key})")] // this will generater route as: ‘personProfiles({key})/Person’. It’s key in parenthesis.
public class PersonProfilesController : ODataController
{
[HttpGet("Person")]
IActionResult GetPerson(ODataQueryOptions<Stuff.PersonProfileService.Models.PersonProfile> options)
{
}
}
Thanks,
-Sam
How do I pass optional (nullable) [FromBody] parameter in ASP.NET Core (5.0)? If I don't send body in my request I get 415 Unsupported Media Type error. Can this be configured and if so, how to do it on a controller or action, rather than an app level? I presume it has to do something with model validation, but not sure. Thanks.
[HttpGet("[action]")]
public async Task<IActionResult> GetElementsAsync([FromBody] IEnumerable<int> elements = default)
{
var result = await dataService.GetData(elements);
return Ok(result);
}
EDIT: To clarify:
This is typical scenario and it works normally:
But passing empty body is returning 415 right away without even reaching action:
You can find a solution here:
https://github.com/pranavkm/OptionalBodyBinding
From this issue on github:
https://github.com/dotnet/aspnetcore/issues/6878
And from .net Core 5 you can use this one:
public async Task<IActionResult> GetElementsAsync([FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] IEnumerable<int> elements = default)
...
Also needed (from Pawel experience):
services.AddControllers(options =>{options.AllowEmptyInputInBodyModelBinding = true;})
Just add content-type in your request header. Without the content-type:application/json will appear 415 when body is empty.
No changes to your controller. Test in my side is ok.
I created a new asp.net core 5 api project and this is my controller:
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace WebApi_net5.Controllers
{
public class HomeController : ControllerBase
{
[HttpGet("[action]")]
public string GetElementsAsync([FromBody] IEnumerable<int> elements = default)
{
return "value";
}
}
}
With ASP.NET Core 3.1, I could allow nullable optional parameters by implementing Nicola's suggestion:
services.AddControllers(options =>{options.AllowEmptyInputInBodyModelBinding = true;})
I will address some points that were not mentioned here.
To get rid of 415 without sending Content-Type you need to create your custom consumer of the Content-Type
https://stackoverflow.com/a/65813534/2531209
But I would say this is an overkill
If you pass Content-Type: application/json in your request header you will get "Body cannot be empty" (Tested on .NET 6) and only then #Nicola answer comes in handy.
From my tests it looks like modifications to the controller are not needed and only FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow) is enough with nullable type as parameter.
Or you can change nothing in your current code and send a header Content-Type: application/json with a body of {}. This will bypasses all of those errors, but this is not the most optimal solutions for public API's
I have a ASP.NET Core 3.1 API where I have not used CORS. As I understand, CORS is a browser thing. And as my ajax calls from another site on another origin is blocked to the API endpoints (which is great), I can still reach the same endpoints by using Postman or a HttpClient and GetAsync() calls.
My question is of it's possible to also block server-to-server calls (or Postman calls) to my API? Or like CORS, only allow certain origins?
Most of my endpoints are protected by a bearer JWT token, but I have an anonymous endpoint that I would like to let only origins I control (or can configure) to have access to that anonymous API.
I solved it after i bumped in to this post on stackoverflow:
How do you create a custom AuthorizeAttribute in ASP.NET Core?
I simply made a custom Authorize attribute [ApiAuthorize()], that I call this way:
[ApiController]
[ApiAuthorize(new string[] { "https://localhost:44351", "https://mysite.onthe.net" })]
public class MyInternalApiController : ControllerBase
{
...
}
It may also be implemented on the Action instead of the Controller. The implementation was done like this:
public class ApiAuthorizeAttribute : TypeFilterAttribute
{
public ApiAuthorizeAttribute(string[] origins) : base(typeof(ApiAuthorizeFilter))
{
Arguments = new object[] { origins };
}
}
public class ApiAuthorizeFilter : IAuthorizationFilter
{
readonly string[] _origins;
public ApiAuthorizeFilter(string[] origins)
{
_origins = origins;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
if (_origins == null)
return;
string referer = context.HttpContext.Request.Headers["Referer"].ToString();
if (string.IsNullOrWhiteSpace(referer) || !_origins.Any(origin => referer.StartsWith(origin, StringComparison.OrdinalIgnoreCase)))
context.Result = new ForbidResult();
}
}
Things to consider:
The implementation and check of the referer could be exact match instead of StartsWith
The handling could use RegEx or any good alternative to handle subdomains, wildcards etc
The referer could be translated to a Uri objects to get better results and variations
A jQuery ajax call gets a "403 - Forbidden" as expected, but Postman gets a "404 - Not Found". To me that does not matter, but that's something to look into if it matters.
But it covers what I need, so I'm happy with this.
I'm writing a .Net Web Api (2) that have this one POST method. This method is currently deserializing it's only parameter by using the standard JSON formatter. We are also writing the Client that will consume this Api a C# Client using System.Net.Http.HttpClient to communicate.
There is the potential to be moving a large volume of data. This made us look into reducing the footprint of the request.
After searching this site, I came across some alternatives using gzip compression. I already have a working proof of concept:
Client side something down the lines of this
Server side something down the lines of this
So, my question...
Do I really need to write all this custom code for this? Is there a built in way to accomplish lowering the footprint of the request?
Some articles that came across mention about enabling gzip (or deflate) in IIS (see Enable IIS7 gzip). This was not working for me (I enabled it, I'm still doing the compression on the Client side, removed the DelegatingHandler from the Server...but nothing, I end up with a null parameter in the controller method)
I ended up implementing a DelegatingHandler to look for a header with ContentEncoding "gzip" and decompress accordingly.
using System;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace WebApi.MessageHandlers
{
/// <summary>
/// GZip message handler.
/// </summary>
public class GZipMessageHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (IsRequestCompressed(request))
{
request.Content = Descompress(request.Content);
}
return base.SendAsync(request, cancellationToken);
}
private bool IsRequestCompressed(HttpRequestMessage request)
{
return request.Content.Headers.ContentEncoding.Contains("gzip", StringComparer.OrdinalIgnoreCase);
}
private HttpContent Descompress(HttpContent content)
{
// Handle compression...
throw new NotImplementedException();
}
}
}
I am trying to implement a simple Breeze controller in Asp.Net MVC4, but can't seem to access it. Is it possibly conflicting with .Net's standard Web.Api ?
If my url is http://localhost:49479/api/values then I get a good return value from Web Api.
However if my url is http://localhost:49479/breeze/Breeze I get "Http 404" error "Resource not found".
If my url is http://localhost:49479/breeze/Breeze/5 I get error No HTTP resource was found that matches the request URI 'http://localhost:49479/breeze/Breeze/5'.
Your advice is greatly appreciate.
Here's what I have in ..Controllers/BreezeController.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using Breeze.ContextProvider;
using Breeze.WebApi2;
using Newtonsoft.Json;
namespace RageSys.Controllers
{
[BreezeController]
public class BreeezeController : ApiController
{
// GET api/values
public string Get(int id)
{
return "value";
}
public IEnumerable<string> GetMtm(int id)
{
return new string[] { "value1", "value2" };
}
}
}
and in BreezeWebApiConfig.cs :
using System.Web.Http;
[assembly: WebActivator.PreApplicationStartMethod(
typeof(RageSys.App_Start.BreezeWebApiConfig), "RegisterBreezePreStart")]
namespace RageSys.App_Start {
///<summary>
/// Inserts the Breeze Web API controller route at the front of all Web API routes
///</summary>
public static class BreezeWebApiConfig {
public static void RegisterBreezePreStart() {
GlobalConfiguration.Configuration.Routes.MapHttpRoute(
name: "BreezeApi",
routeTemplate: "breeze/{controller}/{action}"
);
}
}
}
The result you are getting from your api/values request is not coming from the listed controller. You must have the default ValuesController and WebApiConfig (which defines a route that takes a parameter) still in your project.
You do not have a route for http://localhost:49479/breeze/Breeze/5. The third segment (currently 5) needs to be the name of an Action method. For you, that means GetMtm. You do not have a route that takes any parameters, so you'll get nothing from: http://localhost:49479/breeze/Breeze/GetMtm/5 unless you define such a route. You probably don't want to do this though, because Breeze coupled with Entity Framework will make life very easy. You should implement the simplest possible Breeze / Entity Framework application and see how it works from there.
If you are using parameters and using Breeze, then ensure you use the .withParameters({ ParameterName: "Fred"}) or .withParameters({ id: id-value}), for example, in your Breeze query and ensure the parameter name in your function to be called (GetMtm in your case) at the server matches the parameter name you are using at the client.