How should you build an OData URI?












4















I'm looking to build URIs such as https://example.com/data/customers?$top=100.



Is there a UriBuilder for creating OData URIs (i.e. which can handle characters such as $ appropriately)?



Full info



I have code like this (simplified example):



public Uri CreateMyApiUri(string rootUri, string apiPath, string entity, int pageSize)
{
var builder = new UriBuilder(rootUri);
builder.Path = ConcatPathParts(builder.Path, apiPath, entity); //basically string.Join("/", args), plus code to remove superfluous slashes
var parameters = HttpUtility.ParseQueryString(builder.Query);
if (pageSize > 0) parameters["$top"] = pageSize.ToString();
builder.Query = parameters.ToString();
return builder.Uri;
}
//called like this
var uri = CreateMyApiUri("https://example.com", "data", "customers", 100);


However, the OData special character $ gets encoded for use in the URI as %24.



I've found OData.Net on GitHub which seems a helpful library for such things, but it's not part of the standard library and looks quite heavyweight for my simple need, so I'm hoping to find something simpler before committing to that going down the OData.Net path...



Of course, I could avoid this by doing a simple var uri = string.Join("/", new {rootUri, apiPath, entity, $"?$top={pageSize}"});... but I want to ensure I'm taking advantage of the .net library's character escaping features / not creating a solution for something the framework already gives me.



NB: I'm aware that you can generate classes from OData services, but I don't want to use this approach since that requires that I regenerate the client code if the API is changed (e.g. new fields are added to the target entity). Instead I want to use a more "pure" HTTP approach.










share|improve this question

























  • OData publishes its schema. If the API changes, the schema will also change. That's no less "pure HTTP" than any other approach. In fact, GraphQL and Open API try to bring back that discoverable schema

    – Panagiotis Kanavos
    Nov 22 '18 at 9:26













  • @PanagiotisKanavos by "non pure HTTP" I mean where you have a C# client class, such as ProductClient in docs.microsoft.com/en-us/aspnet/web-api/overview/…. I'm writing middleware which picks up the data and converts it to XML, which can then be transformed via XLSTs before being consumed by other systems. By avoiding generated C# I don't have to redeploy assemblies; I can update the middleware using only configuration (XSLT / URI) for the affected service. There may be a better way though / I'm open to ideas.

    – JohnLBevan
    Nov 22 '18 at 10:09








  • 1





    that's not a matter of pureness, it's a matter of versioning that won't be solved by using XSLT or JSON Path - you need to know the schema to write the proper transformation. The ODATA client allows you to write LINQ queries, retrieve only the fields you want and map them to whatever client entity you want, or even use anonymous types. There's no real difference between an XSLT mapping and a LINQ query, except for ease of use, compile-time checking etc

    – Panagiotis Kanavos
    Nov 22 '18 at 10:53
















4















I'm looking to build URIs such as https://example.com/data/customers?$top=100.



Is there a UriBuilder for creating OData URIs (i.e. which can handle characters such as $ appropriately)?



Full info



I have code like this (simplified example):



public Uri CreateMyApiUri(string rootUri, string apiPath, string entity, int pageSize)
{
var builder = new UriBuilder(rootUri);
builder.Path = ConcatPathParts(builder.Path, apiPath, entity); //basically string.Join("/", args), plus code to remove superfluous slashes
var parameters = HttpUtility.ParseQueryString(builder.Query);
if (pageSize > 0) parameters["$top"] = pageSize.ToString();
builder.Query = parameters.ToString();
return builder.Uri;
}
//called like this
var uri = CreateMyApiUri("https://example.com", "data", "customers", 100);


However, the OData special character $ gets encoded for use in the URI as %24.



I've found OData.Net on GitHub which seems a helpful library for such things, but it's not part of the standard library and looks quite heavyweight for my simple need, so I'm hoping to find something simpler before committing to that going down the OData.Net path...



