Coder Social home page Coder Social logo

iwate / odatahttpclient Goto Github PK

View Code? Open in Web Editor NEW
25.0 8.0 12.0 157 KB

The simplest implementation of OData client.

Home Page: https://iwate.github.io/ODataHttpClient/

License: MIT License

C# 100.00%
odata odata-client simple csharp dotnet

odatahttpclient's Introduction

Test codecov NuGet version

The simplest implementation of OData client.

Install

$ dotnet add package ODataHttpClient
PS> Install-Package  ODataHttpClient

Restrictions

  • Not support XML (JSON only)
  • Not support Query Builder

Usage

Simple Request

var client = new HttpClient();
var odata = new ODataClient(client);
var request = Request.Get($"{endpoint}/Products?$count=true");
var response = await odata.SendAsync(request);

if (response.Success) 
{
    var total = response.ReadAs<int>("$['@odata.count']");
    var products = response.ReadAs<IEnumerable<Product>>("$.value");
}

Batch Request

var client = new HttpClient();
var odata = new ODataClient(client);
var batch = new BatchRequest($"{endpoint}/$batch")
{
    Requests = new []
    {
        Request.Post($"{endpoint}/Products", new 
        { 
            Name = "Sample"
        }),
        Request.Post($"{endpoint}/$1/Categories/$ref", new Dictionay<string, string>
        {
            ["@odata.id"] = $"{endpoint}/Categories(0)"
        })
    }
};

var responses = await odata.SendAsync(batch);
// You can also use BatchAsync. 
// var responses = await odata.BatchAsync(batch);

if (responses.All(res => res.Success)) 
{
    var id = response.First().ReadAs<int>("$.Id");
}

Parameterized Uri

var client = new HttpClient();
var odata = new ODataClient(client);
var request = Request.Get($"{endpoint}/Products?$filter=ReleaseDate ge @Date", new { Date = new DateTimeOffset(new DateTime(2000, 1, 1)) });
var response = await odata.SendAsync(request);

if (response.Success) 
{
    ...
}

And you can use @@ as escape for @.

Set OData Element Type

In default, OData element type is not contained in payload. If you want add element type, you sould use type parameter of Request factory method.

Request.Post("...", payload, type: "ODataDemo.Product")

Change OData Element Type Key

In default, OData element type key is odata.type. If you want change key to other, you should use typeKey parameter of Request factory method.

Request.Post("...", payload, type: "ODataDemo.Product", typeKey: "@odata.type")

Use for OData v3(v2)

If you use for OData v3(v2), you have to change serializer and parametalizer.

1. Global level settings

ODataClient.UseHistoricalGlobal();

2. Client level settings

not yet. If you want, please create issue ticket.

Credential

Basic Auth

var odata = new ODataClient(client, "username", "password");

Custom

You should modify default settings of client which be passed to ODataClient constructor or implemet ICredentialBuilder

public class CustomCredentialBuilder : ODataHttpClient.Credentials.ICredentialBuilder
{
    public void Build(HttpClient client, HttpRequestMessage message)
    {
        // implement custom credential logic.
    }
}

And pass the builder instance to 2nd parameter constructor.

var odata = new ODataClient(client, new CustomCredentialBuilder());

Json Serializer Settings

In default, ODataHttpClient use general json format. If you change OData V2 JSON Serialization Format, can select a way of three.

1. Global level settings

ODataHttpClient.Serializers.JsonSerializer.Default = ODataHttpClient.Serializers.JsonSerializer.Historical;

2. Instance level settings

var odata = new ODataHttpClient(httpClient, ODataHttpClient.Serializers.JsonSerializer.Historical);
var request = odata.RequestFacotry.Get("..."); // MUST use RequestFactory

3. Request level settings

var serializer = ODataHttpClient.Serializers.JsonSerializer.Historical;
var request = Request.Get("...", serializer); // pass serializer
var response = await odata.SendAsync(request);
var data = response.ReadAs<dynamic>(serializer); // pass serializer

NotFound(404)

In default, ODataHttpClient decide 404 response code of GET and HEAD request to success. If you change to error, can select a way of followings.

var request = Request.Get("...", acceptNotFound: false);
var response = await odata.SendAsync(request);

When a response code of other HTTP methods, like as POST,PUT,PATCH and DELETE, is 404, ODataHttpClient set Response.Success false.

odatahttpclient's People

Contributors

iwate avatar marcnet90 avatar psmolich79 avatar xin9le avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

odatahttpclient's Issues

Support for List<T>?

I tried creating a PATCH request for a list of objects like this:

var listOfObjects = new List() { new SomeObject() { Property1 = "test1" }, new SomeObject() { Property1 = "test2" } };
Request.Patch<List>("some_uri", listOfObjects);

and got the following error:

One or more errors occurred. (Object serialized to Array. JObject instance expected.)

