According to this tutorial: http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/using-select-expand-and-value
"Web API 2 adds support for the $expand, $select, and $value options in OData. These options allow a client to control the representation that it gets back from the server"
My question it, how can I disable the representation manipulation done at the client side. In other words, my server makes sure that filtering/selecting etc. are done properly, and thus I do not want the client side to do it again. It is more of an overhead.
I think you misunderstand the purpose of query options like $expand, $select, etc. They do not cause data to be manipulated on the client. Rather, they are instructions to the service. In the Web API OData implementation, query options are typically handled by the EnableQuery attribute or the Queryable attribute. If you don't use these attributes, then you are responsible for writing the code that handles query options. Or you are free to not support them.
In your controller action, like get method, add attribute [EnableQuery] (this is for OData v4)
IN your client, send out request like ~/EntitySet?$filter=... & $select = ...
Then the response will only contain the filtered and select content.
Refer to https://github.com/OData/ODataSamples/tree/master/WebApi/v4/ODataQueryableSample to see the example.
You can create a custom attribute which would inherit from EnableQueryAttribute and then override the ValidateQuery method to limit the allowed query options as well as allowed functions and page size.
using System.Net.Http;
using System.Web.OData;
using System.Web.OData.Query;
public class SecureApiQueryAttribute : EnableQueryAttribute
{
public override void ValidateQuery(HttpRequestMessage request, ODataQueryOptions queryOptions)
{
base.AllowedQueryOptions = AllowedQueryOptions.None;
base.PageSize = 30;
base.AllowedFunctions = AllowedFunctions.AllFunctions;
base.ValidateQuery(request, queryOptions);
}
}
Then you can use this custom attribute like this
[SecureApiQuery]
public IHttpActionResult Get([FromODataUri] int? key = null)
{
}
Related
I'm surely missing something, because most questions around 415 error are referring to POST requests.
In this case, this is a very simple GET request, which works when I enumerate all action parameters, but if I create a simple DTO to contain all of these, I start receiving a 415 error.
I'm calling the api with https://localhost:555/some/test?field1=aaa&field2=1
This works:
[ApiController]
public class SomeController : ControllerBase
{
[Route("Test")]
public SomeResponse GetSomeResponse(string field1, int field2)
{
return new SomeResponse(field1, field2);
}
}
But this doesn't:
[ApiController]
public class SomeController : ControllerBase
{
[Route("Test")]
public SomeResponse GetSomeResponse(SomeRequest request)
{
return new SomeResponse(request.Field1, request.Field2);
}
}
public class SomeRequest
{
public string Field1 { get; set; }
public int Field2 { get; set; }
}
public class SomeResponse
{
public Someresponse(string field1, int field2)
{
Field1 = field1;
Field2 = field2;
}
public string Field1 { get; set; }
public int Field2 { get; set; }
}
The controller class is only using Microsoft.AspNetCore.Mvc;
When I try to use SomeRequest class, the API answers "415 - Unsuported Media Type"
The only difference is the way to receive the values, I'm not switching from uri to body (which could be json or plain text, etc.)
But since I'm not using the body, I can't understand which media-type it is referring to
My startup class is the same as the WeatherForecast, created with the project template ASP.NET Core Web API, with Visual Studio 2022, .Net6
Well, a possible solution is to specify [FromQuery]:
public SomeResponse GetSomeResponse([FromQuery] SomeRequest request)
Tho, I am not very happy with this as reaction to "Unsuported Format", so, other suggestions are welcome.
Ben Foster has a great writeup on this in Custom Model Binding in ASP.NET 6.0 Minimal APIs
I haven't tried it myself yet but adding this TryParse method to your SomeRequest type might help:
static bool TryParse(string? value, IFormatProvider? provider, out T parameter)
Generally I try not to bind objects to HttpGet because standard object serialization uses characters that have other meanings in standard URL routing and therefore need to be escaped in the url, which just over complicates the server and the client implementation.
I support the concept for common complex type style structures like Point that might be reused thought you application in many controllers and endpoints, but to create an object wrapper for each request would be ludicrous, that is a common client side technique in generated clients but we try to avoid that on the server-side.
I recommend against binding objects as GET parameters as it is a non-standard style of code that introduces a level of technical debt to the solution both in terms of API management and client implementation.
Even if you use custom binders or route handlers these solutions end up being a lot more custom code than it would have been to use primitive parameters and map them to your preferred type implementation in the first line of your endpoint method.
You should use the HttpGetAttribute to bind the request to query string parameters and specifically scope the requests so that only GET is allowed:
[HttpGet("Test")]
public SomeResponse GetSomeResponse2(SomeRequest request)
{
return new SomeResponse(request.Field1, request.Field2);
}
It is subtle but since .Net 5 (when FromUriAttribute was replaced and the various OData and MVC routing mechanisms were combined into a common pipeline) we are encouraged to use HttpGetAttribute (Or the other Http Method variants) instead of RouteAttribute as a way of minimising the configuration.
This code has similar functionality to the answer provided by #Zameb but it prevents users from attempting to use POST to access this endpoint. [FromQuery] creates a controller that even when a JSON object is POSTed to the endpoint the parameters are specifically mapped to the Query Parameters.
i'm evaluating Breeze.Js for a large enterprise, data oriented, Angular 5 application in order to take advantage of the following features that are missing in the vanilla Angular framework:
client side data store
client side model state tracking
client side model validation rules
bulk data persistence (SaveChanges() method to persist all entities).
For test purposes i've written the following simple BreezeController in my ASP.NET WebApi + EntityFramework server side:
[EnableCors(origins: "*", headers: "*", methods: "*")]
[BreezeController]
public class PeopleController : ApiController
{
private AdventureWorksDbContext db = new AdventureWorksDbContext();
#region "Breeze"
readonly EFContextProvider<AdventureWorksDbContext> _contextProvider =
new EFContextProvider<AdventureWorksDbContext>();
// ~/breeze/todos/Metadata
[HttpGet]
public string Metadata()
{
return System.Text.Encoding.UTF8.GetString(AdventureWorks.WebApi.Properties.Resources.WebApiMetadata);
}
// ~/breeze/todos/Todos
// ~/breeze/todos/Todos?$filter=IsArchived eq false&$orderby=CreatedAt
[HttpGet]
public IQueryable<PersonDTO> GetPeople()
{
return db.People.ProjectTo<PersonDTO>();
}
// ~/breeze/todos/SaveChanges
[HttpPost]
public SaveResult SaveChanges(Newtonsoft.Json.Linq.JObject saveBundle)
{
return _contextProvider.SaveChanges(saveBundle);
}
#endregion
}
As you can see in my example (it uses AdventureWorks DB) i've done the following modifications:
1) "GetPeople()" endpoint returns a queryable of DTO ("ProjectTo" extension is provided by Automapper). I need to do this in order to shape the model in a usable way for the client, avoid recursions, deep dive in the schema, big fields serialization and so on.
2) "Metadata()" endpoint returns a string resource that represents metadata of the DTO class. I builded it using "PocoMetadata" tool of the "Breeze Tooling Suite" (https://github.com/Breeze/breeze.tooling). This is needed because i can't return the _contextProvider.Metadata() result as long as i'm using DTO's and not EF POCO class.
Now, if in my Angular 5 client i issue an ODATA query like the following i can see that executeQuery() method actually works:
export class BreezeDataStoreComponent implements OnInit {
private _em: EntityManager;
constructor() {
this._em = new EntityManager({
serviceName: 'http://localhost:31328/breeze/People'
});
}
ngOnInit() {
const query = EntityQuery.from('GetPeople')
.where('FirstName', FilterQueryOp.StartsWith, 'F')
.orderBy('LastName', true);
this._em.executeQuery(query).then(res => {
// Here i can get all People instances.
// Now i try to get the first, edit the name and saveChanges.
(res.results[0] as any).FirstName = 'Franklino';
this._em.saveChanges().then(saveResult => {
const test = saveResult.entities;
});
});
}
}
Unfortunately problems comes with SaveChanges().
When the Angular client calls that method, in my server side i get the following error:
System.InvalidOperationException: Sequence contains no matching
element
I think it's due to the fact that i'm calling SaveChanges() over an EF context provider passing a JObject bundle referred to DTO instead of POCO class.
So my question is:
Is it possible to use BreezeJs query and bulk persistence (SaveChanges() method) using DTO's? It's a pretty common need in big data-centric enterprise applications since i think it's a bad practice exposing EF POCOs on WebApi.
should i rely instead over a classic WebApi that respond to the POST\PUT\DELETE HTTP verbs? In that case, how to configure Breeze client in order to contact those endpoints instead of "SaveChanges" when persisting data?
If Breeze is not suitable for this needs are there other technolgies that provides the 4 abovementioned points?
Thank you very much.
To make SaveChanges work with your DTOs, you would need to either
Write your own method to unpack the JObject saveBundle, or
Use the BeforeSaveChanges method to modify the dictionary of DTOs and replace them with entities that EF understands.
Number 2 seems like the better choice. If you do not have a 1:1 match between entities and DTOs, some logic would be required when doing the mapping.
I have to do paging for an odata endpoint built using Entity Framework . I know I can do it using
private ODataQuerySettings settings = new ODataQuerySettings();
settings.PageSize = myPageSize; // I keep this value in web.config of solution
and
options.ApplyTo(IQueryable, settings);
But I am constrained not to use ApplyTo (i.e. I don't want to use the settings above) and take the page size from the web.config of my solution without modifying the url presented by the web api i.e. no client size paging.
So, far I haven't found a way to do this. I can't put page size in [ Enable Query ] as that is not dynamically lifting page size parameter from web.config.
I wonder if what I want could be done or am I trying to do something too tricky.
You can extend the default behavior of the EnableQuery attribute to use web.config's value as you want. Maybe something like this:
public class EnablePagedQueryAttribute : EnableQueryAttribute
{
public EnablePagedQueryAttribute()
{
int myPageSizeFromWebConfig = 0;
// Get value from web.config as you want:
if (int.TryParse(ConfigurationManager.AppSettings["myPageSize"], out myPageSizeFromWebConfig))
{
this.PageSize = myPageSizeFromWebConfig;
}
}
}
I have a solution which includes a thick client (implemented using CefSharp for the majority of the user interface), and the javascript application needs to execute some C# logic in the application hosting the CEF browser. I considered using WebView.RegisterJsObject(), but I can write less glue code if I can just use $.ajax() from the html pages.
I already have ServiceStack set up for the web services and the web client in this solution. I'd like to route requests from the CEF browser to a local ServiceStack host (without actually using http).
Here's some psuedo code to illustrate what I would like to do:
public partial class MainWindow : IRequestHandler {
WebView _webView;
CefSharpServiceStackHost _serviceHost;
public MainWindow() {
// initialize CefSharp...
_webView.RequestHandler = this;
// initialize ServiceStackHost...
}
// other IRequestHandler methods...
// method this intercepts ajax calls from the CEF browser
public bool OnBeforeResourceLoad(IWebBrowser browser, IRequestResponse requestResponse) {
// translate CefSharp.IRequestResponse to ServiceStack.IRequest or HttpRequest
// should execute HelloService.Any() for the requestResponse.Url = "/hello/Zach"
var response = _serviceHost.ExecuteService(Translate(requestResponse));
requestResponse.RespondWith(response.Stream);
return false;
}
}
[Route("/hello/{Name}")]
public class Hello {
public string Hello { get; set; }
}
public class HelloService {
public object Any(Hello request) { // ... }
}
The part I can't figure out is how to extend ServiceStackHost so I can pass some sort of request object to it. Is this even possible?
This might be a stupid answer, but why not just use http anyway? The web is so heavily based on it that things actually gets easier if you use it even in cases like this (where it isn't really necessary).
If this isn't OK, you can implement a custom scheme handler that routes requests to foo://bar to your C# code, and do whatever you like. The CefSharp.Wpf.Example has an example custom scheme handler, so it should help you along the way.
What you're after sounds similar to how MQ Servers execute services in ServiceStack by simply routing messages to:
ServiceController.ExecuteMessage(IMessage)
There are a number of other API's on ServiceController you can use to execute requests in ServiceStack:
//Execute the Request DTO with an empty Request context:
object Execute(object requestDto)
//Execute the Request DTO with the supplied Request context:
object Execute(object requestDto, IRequest request)
For the IRequest context, you can use the built-in BasicRequest class, or your own that implements IRequest.
I have an InstaSharp.Endpoints.Relationships.Authenticated object EP_RELATIONSHIPS, and I can call EP_RELATIONSHIPS.Follows() to get a list of users I'm following. I follow a few hundred people, but I only get a result of 50.
When I check the JSON data on the Instagram API page using the API Console, I can see that there's a pagination URL.
Other return objects such as InstaSharp.Model.Responses.MediasResponse have an object called .Pagination that seem to provide this functionality.
Is this library incomplete? Why is there no pagination in the Relationships endpoint response and how can I accomplish pagination without having to re-write my own version of this part of InstaSharp?
The latest version of Instasharp (https://github.com/InstaSharp/InstaSharp) has the 'Pagination' property in the class.
There is also an implementation of the pagination being used to return multiple page sets in the library also in the Tags.RecentMultiplePages(..) method, which could in the future be made more generic and rolled out to multiple methods.
You could create your own object that has the pagination -> next_url | next_cursor. Grab the json from the response and Deserialize it into your own object..
To further clarify Damian's answer, if you look at the unit tests on the InstaSharp github, you can see an example of how to use Pagination:
public async Task Follows_NextCursor()
{
//This test will fail if testing with an account with less than one page of follows
var result = await relationships.Follows();
result = await relationships.Follows(457273003/*ffujiy*/, result.Pagination.NextCursor);
Assert.IsTrue(result.Data.Count > 0);
}