Cleanest way to implement multiple parameters filters in a REST API











up vote
0
down vote

favorite












I am currently implementing a RESTFUL API that provides endpoints to interface with a database .



I want to implement filtering in my API , but I need to provide an endpoint that can provide a way to apply filtering on a table using all the table's columns.



I've found some patterns such as :



GET /api/ressource?param1=value1,param2=value2...paramN=valueN


param1,param2...param N being my table columns and the values.



I've also found another pattern that consists of send a JSON object that represents the query .



To filter on a field, simply add that field and its value to the query :



GET /app/items
{
"items": [
{
"param1": "value1",
"param2": "value",
"param N": "value N"
}
]
}


I'm looking for the best practice to achieve this .



I'm using EF Core with ASP.NET Core for implementing this.










share|improve this question


























    up vote
    0
    down vote

    favorite












    I am currently implementing a RESTFUL API that provides endpoints to interface with a database .



    I want to implement filtering in my API , but I need to provide an endpoint that can provide a way to apply filtering on a table using all the table's columns.



    I've found some patterns such as :



    GET /api/ressource?param1=value1,param2=value2...paramN=valueN


    param1,param2...param N being my table columns and the values.



    I've also found another pattern that consists of send a JSON object that represents the query .



    To filter on a field, simply add that field and its value to the query :



    GET /app/items
    {
    "items": [
    {
    "param1": "value1",
    "param2": "value",
    "param N": "value N"
    }
    ]
    }


    I'm looking for the best practice to achieve this .



    I'm using EF Core with ASP.NET Core for implementing this.










    share|improve this question
























      up vote
      0
      down vote

      favorite









      up vote
      0
      down vote

      favorite











      I am currently implementing a RESTFUL API that provides endpoints to interface with a database .



      I want to implement filtering in my API , but I need to provide an endpoint that can provide a way to apply filtering on a table using all the table's columns.



      I've found some patterns such as :



      GET /api/ressource?param1=value1,param2=value2...paramN=valueN


      param1,param2...param N being my table columns and the values.



      I've also found another pattern that consists of send a JSON object that represents the query .



      To filter on a field, simply add that field and its value to the query :



      GET /app/items
      {
      "items": [
      {
      "param1": "value1",
      "param2": "value",
      "param N": "value N"
      }
      ]
      }


      I'm looking for the best practice to achieve this .



      I'm using EF Core with ASP.NET Core for implementing this.










      share|improve this question













      I am currently implementing a RESTFUL API that provides endpoints to interface with a database .



      I want to implement filtering in my API , but I need to provide an endpoint that can provide a way to apply filtering on a table using all the table's columns.



      I've found some patterns such as :



      GET /api/ressource?param1=value1,param2=value2...paramN=valueN


      param1,param2...param N being my table columns and the values.



      I've also found another pattern that consists of send a JSON object that represents the query .



      To filter on a field, simply add that field and its value to the query :



      GET /app/items
      {
      "items": [
      {
      "param1": "value1",
      "param2": "value",
      "param N": "value N"
      }
      ]
      }


      I'm looking for the best practice to achieve this .



      I'm using EF Core with ASP.NET Core for implementing this.







      entity-framework rest asp.net-web-api






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Nov 22 at 16:07









      Isser Zack

      31




      31
























          1 Answer
          1






          active

          oldest

          votes

















          up vote
          0
          down vote



          accepted










          Firstly be cautious about filtering on everything/anything. Base the available filters on what users will need and expand from that depending on demand. Less code to write, less complexity, fewer indexes needed on the DB side, better performance.



          That said, the approach I use for pages that have a significant number of filters is to use an enumeration server side where my criteria fields are passed back their enumeration value (number) to provide on the request. So a filter field would comprise of a name, default or applicable values, and an enumeration value to use when passing an entered or selected value back to the search. The requesting code creates a JSON object with the applied filters and Base64's it to send in the request:



          I.e.



          {
          p1: "Jake",
          p2: "8"
          }


          The query string looks like:
          .../api/customer/search?filters=XHgde0023GRw....



          On the server side I extract the Base64 then parse it as a Dictionary<string,string> to feed to the filter parsing. For example given that the criteria was for searching for a child using name and age:



          // this is the search filter keys, these (int) values are passed to the search client for each filter field.
          public enum FilterKeys
          {
          None = 0,
          Name,
          Age,
          ParentName
          }

          public JsonResult Search(string filters)
          {
          string filterJson = Encoding.UTF8.GetString(Convert.FromBase64String(filters));
          var filterData = JsonConvert.DeserializeObject<Dictionary<string, string>>(filterJson);

          using (var context = new TestDbContext())
          {
          var query = context.Children.AsQueryable();

          foreach (var filter in filterData)
          query = filterChildren(query, filter.Key, filter.Value);

          var results = query.ToList(); //example fetch.
          // TODO: Get the results, package up view models, and return...
          }
          }

          private IQueryable<Child> filterChildren(IQueryable<Child> query, string key, string value)
          {
          var filterKey = parseFilterKey(key);
          if (filterKey == FilterKeys.None)
          return query;

          switch (filterKey)
          {
          case FilterKeys.Name:
          query = query.Where(x => x.Name == value);
          break;
          case FilterKeys.Age:
          DateTime birthDateStart = DateTime.Today.AddYears((int.Parse(value) + 1) * -1);
          DateTime birthDateEnd = birthDateStart.AddYears(1);
          query = query.Where(x => x.BirthDate <= birthDateEnd && x.BirthDate >= birthDateStart);
          break;
          }
          return query;
          }

          private FilterKeys parseFilterKey(string key)
          {
          FilterKeys filterKey = FilterKeys.None;

          Enum.TryParse(key.Substring(1), out filterKey);
          return filterKey;
          }


          You can use strings and constants to avoid the enum parsing, however I find enums are readable and keep the sent payload a little more compact. The above is a simplified example and obviously needs error checking. The implementation code for complex filter conditions such as the age to birth date above would better be suited as a separate method, but it should give you some ideas. You can search for children by name, and/or age, and/or parent's name for example.






          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',
            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%2f53434725%2fcleanest-way-to-implement-multiple-parameters-filters-in-a-rest-api%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








            up vote
            0
            down vote



            accepted










            Firstly be cautious about filtering on everything/anything. Base the available filters on what users will need and expand from that depending on demand. Less code to write, less complexity, fewer indexes needed on the DB side, better performance.



            That said, the approach I use for pages that have a significant number of filters is to use an enumeration server side where my criteria fields are passed back their enumeration value (number) to provide on the request. So a filter field would comprise of a name, default or applicable values, and an enumeration value to use when passing an entered or selected value back to the search. The requesting code creates a JSON object with the applied filters and Base64's it to send in the request:



            I.e.



            {
            p1: "Jake",
            p2: "8"
            }


            The query string looks like:
            .../api/customer/search?filters=XHgde0023GRw....



            On the server side I extract the Base64 then parse it as a Dictionary<string,string> to feed to the filter parsing. For example given that the criteria was for searching for a child using name and age:



            // this is the search filter keys, these (int) values are passed to the search client for each filter field.
            public enum FilterKeys
            {
            None = 0,
            Name,
            Age,
            ParentName
            }

            public JsonResult Search(string filters)
            {
            string filterJson = Encoding.UTF8.GetString(Convert.FromBase64String(filters));
            var filterData = JsonConvert.DeserializeObject<Dictionary<string, string>>(filterJson);

            using (var context = new TestDbContext())
            {
            var query = context.Children.AsQueryable();

            foreach (var filter in filterData)
            query = filterChildren(query, filter.Key, filter.Value);

            var results = query.ToList(); //example fetch.
            // TODO: Get the results, package up view models, and return...
            }
            }

            private IQueryable<Child> filterChildren(IQueryable<Child> query, string key, string value)
            {
            var filterKey = parseFilterKey(key);
            if (filterKey == FilterKeys.None)
            return query;

            switch (filterKey)
            {
            case FilterKeys.Name:
            query = query.Where(x => x.Name == value);
            break;
            case FilterKeys.Age:
            DateTime birthDateStart = DateTime.Today.AddYears((int.Parse(value) + 1) * -1);
            DateTime birthDateEnd = birthDateStart.AddYears(1);
            query = query.Where(x => x.BirthDate <= birthDateEnd && x.BirthDate >= birthDateStart);
            break;
            }
            return query;
            }

            private FilterKeys parseFilterKey(string key)
            {
            FilterKeys filterKey = FilterKeys.None;

            Enum.TryParse(key.Substring(1), out filterKey);
            return filterKey;
            }


            You can use strings and constants to avoid the enum parsing, however I find enums are readable and keep the sent payload a little more compact. The above is a simplified example and obviously needs error checking. The implementation code for complex filter conditions such as the age to birth date above would better be suited as a separate method, but it should give you some ideas. You can search for children by name, and/or age, and/or parent's name for example.






            share|improve this answer

























              up vote
              0
              down vote



              accepted










              Firstly be cautious about filtering on everything/anything. Base the available filters on what users will need and expand from that depending on demand. Less code to write, less complexity, fewer indexes needed on the DB side, better performance.



              That said, the approach I use for pages that have a significant number of filters is to use an enumeration server side where my criteria fields are passed back their enumeration value (number) to provide on the request. So a filter field would comprise of a name, default or applicable values, and an enumeration value to use when passing an entered or selected value back to the search. The requesting code creates a JSON object with the applied filters and Base64's it to send in the request:



              I.e.



              {
              p1: "Jake",
              p2: "8"
              }


              The query string looks like:
              .../api/customer/search?filters=XHgde0023GRw....



              On the server side I extract the Base64 then parse it as a Dictionary<string,string> to feed to the filter parsing. For example given that the criteria was for searching for a child using name and age:



              // this is the search filter keys, these (int) values are passed to the search client for each filter field.
              public enum FilterKeys
              {
              None = 0,
              Name,
              Age,
              ParentName
              }

              public JsonResult Search(string filters)
              {
              string filterJson = Encoding.UTF8.GetString(Convert.FromBase64String(filters));
              var filterData = JsonConvert.DeserializeObject<Dictionary<string, string>>(filterJson);

              using (var context = new TestDbContext())
              {
              var query = context.Children.AsQueryable();

              foreach (var filter in filterData)
              query = filterChildren(query, filter.Key, filter.Value);

              var results = query.ToList(); //example fetch.
              // TODO: Get the results, package up view models, and return...
              }
              }

              private IQueryable<Child> filterChildren(IQueryable<Child> query, string key, string value)
              {
              var filterKey = parseFilterKey(key);
              if (filterKey == FilterKeys.None)
              return query;

              switch (filterKey)
              {
              case FilterKeys.Name:
              query = query.Where(x => x.Name == value);
              break;
              case FilterKeys.Age:
              DateTime birthDateStart = DateTime.Today.AddYears((int.Parse(value) + 1) * -1);
              DateTime birthDateEnd = birthDateStart.AddYears(1);
              query = query.Where(x => x.BirthDate <= birthDateEnd && x.BirthDate >= birthDateStart);
              break;
              }
              return query;
              }

              private FilterKeys parseFilterKey(string key)
              {
              FilterKeys filterKey = FilterKeys.None;

              Enum.TryParse(key.Substring(1), out filterKey);
              return filterKey;
              }


              You can use strings and constants to avoid the enum parsing, however I find enums are readable and keep the sent payload a little more compact. The above is a simplified example and obviously needs error checking. The implementation code for complex filter conditions such as the age to birth date above would better be suited as a separate method, but it should give you some ideas. You can search for children by name, and/or age, and/or parent's name for example.






              share|improve this answer























                up vote
                0
                down vote



                accepted







                up vote
                0
                down vote



                accepted






                Firstly be cautious about filtering on everything/anything. Base the available filters on what users will need and expand from that depending on demand. Less code to write, less complexity, fewer indexes needed on the DB side, better performance.



                That said, the approach I use for pages that have a significant number of filters is to use an enumeration server side where my criteria fields are passed back their enumeration value (number) to provide on the request. So a filter field would comprise of a name, default or applicable values, and an enumeration value to use when passing an entered or selected value back to the search. The requesting code creates a JSON object with the applied filters and Base64's it to send in the request:



                I.e.



                {
                p1: "Jake",
                p2: "8"
                }


                The query string looks like:
                .../api/customer/search?filters=XHgde0023GRw....



                On the server side I extract the Base64 then parse it as a Dictionary<string,string> to feed to the filter parsing. For example given that the criteria was for searching for a child using name and age:



                // this is the search filter keys, these (int) values are passed to the search client for each filter field.
                public enum FilterKeys
                {
                None = 0,
                Name,
                Age,
                ParentName
                }

                public JsonResult Search(string filters)
                {
                string filterJson = Encoding.UTF8.GetString(Convert.FromBase64String(filters));
                var filterData = JsonConvert.DeserializeObject<Dictionary<string, string>>(filterJson);

                using (var context = new TestDbContext())
                {
                var query = context.Children.AsQueryable();

                foreach (var filter in filterData)
                query = filterChildren(query, filter.Key, filter.Value);

                var results = query.ToList(); //example fetch.
                // TODO: Get the results, package up view models, and return...
                }
                }

                private IQueryable<Child> filterChildren(IQueryable<Child> query, string key, string value)
                {
                var filterKey = parseFilterKey(key);
                if (filterKey == FilterKeys.None)
                return query;

                switch (filterKey)
                {
                case FilterKeys.Name:
                query = query.Where(x => x.Name == value);
                break;
                case FilterKeys.Age:
                DateTime birthDateStart = DateTime.Today.AddYears((int.Parse(value) + 1) * -1);
                DateTime birthDateEnd = birthDateStart.AddYears(1);
                query = query.Where(x => x.BirthDate <= birthDateEnd && x.BirthDate >= birthDateStart);
                break;
                }
                return query;
                }

                private FilterKeys parseFilterKey(string key)
                {
                FilterKeys filterKey = FilterKeys.None;

                Enum.TryParse(key.Substring(1), out filterKey);
                return filterKey;
                }


                You can use strings and constants to avoid the enum parsing, however I find enums are readable and keep the sent payload a little more compact. The above is a simplified example and obviously needs error checking. The implementation code for complex filter conditions such as the age to birth date above would better be suited as a separate method, but it should give you some ideas. You can search for children by name, and/or age, and/or parent's name for example.






                share|improve this answer












                Firstly be cautious about filtering on everything/anything. Base the available filters on what users will need and expand from that depending on demand. Less code to write, less complexity, fewer indexes needed on the DB side, better performance.



                That said, the approach I use for pages that have a significant number of filters is to use an enumeration server side where my criteria fields are passed back their enumeration value (number) to provide on the request. So a filter field would comprise of a name, default or applicable values, and an enumeration value to use when passing an entered or selected value back to the search. The requesting code creates a JSON object with the applied filters and Base64's it to send in the request:



                I.e.



                {
                p1: "Jake",
                p2: "8"
                }


                The query string looks like:
                .../api/customer/search?filters=XHgde0023GRw....



                On the server side I extract the Base64 then parse it as a Dictionary<string,string> to feed to the filter parsing. For example given that the criteria was for searching for a child using name and age:



                // this is the search filter keys, these (int) values are passed to the search client for each filter field.
                public enum FilterKeys
                {
                None = 0,
                Name,
                Age,
                ParentName
                }

                public JsonResult Search(string filters)
                {
                string filterJson = Encoding.UTF8.GetString(Convert.FromBase64String(filters));
                var filterData = JsonConvert.DeserializeObject<Dictionary<string, string>>(filterJson);

                using (var context = new TestDbContext())
                {
                var query = context.Children.AsQueryable();

                foreach (var filter in filterData)
                query = filterChildren(query, filter.Key, filter.Value);

                var results = query.ToList(); //example fetch.
                // TODO: Get the results, package up view models, and return...
                }
                }

                private IQueryable<Child> filterChildren(IQueryable<Child> query, string key, string value)
                {
                var filterKey = parseFilterKey(key);
                if (filterKey == FilterKeys.None)
                return query;

                switch (filterKey)
                {
                case FilterKeys.Name:
                query = query.Where(x => x.Name == value);
                break;
                case FilterKeys.Age:
                DateTime birthDateStart = DateTime.Today.AddYears((int.Parse(value) + 1) * -1);
                DateTime birthDateEnd = birthDateStart.AddYears(1);
                query = query.Where(x => x.BirthDate <= birthDateEnd && x.BirthDate >= birthDateStart);
                break;
                }
                return query;
                }

                private FilterKeys parseFilterKey(string key)
                {
                FilterKeys filterKey = FilterKeys.None;

                Enum.TryParse(key.Substring(1), out filterKey);
                return filterKey;
                }


                You can use strings and constants to avoid the enum parsing, however I find enums are readable and keep the sent payload a little more compact. The above is a simplified example and obviously needs error checking. The implementation code for complex filter conditions such as the age to birth date above would better be suited as a separate method, but it should give you some ideas. You can search for children by name, and/or age, and/or parent's name for example.







                share|improve this answer












                share|improve this answer



                share|improve this answer










                answered Nov 22 at 22:34









                Steve Py

                5,17511017




                5,17511017






























                    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.





                    Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


                    Please pay close attention to the following guidance:


                    • 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%2f53434725%2fcleanest-way-to-implement-multiple-parameters-filters-in-a-rest-api%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

                    What visual should I use to simply compare current year value vs last year in Power BI desktop

                    Alexandru Averescu

                    Trompette piccolo