Pass variable number of query string parameters to single controller action - asp.net-web-api2

I am creating an API that has a controller with one GET action:
[Route("api/xxxxx/{param1:int}/{param1:int}")]
public IHttpActionResult Get(int param1, int param2) {
// method body...
}
The URLs will be in the following format:
/api/xxxxx/1/1?p1=5&p2=hello&p3=20161108
/api/xxxxx/1/1?p1=active
The number and names of the of query string parameters will vary.
I want to pass the query string parameters into the controller method, but I cannot hard code them into the method signature, due to the varying names and numbers. Is there a way to do this? I've tried calling var qsParams = ControllerContext.Request.GetQueryNameValuePairs();, but I get a resource not found error when trying to request any URL with a query string, given the Route attribute shown above.
I've come up with one alternative: use route values instead of query string parameters, then use the catch-all {*tags} and pass it as a method parameter:
[Route("api/xxxxx/{param1:int}/{param1:int}/{*tags}")]
public IHttpActionResult Get(int param1, int param2, string tags) {
// method body...
}
With URLs in the format
/api/xxxxx/1/1/5/john/20161108
/api/xxxxx/1/1/active
This works, but I'd rather use the query string to be able to use named keys instead of relying on the ordering of the parameters (also, using the query string seems to better conceptual match for what I'm doing).
So, how can I pass variable query string parameters into a controller action? I say "pass" the parameters, but they don't necessarily need to be passed as a method parameters, as long as I could access the query string parameters from the method body, while getting a URL with a query string to resolve to the action in question.
EDIT:
It's worth mentioning that creating multiple action methods for every possible parameter set is not an option.
EDIT 2:
I see two direct solutions, if they're possible:
Pass the entire query string to the action method as a single string
parameter. I could then manually parse the query string.
Be able to use ControllerContext.Request.GetQueryNameValuePairs()
inside the method body, while NOT adding corresponding parameters to
the method signature.
However, I haven't figured out if these two things are possible, though it seems likely one or both would be.

If you want to send an Entity to your Controller you can get it by [FromBody]
Think an Entity is the simplest way to pass it.
F.e.
[HttpPost]
[Route("api/xxxxx/send")]
public void SendReuqest([FromBody] Entity name)
hope understand your problem right.

I'm posting an answer to document alternatives I've come up with. I know some (all?) of these will be hacky. Nonetheless...
ANSWER
Use method GetQueryNameValuePairs. This is the very first thing I tried, but I must have had something different in the route attribute or the method signature, because I was getting a "not found" error before. Now, however, this is working perfectly. This makes this whole question and answer basically moot for me.
Example URL:
/api/xxxxx/1/1?p1=5&p2=john&p3=20161108
Action:
[Route("api/xxxxx/{param1:int}/{param1:int}")]
public IHttpActionResult Get(int param1, int param2) {
var qsParams = ControllerContext.Request.GetQueryNameValuePairs();
// rest of method body...
}
Option 1
Use a single query string parameter with a custom format that the controller understands and will be able to parse.
Example URL:
/api/xxxxx/1/1?qs=p1~5|p2~john|p3~20161108
Action:
[Route("api/xxxxx/{param1:int}/{param1:int}")]
public IHttpActionResult Get(int param1, int param2, string qs) {
string[] qsParamsArray = qs.Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries);
IDictionary<string, string> qsParams = new Dictionary<string, string>();
foreach (string p in qsParamsArray) {
string[] kv = p.Split(new string[] { "~" }, StringSplitOptions.None);
if (String.IsNullOrWhiteSpace(kv[0])) {
return NotFound();
}
qsParams.Add(kv[0], kv[1]);
}
// Use qsParams as desired...
}
Option 2
Put the query string parameters in the route instead of the query string. Must rely on ordering of parameters.
Example URL:
/api/xxxxx/1/1/5/john/20161108
Action:
[Route("api/xxxxx/{param1:int}/{param1:int}/{*tags}")]
public IHttpActionResult Get(int param1, int param2, string tags) {
// parse the "tags" parameter here...
// rest of method body...
}
Option 3
Hard code a maximum number of optional parameters with generic names, and specific query string parameters for both names and values of the parameters.
Example URL:
/api/xxxxx/1/1?name1=p1&value1=5&name2=p2&value2=john&name3=p3&value3=20161108
Action:
[Route("api/xxxxx/{param1:int}/{param1:int}")]
public IHttpActionResult Get(
int param1,
int param2,
string name1 = null,
string value1 = null,
string name2 = null,
string value2 = null,
string name3 = null,
string value3 = null,
string name4 = null,
string value4 = null,
...,
string nameNMAX = null,
string valueNMAX = null
) {
// method body...
}