Of course, I could avoid this by doing a simple var uri = string.Join("/", new {rootUri, apiPath, entity, $"?$top={pageSize}"});... but I want to ensure I'm taking advantage of the .net library's character escaping features / not creating a solution for something the framework already gives me.



NB: I'm aware that you can generate classes from OData services, but I don't want to use this approach since that requires that I regenerate the client code if the API is changed (e.g. new fields are added to the target entity). Instead I want to use a more "pure" HTTP approach.










share|improve this question

























  • OData publishes its schema. If the API changes, the schema will also change. That's no less "pure HTTP" than any other approach. In fact, GraphQL and Open API try to bring back that discoverable schema

    – Panagiotis Kanavos
    Nov 22 '18 at 9:26













  • @PanagiotisKanavos by "non pure HTTP" I mean where you have a C# client class, such as ProductClient in docs.microsoft.com/en-us/aspnet/web-api/overview/…. I'm writing middleware which picks up the data and converts it to XML, which can then be transformed via XLSTs before being consumed by other systems. By avoiding generated C# I don't have to redeploy assemblies; I can update the middleware using only configuration (XSLT / URI) for the affected service. There may be a better way though / I'm open to ideas.

    – JohnLBevan
    Nov 22 '18 at 10:09








  • 1





    that's not a matter of pureness, it's a matter of versioning that won't be solved by using XSLT or JSON Path - you need to know the schema to write the proper transformation. The ODATA client allows you to write LINQ queries, retrieve only the fields you want and map them to whatever client entity you want, or even use anonymous types. There's no real difference between an XSLT mapping and a LINQ query, except for ease of use, compile-time checking etc

    – Panagiotis Kanavos
    Nov 22 '18 at 10:53














4












4








4








I'm looking to build URIs such as https://example.com/data/customers?$top=100.



Is there a UriBuilder for creating OData URIs (i.e. which can handle characters such as $ appropriately)?



Full info



I have code like this (simplified example):



public Uri CreateMyApiUri(string rootUri, string apiPath, string entity, int pageSize)
{
var builder = new UriBuilder(rootUri);
builder.Path = ConcatPathParts(builder.Path, apiPath, entity); //basically string.Join("/", args), plus code to remove superfluous slashes
var parameters = HttpUtility.ParseQueryString(builder.Query);
if (pageSize > 0) parameters["$top"] = pageSize.ToString();
builder.Query = parameters.ToString();
return builder.Uri;
}
//called like this
var uri = CreateMyApiUri("https://example.com", "data", "customers", 100);


However, the OData special character $ gets encoded for use in the URI as %24.



I've found OData.Net on GitHub which seems a helpful library for such things, but it's not part of the standard library and looks quite heavyweight for my simple need, so I'm hoping to find something simpler before committing to that going down the OData.Net path...



Of course, I could avoid this by doing a simple var uri = string.Join("/", new {rootUri, apiPath, entity, $"?$top={pageSize}"});... but I want to ensure I'm taking advantage of the .net library's character escaping features / not creating a solution for something the framework already gives me.



NB: I'm aware that you can generate classes from OData services, but I don't want to use this approach since that requires that I regenerate the client code if the API is changed (e.g. new fields are added to the target entity). Instead I want to use a more "pure" HTTP approach.










share|improve this question
















I'm looking to build URIs such as https://example.com/data/customers?$top=100.



Is there a UriBuilder for creating OData URIs (i.e. which can handle characters such as $ appropriately)?



Full info



I have code like this (simplified example):



public Uri CreateMyApiUri(string rootUri, string apiPath, string entity, int pageSize)
{
var builder = new UriBuilder(rootUri);
builder.Path = ConcatPathParts(builder.Path, apiPath, entity); //basically string.Join("/", args), plus code to remove superfluous slashes
var parameters = HttpUtility.ParseQueryString(builder.Query);
if (pageSize > 0) parameters["$top"] = pageSize.ToString();
builder.Query = parameters.ToString();
return builder.Uri;
}
//called like this
var uri = CreateMyApiUri("https://example.com", "data", "customers", 100);