Does the Create method of the Request class need to support JArray.FromObject(...)?

If T can be enumerated, it can not be deserialized using ReadAs<T>.

Usage environment

  • version : 1.6.3
  • platform : aspnetcore2.1 and LINQPad

Reproduction procedure

  1. Create Get request.
  2. Call SendAsync.
  3. Deserialize the response to IEnumerable<T>
ODataHttpClient.Serializers.JsonSerializer.Default = ODataHttpClient.Serializers.JsonSerializer.General;
var client = new ODataClient(new HttpClient(), user, pass);

var uri = "http://example.com/data/ProductContents?$filter=Id eq 1 or Id eq 2";
var request = Request.Get(uri);
var response = await client.SendAsync(request);
var values = response.ReadAs<IEnumerable<ProductContent>>("$.value");

Error Detail

Throw JsonSerializationException

Exception message

Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'ProductContent' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List<T> that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.Path 'value', line 1, position 94.

Response Body

{"@odata.context":"http://example.com/data/ProductContents/$metadata#ProductContents","value":[{"Id":1,"ProductManageId":1,"ContentType":1,"Heading":"empty","Content":"hogehoge","SortOrder":0},{"Id":2,"ProductManageId":1,"ContentType":2,"Heading":"empty","Content":"hogehoge","SortOrder":0}]}

It is possible to deserialize using JToken or "JObject.ToObjedct ()" without using the ReadAs method.

JObject Pattern

var values = response.ReadAs<JArray>("$.value");
var objects = values.Select(v => ((JObject)v).ToObject<ProductContent>());

JToken Pattern

private static readonly JsonSerializerSettings _general = new JsonSerializerSettings
{
  Converters ={ new ByteArrayConverter() }
};

var token = JToken.Parse(response.Body).SelectToken("$.value");
var objects = token.ToObject<IEnumerable<ProductContent>>(Newtonsoft.Json.JsonSerializer.Create(_general));

Add custom header field to each request

Issuehunt badges

When use following features, need to set field in request headers.

If-Match
If-None-Match

Plan

  • add Headers property to Request class
  • add headers parameters Request factory method
  • set Headers to HttpRequestMessage instance in Request#CreateMethod
  • add Headers Property to Response class
  • copy headers to Response from HttpResponseMessage

IssueHunt Summary

marcnet80 marcnet80 has been rewarded.

Backers (Total: $2.00)

Submitted pull Requests


Tips


IssueHunt has been backed by the following sponsors. Become a sponsor

Batch operation results in 'Error parsing HTTP message header byte 101 of message System.Byte[].'

When calling an endpoint that contains a non 200 response for one of the calls, I get back
Error parsing HTTP message header byte 101 of message System.Byte[].

Note, not for all calls.

at System.Net.Http.HttpContentMessageExtensions.<ReadAsHttpResponseMessageAsyncCore>d__19.MoveNext() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at ODataHttpClient.ODataClient.<ParseMultiAsync>d__18.MoveNext()

Quite possibly similar to the problem seen here
https://forums.asp.net/t/2037342.aspx?Max+header+size+when+serializing+HttpMessageContent

Note, this is for an OData 2.0 endpoint.

System.Text.Json Support?

Hello,

Thank you for ODataHttpClient! I was wondering if you have any plans to support serialization using System.Text.Json?

Thanks,
Ben

Responses to Multiple requests in a changeset

When I send a request like:

--batch

--changeset

POST ...

--changeset

POST ...

--changeset--

--batch--

The response that comes back from (await odata.SendAsync(batch, stoppingToken)) only includes a single response (The batch response). I need to see the responses to my individual requests (POST) here.

Any advice @iwate ?

Is it support odata open type ?

Is it support odata open type out of the box as Newtonsoft json serialize and deserialize does not support it ?

Is there any special serialization setting which we need to provide ?

Boolean values have wrong format on german system

Hi @iwate,

first thanks for your very clean and simple code, it was a pleasant read.

I found an issue, that will probably only occur on non english systems like my german one.
Boolean values will be formated as True or False (please notice the uppercase leading character),
which seems not to be the standard format for OData, which mandates all lowercase.

A simple fix which works is to treat booleans as a separate type in the parameterizers

case bool val: return Literal(val);

private string Literal(bool value) => value ? "true" : "false";

When the tests run on netcoreapp2.1, batch request tests faild.

Reason: Default Http Version of HttpRequestMessage is 2.0 on netcoreapp2.1

Request

POST https://services.odata.org/V3/(S(ni3esrvxcoxxfea2kdchzo0o))/OData/OData.svc/$batch HTTP/1.1
Accept: application/json
Content-Type: multipart/mixed; boundary="batch6ce90353-0282-40cc-b6c5-8bc509d7c2ed"
Content-Length: 343
Host: services.odata.org

--batch6ce90353-0282-40cc-b6c5-8bc509d7c2ed
Content-Type: application/http
Content-Transfer-Encoding: binary