Related

asp.net core api - how to distinguish between `api/cars` and `api/cars?color=red` calls with [FromQuery] object

I am using [FromQuery] atribute in controller's Get method:
//CarsController, etc..
[HttpGet]
public async Task<ActionResult<IEnumerable<CarsDto>>> Get([FromQuery] CarsParameter? carsParam = null)
{
//param is always not null here
}
Inside the method I need to distinguish between api/cars and api/cars?color=red calls. Problem is, that carsParam object is never null, so I cannot say if the Color="" (defailt value) is intended to be empty string or it's because of the call was api/cars
CarsParameter is a simple class:
public class CarsParameter
{
public string Color {get; set;} = "";
//more params here
}
Yes, I can use different path, like api/cars/withParams?color=red, but i am looking for more subtle solution.
I need to distinguish between api/cars and api/cars?color=red calls. Problem is, that carsParam object is never null
Please note that default model binding starts by looking through the sources for the key carsParam.Color. If that isn't found, it looks for Color without a prefix, which cause the issue.
To achieve your requirement, you can try to specify prefix explicitly, like below.
public async Task<ActionResult<IEnumerable<CarsDto>>> Get([FromQuery][Bind(Prefix = "carsParam")] CarsParameter? carsParam = null)
{
Request to api/cars?color=red&carsParam.color=yellow&carsParam.brand=test and following is test result

.Net Core API Endpoint not allowing QueryString parameters

Very possible this is a duplicate, but I've looked and can't find an answer. The first answer here looked promising: Query string not working while using attribute routing But I tried that and it didn't work.
[HttpGet, Route("api/machine/byid/{id=id}/{pageNumber=pageNumber}/{pageSize=pageSize}/{fields=fields}")]
public string ById(int id, int pageNumber, int pageSize, string fields)
// code removed
}
This works:
https://localhost:44303/api/machine/byid/1/2/3/a,b,c
This does not:
https://localhost:44303/api/machine/byid?id=1&pageNumber=2&pageSize=3&fields=a,b,c
The second url returns:
{"type":"https://www.rfc-editor.org/rfc/rfc7231#section-6.5.1","title":"One or more validation errors occurred.","status":400,"traceId":"|bf12950b-472923d3a24062d1.","errors":{"id":["The value 'id' is not valid."],"pageSize":["The value 'pageSize' is not valid."],"pageNumber":["The value 'pageNumber' is not valid."]}}
You would need two routes:
[HttpGet("api/machine/byid")]
public string ById(
[FromQuery("id")] int id,
[FromQuery("pageNumber")] int pageNumber,
[FromQuery("pageSize")] int pageSize,
[FromQuery("fields")] string fields)
{
}
Follow this link for more informations
The example you provided demonstrates route parameters. There is a distinct difference between route parameters and query parameters.
To accomplish query parameters, you can the [FromQuery] attribute to your method parameters. This will allow for the query parameter example that you provided,
Example : https://localhost:5000/api/persons?firstName=bob&lastName=smith
You can also provide default values for these from within your method parameters. You can string multiple query parameters together in one action.
For route parameters, the parameters are provided via the route itself.
Example : https://localhost:5000/api/persons/23
These parameters are defined from within the [HttpGet("{id}")] attribute on your controller action. You can also constrain the parameter to a certain type, such as an int. This is achieved by adding a colon and specifying the type. Example [HttpGet("{id:int}")]. No further attributes are required to be added within your method parameters for route parameters.
Of course you must also declare these parameters in your method parameters, for both types.
// "/api/persons/23"
[HttpGet("{id}")]
public async Task<IActionResult> GetPersonById(int id)
{
// Code ...
}
// "/api/persons?firstName=bob&lastName=smith"
[HttpGet]
public async Task<IActionResult> GetPersonByName([FromQuery] string firstName = null, [FromQuery] string lastName = null)
{
// Code here... both firstName and lastName can now be optional or only one provided
}
The answer by sturcotte06 was close, but was not 100% Core compliant. This works:
[HttpGet, Route("api/machine/byid/{id=id}/{pageNumber=pageNumber}/{pageSize=pageSize}/{fields=fields}")]
public string ById([FromQuery] int id, [FromQuery] int pageNumber, [FromQuery] int pageSize, [FromQuery] string fields)
{
// code removed
}

OutputCache varying by a complex object property passed using ModelBinder from Session