However, the OData special character $ gets encoded for use in the URI as %24.



I've found OData.Net on GitHub which seems a helpful library for such things, but it's not part of the standard library and looks quite heavyweight for my simple need, so I'm hoping to find something simpler before committing to that going down the OData.Net path...



Of course, I could avoid this by doing a simple var uri = string.Join("/", new {rootUri, apiPath, entity, $"?$top={pageSize}"});... but I want to ensure I'm taking advantage of the .net library's character escaping features / not creating a solution for something the framework already gives me.



NB: I'm aware that you can generate classes from OData services, but I don't want to use this approach since that requires that I regenerate the client code if the API is changed (e.g. new fields are added to the target entity). Instead I want to use a more "pure" HTTP approach.







c# odata query-string querystringparameter uribuilder






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 22 '18 at 9:10







JohnLBevan

















asked Nov 21 '18 at 18:55









JohnLBevanJohnLBevan

14.5k146108




14.5k146108













  • OData publishes its schema. If the API changes, the schema will also change. That's no less "pure HTTP" than any other approach. In fact, GraphQL and Open API try to bring back that discoverable schema

    – Panagiotis Kanavos
    Nov 22 '18 at 9:26













  • @PanagiotisKanavos by "non pure HTTP" I mean where you have a C# client class, such as ProductClient in docs.microsoft.com/en-us/aspnet/web-api/overview/…. I'm writing middleware which picks up the data and converts it to XML, which can then be transformed via XLSTs before being consumed by other systems. By avoiding generated C# I don't have to redeploy assemblies; I can update the middleware using only configuration (XSLT / URI) for the affected service. There may be a better way though / I'm open to ideas.

    – JohnLBevan
    Nov 22 '18 at 10:09








  • 1





    that's not a matter of pureness, it's a matter of versioning that won't be solved by using XSLT or JSON Path - you need to know the schema to write the proper transformation. The ODATA client allows you to write LINQ queries, retrieve only the fields you want and map them to whatever client entity you want, or even use anonymous types. There's no real difference between an XSLT mapping and a LINQ query, except for ease of use, compile-time checking etc

    – Panagiotis Kanavos
    Nov 22 '18 at 10:53



















  • OData publishes its schema. If the API changes, the schema will also change. That's no less "pure HTTP" than any other approach. In fact, GraphQL and Open API try to bring back that discoverable schema

    – Panagiotis Kanavos
    Nov 22 '18 at 9:26













  • @PanagiotisKanavos by "non pure HTTP" I mean where you have a C# client class, such as ProductClient in docs.microsoft.com/en-us/aspnet/web-api/overview/…. I'm writing middleware which picks up the data and converts it to XML, which can then be transformed via XLSTs before being consumed by other systems. By avoiding generated C# I don't have to redeploy assemblies; I can update the middleware using only configuration (XSLT / URI) for the affected service. There may be a better way though / I'm open to ideas.

    – JohnLBevan
    Nov 22 '18 at 10:09








  • 1





    that's not a matter of pureness, it's a matter of versioning that won't be solved by using XSLT or JSON Path - you need to know the schema to write the proper transformation. The ODATA client allows you to write LINQ queries, retrieve only the fields you want and map them to whatever client entity you want, or even use anonymous types. There's no real difference between an XSLT mapping and a LINQ query, except for ease of use, compile-time checking etc

    – Panagiotis Kanavos
    Nov 22 '18 at 10:53

















OData publishes its schema. If the API changes, the schema will also change. That's no less "pure HTTP" than any other approach. In fact, GraphQL and Open API try to bring back that discoverable schema

– Panagiotis Kanavos
Nov 22 '18 at 9:26