GET /V3/(S(ni3esrvxcoxxfea2kdchzo0o))/OData/OData.svc/Products HTTP/2.0
Host: services.odata.org
Accept: application/json, application/octet-stream, text/plain
Content-ID: 1


--batch6ce90353-0282-40cc-b6c5-8bc509d7c2ed--

Response

HTTP/1.1 202 Accepted
Cache-Control: no-cache
Content-Length: 1250
Content-Type: multipart/mixed; boundary=batchresponse_48558045-855d-428a-8375-aecd54694e61
Server: Microsoft-IIS/10.0
X-Content-Type-Options: nosniff
DataServiceVersion: 1.0;
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET
Access-Control-Allow-Headers: Accept, Origin, Content-Type, MaxDataServiceVersion
Access-Control-Expose-Headers: DataServiceVersion
Date: Tue, 13 Nov 2018 07:55:36 GMT

--batchresponse_48558045-855d-428a-8375-aecd54694e61
Content-Type: application/http
Content-Transfer-Encoding: binary

HTTP/1.1 400 Bad Request
DataServiceVersion: 1.0;
Content-Type: application/xml;charset=utf-8

<?xml version="1.0" encoding="utf-8"?><m:error xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"><m:code /><m:message xml:lang="en-US">An error occurred while processing this request.</m:message><m:innererror><m:message>The HTTP version 'HTTP/2.0' used in a batch operation request or response is not valid. The value must be 'HTTP/1.1'.</m:message><m:type>Microsoft.Data.OData.ODataException</m:type><m:stacktrace>   at Microsoft.Data.OData.ODataBatchReader.ParseRequestLine(String requestLine, String&amp; httpMethod, Uri&amp; requestUri)&#xD;
   at Microsoft.Data.OData.ODataBatchReader.CreateOperationRequestMessageImplementation()&#xD;
   at Microsoft.Data.OData.ODataBatchReader.InterceptException[T](Func`1 action)&#xD;
   at Microsoft.Data.OData.ODataBatchReader.CreateOperationRequestMessage()&#xD;
   at System.Data.Services.DataService`1.BatchDataService.HandleBatchContent(Stream responseStream)</m:stacktrace></m:innererror></m:error>
--batchresponse_48558045-855d-428a-8375-aecd54694e61--

ODataParameterizer unintentionally replaces `$$` with `$`

Summary

ODataParameterizer and ODataV4Parameterizer replaces $$ in string with $. This is because Regex.Replace replaces the $ character. We expect that any string containing $$ will be expanded directly into the query.

Reproduction Code

// new { x = "$$abc$$" }
$filter=value eq @x
// $ is vanished
$filter=value eq '$abc$'

OData Batches and Content-ID

I'd like to use the Content-ID from the batches coming back so that we can correlate a failed response with the corresponding request.

Using Fiddler, the target application provides this as the raw text response for a batch with 5 items (with the 4th set to fail):

--batchresponse_2bbf2f24-1194-4138-92f0-527b237a68a5
Content-Type: multipart/mixed; boundary=changesetresponse_869711e5-41d1-4301-8510-3eb4b6a0c9b8

--changesetresponse_869711e5-41d1-4301-8510-3eb4b6a0c9b8
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 4

HTTP/1.1 500 Internal Server Error
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{
  "error":{

I've pulled the source and it looks like in ParseMultiAsync(...) it's available in content.Headers in this block:

if (content.Headers.ContentType.MediaType == "application/http")
                {
                    if (!content.Headers.ContentType.Parameters.Contains(_responseMsgType))
                        content.Headers.ContentType.Parameters.Add(_responseMsgType);
                    
                    var part = await content.ReadAsHttpResponseMessageAsync();

                    //headers.Add("Content-ID", content.Headers.GetValues("Content-ID"));
                    result.Add(await ParseAsync(part.StatusCode, part.Content, headers));
                }

However, it's not in the headers that are added into the Response.

What is the recommended approach for tackle this? I'm happy to modify the source code and submit a pull-request, but I also want to make sure to stay true to the minimalist vision of this library.

If there's some way to solve that doesn't involve modifying source, that would be really helpful to know.

Thank you.

Batch request with System.InvalidOperationException: This operation is not supported for a relative URI

Hi Team,
I have a question about creating a $batch of items using OdataClient However we get the exception : System.InvalidOperationException: This operation is not supported for a relative URI.
var responses = await _oDataClient.SendAsync(batch).ConfigureAwait(false);

I have a function to init the batch variable:
public BatchRequest InitBatchRequest (List myItems){
var batch = new BatchRequest(_uriBatch);
foreach (var item in myItems)
{
var req = Request.Post(_uriSingle, item);
batch.Requests.Add(req);
}
return batch;
}
I appreciate your any insight to resolve the issue.
Thank you
Nhat

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.