We are using Couchbase for our Session and for OutputCache.
In this context, how can we cache by a complex object that is being passed to the method using a Custom Model Binder that retrieves a value from the Session?
This is the signature of the method I want to cache with the OutputCache attribute:
[HttpGet]
[OutputCache(CacheProfile = "MyObjectsCache", VaryByParam = "myParam")]
public ActionResult Index([ModelBinder(typeof (CustomVariableSessionModelBinder<MyClass>))] MyClass myParam)
{
Note: The ModelBinder is being used here for reasons beyond me and I cannot change it.
MyClass is a complex object that has an Id. I want to use the Id as the caching identifier.
public class MyClass
{
public int Id{get;set;}
//Other Properties
This is how the object is being retrieved from Session:
var sessionKey = typeof (MyNamespace.MyClass).FullName;
var httpContext = HttpContext.Current;
MyNamespace.MyClass newObject = null;
if (httpContext.Session != null)
{
newObject = httpContext.Session[sessionKey] as MyNamespace.MyClass;
}
Is it possible yo use VaryByParam for this scenario or will I have to use VaryByCustom?
I haven't tested this, but it should work. It's pretty much your only option anyways, though.
In addition to the built in ways to vary, you can vary by "Custom". This will call into a method in Global.asax you'll need to override: GetVaryByCustomString. Importantly for you situation here, this method is passed HttpContext, so you should be able to look into the session. Essentially, the solution will look something like:
public override string GetVaryByCustomString(HttpContext context, string custom)
{
var args = custom.ToLower().Split(';');
var sb = new StringBuilder();
foreach (var arg in args)
{
switch (arg)
{
case "session":
var obj = // get your object from session
// now create some unique string to append
sb.AppendFormat("Session{0}", obj.Id);
}
}
return sb.ToString();
}
This is designed to handle multiple different types of "custom" vary types. For example, if you wanted to vary by "User", which is common, you can merely add a case for that in your switch. The important part is that the string returned by this method is actually what the output cache varies on, so you want that to be unique for the situation. This is why I prefixed the object's id with "Session" here. For example, if you just added the id, let's say 123, and then in another scenario you varied by user and that string was composed of just the user's id, which happened to be 123 as well. It would be the same string to the output cache, and you'd end with some weird results. Just be mindful of what the custom string looks like.
Now, you'd just alter your OutputCache attribute like:
[OutputCache(CacheProfile = "MyObjectsCache", VaryByParam = "myParam", VaryByCustom = "Session")]
Note: to vary by multiple custom things at once, you'd separate them with a ; (based on how the code above works). For example: VaryByCustom = "Session;User"

Routes in WCF - Can it be like MVC?

Disclaimer: Yes I know WCF isn't the Web API or MVC. However I'm trying to figure out a hack to do sort of what MVC does with routing so that I may be able to keep the same method names in my .svc contract behind the scenes when I wire up different requests with different sets of querystring params.
So I have this dumb WCF service and I want to have an overload of Get methods in here. But I can't, WCF bitches about it when I try to make a call to this service.
I have 2 methods named "Get" that I'm trying to essentially overload but differentiate by querystring. Notice the two below have different sets of querystrings in the uri so my intent is not having to have all these different Get, GetbySomething, GetBySomething2. It'd be nice to just stick with the name Get() on all mymethods and just overload them / differentiate them so that the request coming in can wire up to maybe the querystring differentiation sort of like MVC does:
[WebGet(UriTemplate = "?securityToken={securityToken}&brokerId={brokerId}&orderStatus={orderStatus}&pageNumber={pageNumber}&pageSize={pageSize}&sortBy={sortBy}&purchaseOrderNumber={purchaseOrderNumber}")]
public OrderResponse Get(string securityToken, string brokerId, string orderStatus, string pageNumber, string pageSize, string sortBy, string purchaseOrderNumber)
[WebGet(UriTemplate = "?securityToken={securityToken}&brokerId={brokerId}&purchaseOrderNumber={purchaseOrderNumber}&startDate={startDate}&endDate={endDate}&pageNumber={pageNumber}&pageSize={pageSize}&sortBy={sortBy}")]
public OrderResponse Get(string securityToken, string brokerId, int purchaseOrderNumber, string startDate, string endDate, string pageNumber, string pageSize, string sortBy)
Error in response coming from WCF: "Cannot have two operations in the same contract with the same name, methods Get and Get in type OrderService violate this rule. You can change the name of one of the operations by changing the method name or by using the Name property of OperationContractAttribute. "
ok whatever fine. So can I somehow use a route table in my global.asax to essentially do what MVC can do?
RouteTable.Routes.Add(new ServiceRoute("Inventory", new WebServiceHostFactory(), typeof(InventoryService)));
MVC allows me to map but takes into account the querystring as part of the matching:
Just an example, I could take into account querystrings ?someParam={someValue}
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}?someParam={someValue}",
defaults: new { id = RouteParameter.Optional }
);
yes I also know in MVC you can overload your methods to differentiate them via parameters in the controller and as you can see here my WCF contract methods for Get() do have different set of params so one would think WCF would let you differentiate the same method name by params but it doesn't.
You can overload the functions to accept different parameters
Because in WCF the way you specify the querystring match part is via attribute:
[WebGet(UriTemplate = "?securityToken={securityToken}&brokerId={brokerId}&purchaseOrderNumber={purchaseOrderNumber}&startDate={startDate}&endDate={endDate}&pageNumber={pageNumber}&pageSize={pageSize}&sortBy={sortBy}]
so is there a way to use my RouteTable.Routes.Add(new ServiceRoute("V1/Inventory", new WebServiceHostFactory(), typeof(InventoryService))); that I have in my global.ascx to somehow all me to make Get method names unique and overload them by differentiating them by querystring?
Because right now to fix this I'd have to make one of my Get method names something else which is IMO stupid and unclean. I'd have to do something like GetByDateRange. So I'd have to do something like:
[WebGet(UriTemplate = "?securityToken={securityToken}&brokerId={brokerId}&orderStatus={orderStatus}&pageNumber={pageNumber}&pageSize={pageSize}&sortBy={sortBy}&purchaseOrderNumber={purchaseOrderNumber}")]
public OrderResponse Get(string securityToken, string brokerId, string orderStatus, string pageNumber, string pageSize, string sortBy, string purchaseOrderNumber)
[WebGet(UriTemplate = "?securityToken={securityToken}&brokerId={brokerId}&purchaseOrderNumber={purchaseOrderNumber}&startDate={startDate}&endDate={endDate}&pageNumber={pageNumber}&pageSize={pageSize}&sortBy={sortBy}")]
public OrderResponse GetByDateRange(string securityToken, string brokerId, int purchaseOrderNumber, string startDate, string endDate, string pageNumber, string pageSize, string sortBy)
This puts me back into RPC style method names behind my web service handling and I hate that. We are using WCF restfully as well so I'd like to keep this clean by just having a bunch of overloaded Put() methods, Get(), etc.

Dynamic Table Names in Linq to SQL

Hi all I have a horrid database I gotta work with and linq to sql is the option im taking to retrieve data from. anywho im trying to reuse a function by throwing in a different table name based on a user selection and there is no way to my knowledge to modify the TEntity or Table<> in a DataContext Query.
This is my current code.
public void GetRecordsByTableName(string table_name){
string sql = "Select * from " + table_name;
var records = dataContext.ExecuteQuery</*Suppossed Table Name*/>(sql);
ViewData["recordsByTableName"] = records.ToList();
}
I want to populate my ViewData with Enumerable records.
You can call the ExecuteQuery method on the DataContext instance. You will want to call the overload that takes a Type instance, outlined here:
http://msdn.microsoft.com/en-us/library/bb534292.aspx
Assuming that you have a type that is attributed correctly for the table, passing that Type instance for that type and the SQL will give you what you want.
As casperOne already answered, you can use ExecuteQuery method first overload (the one that asks for a Type parameter). Since i had a similar issue and you asked an example, here is one:
public IEnumerable<YourType> RetrieveData(string tableName, string name)
{
string sql = string.Format("Select * FROM {0} where Name = '{1}'", tableName, name);
var result = YourDataContext.ExecuteQuery(typeof(YourType), sql);
return result;
}
Pay attention to YourType since you will have to define a type that has a constructor (it can't be abstract or interface). I'd suggest you create a custom type that has exactly the same attributes that your SQL Table. If you do that, the ExecuteQuery method will automatically 'inject' the values from your table to your custom type. Like that:
//This is a hypothetical table mapped from LINQ DBML
[global::System.Data.Linq.Mapping.TableAttribute(Name="dbo.ClientData")]
public partial class ClientData : INotifyPropertyChanging, INotifyPropertyChanged
{
private int _ID;
private string _NAME;
private string _AGE;
}
//This would be your custom type that emulates your ClientData table
public class ClientDataCustomType
{
private int _ID;
private string _NAME;
private string _AGE;
}
So, on the former example, the ExecuteQuery method would be:
var result = YourDataContext.ExecuteQuery(typeof(ClientDataCustomType), sql);