OData publishes its schema. If the API changes, the schema will also change. That's no less "pure HTTP" than any other approach. In fact, GraphQL and Open API try to bring back that discoverable schema

– Panagiotis Kanavos
Nov 22 '18 at 9:26















@PanagiotisKanavos by "non pure HTTP" I mean where you have a C# client class, such as ProductClient in docs.microsoft.com/en-us/aspnet/web-api/overview/…. I'm writing middleware which picks up the data and converts it to XML, which can then be transformed via XLSTs before being consumed by other systems. By avoiding generated C# I don't have to redeploy assemblies; I can update the middleware using only configuration (XSLT / URI) for the affected service. There may be a better way though / I'm open to ideas.

– JohnLBevan
Nov 22 '18 at 10:09







@PanagiotisKanavos by "non pure HTTP" I mean where you have a C# client class, such as ProductClient in docs.microsoft.com/en-us/aspnet/web-api/overview/…. I'm writing middleware which picks up the data and converts it to XML, which can then be transformed via XLSTs before being consumed by other systems. By avoiding generated C# I don't have to redeploy assemblies; I can update the middleware using only configuration (XSLT / URI) for the affected service. There may be a better way though / I'm open to ideas.

– JohnLBevan
Nov 22 '18 at 10:09






1




1





that's not a matter of pureness, it's a matter of versioning that won't be solved by using XSLT or JSON Path - you need to know the schema to write the proper transformation. The ODATA client allows you to write LINQ queries, retrieve only the fields you want and map them to whatever client entity you want, or even use anonymous types. There's no real difference between an XSLT mapping and a LINQ query, except for ease of use, compile-time checking etc

– Panagiotis Kanavos
Nov 22 '18 at 10:53





that's not a matter of pureness, it's a matter of versioning that won't be solved by using XSLT or JSON Path - you need to know the schema to write the proper transformation. The ODATA client allows you to write LINQ queries, retrieve only the fields you want and map them to whatever client entity you want, or even use anonymous types. There's no real difference between an XSLT mapping and a LINQ query, except for ease of use, compile-time checking etc

– Panagiotis Kanavos
Nov 22 '18 at 10:53












1 Answer
1






active

oldest

votes


















0














I found a solution; I didn't need a special ODataUriBuilder; rather there was a bug in my use of query.ToString(), as explained here: https://stackoverflow.com/a/26789977/361842



Applying that fix to the above code solves the issue:



public Uri CreateMyApiUri(string rootUri, string apiPath, string entity, int pageSize)
{
var builder = new UriBuilder(rootUri);
builder.Path = ConcatPathParts(builder.Path, apiPath, entity); //basically string.Join("/", args), plus code to remove superfluous slashes
var parameters = HttpUtility.ParseQueryString(builder.Query);
if (pageSize > 0) parameters["$top"] = pageSize.ToString();

//the fix:
builder.Query = Uri.EscapeUriString(HttpUtility.UrlDecode(parameters.ToString()));
//instead of:
//builder.Query = parameters.ToString();

return builder.Uri;
}
//called like this
var uri = CreateMyApiUri("https://example.com", "data", "customers", 100);





share|improve this answer























    Your Answer






    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "1"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














    draft saved

    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53418841%2fhow-should-you-build-an-odata-uri%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    1 Answer
    1






    active

    oldest

    votes








    1 Answer
    1






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    0














    I found a solution; I didn't need a special ODataUriBuilder; rather there was a bug in my use of query.ToString(), as explained here: https://stackoverflow.com/a/26789977/361842



    Applying that fix to the above code solves the issue:



    public Uri CreateMyApiUri(string rootUri, string apiPath, string entity, int pageSize)
    {
    var builder = new UriBuilder(rootUri);
    builder.Path = ConcatPathParts(builder.Path, apiPath, entity); //basically string.Join("/", args), plus code to remove superfluous slashes
    var parameters = HttpUtility.ParseQueryString(builder.Query);
    if (pageSize > 0) parameters["$top"] = pageSize.ToString();

    //the fix:
    builder.Query = Uri.EscapeUriString(HttpUtility.UrlDecode(parameters.ToString()));
    //instead of:
    //builder.Query = parameters.ToString();

    return builder.Uri;
    }
    //called like this
    var uri = CreateMyApiUri("https://example.com", "data", "customers", 100);





    share|improve this answer




























      0














      I found a solution; I didn't need a special ODataUriBuilder; rather there was a bug in my use of query.ToString(), as explained here: https://stackoverflow.com/a/26789977/361842



      Applying that fix to the above code solves the issue:



      public Uri CreateMyApiUri(string rootUri, string apiPath, string entity, int pageSize)
      {
      var builder = new UriBuilder(rootUri);
      builder.Path = ConcatPathParts(builder.Path, apiPath, entity); //basically string.Join("/", args), plus code to remove superfluous slashes
      var parameters = HttpUtility.ParseQueryString(builder.Query);
      if (pageSize > 0) parameters["$top"] = pageSize.ToString();

      //the fix:
      builder.Query = Uri.EscapeUriString(HttpUtility.UrlDecode(parameters.ToString()));
      //instead of:
      //builder.Query = parameters.ToString();

      return builder.Uri;
      }
      //called like this
      var uri = CreateMyApiUri("https://example.com", "data", "customers", 100);





      share|improve this answer


























        0












        0








        0







        I found a solution; I didn't need a special ODataUriBuilder; rather there was a bug in my use of query.ToString(), as explained here: https://stackoverflow.com/a/26789977/361842



        Applying that fix to the above code solves the issue:



        public Uri CreateMyApiUri(string rootUri, string apiPath, string entity, int pageSize)
        {
        var builder = new UriBuilder(rootUri);
        builder.Path = ConcatPathParts(builder.Path, apiPath, entity); //basically string.Join("/", args), plus code to remove superfluous slashes
        var parameters = HttpUtility.ParseQueryString(builder.Query);
        if (pageSize > 0) parameters["$top"] = pageSize.ToString();

        //the fix:
        builder.Query = Uri.EscapeUriString(HttpUtility.UrlDecode(parameters.ToString()));
        //instead of:
        //builder.Query = parameters.ToString();

        return builder.Uri;
        }
        //called like this
        var uri = CreateMyApiUri("https://example.com", "data", "customers", 100);





        share|improve this answer













        I found a solution; I didn't need a special ODataUriBuilder; rather there was a bug in my use of query.ToString(), as explained here: https://stackoverflow.com/a/26789977/361842



        Applying that fix to the above code solves the issue:



        public Uri CreateMyApiUri(string rootUri, string apiPath, string entity, int pageSize)
        {
        var builder = new UriBuilder(rootUri);
        builder.Path = ConcatPathParts(builder.Path, apiPath, entity); //basically string.Join("/", args), plus code to remove superfluous slashes
        var parameters = HttpUtility.ParseQueryString(builder.Query);
        if (pageSize > 0) parameters["$top"] = pageSize.ToString();

        //the fix:
        builder.Query = Uri.EscapeUriString(HttpUtility.UrlDecode(parameters.ToString()));
        //instead of:
        //builder.Query = parameters.ToString();

        return builder.Uri;
        }
        //called like this
        var uri = CreateMyApiUri("https://example.com", "data", "customers", 100);






        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered Nov 22 '18 at 9:18









        JohnLBevanJohnLBevan

        14.5k146108




        14.5k146108
































            draft saved

            draft discarded




















































            Thanks for contributing an answer to Stack Overflow!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53418841%2fhow-should-you-build-an-odata-uri%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            MongoDB - Not Authorized To Execute Command

            How to fix TextFormField cause rebuild widget in Flutter

            in spring boot 2.1 many test slices are not allowed anymore due to multiple @BootstrapWith