Trying to read querystring in C# - c#

I am a newbie when it comes to C#, but I need to use it for a project at work.
I am building a web page that is using jQuery to call a C# program. The C# program will connect to a SQL server, retrieve data for agencies and return it to the calling webpage as JSON.
I have all that working, I can get both a single agency and a collection of agencies and return it properly. Below is the code:
public class AgencyController : ApiController
{
// GET: api/Agency
public List<AgencyData> Get()
{
//var queryValues = Request.RequestUri.ParseQueryString();
//string filter = queryValues.Get("filter").ToString();
List<AgencyData> json;
json = SQLAllAgencyData("");
return json;
}
// GET: api/Agency/5
public List<AgencyData> Get(string id)
{
List<AgencyData> json;
json = SQLAgencyData(id);
return json;
}
What I want to do now is to be able to pass additional information to the C# program. Something like this:
www.domain.com/api/Agency?state=TX&city=Dallas
I can not figure out how to do that. All the examples I found result in build errors.
Here are a couple of links I tried:
http://forums.asp.net/t/1072321.aspx?How+to+get+parameter+in+url+by+C+for+net
Is there a way to get all the querystring name/value pairs into a collection?
You can also see the two commented out line in my code, they also don't work.
I figure that Request is never set to anything, or defined/declared, but I haven't been able to find an example of how to do that.
Suggestions?

There is no need to read the query string, the WEB API model binder will bind any query string parameters to parameters of your action method... I have never known a scenario where I needed to manually parse the query string
With Attribute Routing:
Attribute routing allows you to specifiy nullable parameters, you will need to enable attribute routing which you can find hundreds of tutorials on.
[Route("Agency/{state?}/{city?}")
public List<AgencyData> Get(string state = null, string city = null)
{
List<AgencyData> json;
json = SQLAllAgencyData("");
return json;
}
This would make the url look like this...
http://xxxxxx.com/api/Agency/Texas/Dallas
I am however almost sure your query string syntax would work too however you will need to try that.
Without Attribute Routing:
If you do not want to add attribute routing to Web API you can also add overloaded action methods to the controller..
// /api/Agency
public List<AgencyData> Get()
{
var json = SQLAllAgencyData("");
return json;
}
// /api/Agency?state=texas&city=dallas
public List<AgencyData> Get(string state, string city)
{
// Params will be equal to your values...
var json = SQLAllAgencyData("");
return json;
}
EDIT: Turns out there is no need to overload the action method... simply set the parameter defaults to null... (overload seems cleaner though)
// /Agency/
// /Agency?state=Texas&city=Dallas
public List<AgencyData> Get(string state = null, string city = null)
{
// Check for null.. etc.
}
Edit: To make this work I have used the default routing config...
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}

if you want state and city as parameter of method than just add that to parameter of your method and parameters name should match with query string parameter names
Get(String state, String city)

You can directly accept the query parameters as method parameters and the query string values can be used in your method.
public List<AgencyData> Get(String state, String city)
{
//var queryValues = Request.RequestUri.ParseQueryString();
//string filter = queryValues.Get("filter").ToString();
List<AgencyData> json;
json = SQLAllAgencyData("");
return json;
}

Related

Can an ASP.NET Core Web API Controller have more than one ControllerRoute?

I have a controller with the following setup for API requests:
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action}/{id?}",
defaults: new { controller = "Home", action = "Index" }
);
This works great for GET requests, but for POST requests my parameters do not seem to have any values. On the frontend, in JavaScript, I can see my parameters in the payload so I know they are there. However, my controller must not be set up correctly to take the POST requests.
Here is my GET request which works, and my POST which doesn't. I believe my issue is that I set the controller route to require the ? and take parameters. However, I still need to do post requests!
GET request:
public ActionResult Contacts([FromQuery] String DD_INPUT)
{
//We have parameters here just in case we want to use them
IEnumerable queryResult;
String query = "Exec dbo.storedprocedure2 #DD_INPUT";
using (var connection = new SqlConnection(connectionString))
{
queryResult = connection.Query(query, new { DD_INPUT = DD_INPUT });
}
return Ok(queryResult);
}
POST request:
[HttpPost]
public ActionResult AddFirm([FromBody] String FIRM_NAME)
{
String query = "exec dbo.storeprocedername #FIRM_NAME";
System.Diagnostics.Debug.WriteLine("value:" + FIRM_NAME);
using (var connection = new SqlConnection(connectionString))
{
var json = connection.QuerySingle<string>(query, new { FIRM_NAME = FIRM_NAME});
return Content(json, "application/json");
}
}
POST Request JavaScript
axios.post(window.location.origin + '/API/AddFirm', {
FIRM_NAME: this.FirmName
}).then(response => {
this.FirmsArray = response.data;
}).catch(error => {
console.log(error.response.data.error);
});
if you want to use [frombody] in your action, you will have to stringify your data
const json = JSON.stringify(this.FirmName);
await axios.post(window.location.origin + '/API/AddFirm', json, {
headers: {
'Content-Type': 'application/json'
}
}).then(response => {
this.FirmsArray = response.data;
}).catch(error => {
console.log(error.response.data.error);
});
I am only not sure about url that you are offering. Usually Api has a different url than application it is called from. And it usually looks like "/MyController/MyAction". Unfortunately you didn't post your controller header.
UPDATE
if you need post several properties using [frombody] action you need to change your action too
Create view model
public class ViewModel
{
public string FirmName {get; set;}
public string Email {get; set;}
}
action
[HttpPost]
public ActionResult AddFirm([FromBody] ViewModel viewModel)
ajax
data: JSON.stringify({FirmName: this.FirmName, Email: this.CurrentEmail}),
Your configuration up top is your service configuration where you configure that all controllers and all endpoints in your controllers have the format of "{controller}/{action}/{id?}".
You can configure how your route is built not only on API level like you did in your example, but also on Controller and Endpoint level:
i.e.
[ApiController]
[Route("{controller}s"}
public class FirmController : ControllerBase
{
[HttpGet]
[Route("/{firmId}/contacts/{contactId}"]
public ActionResult GetContacts([FromRoute] int firmId, [FromRoute] int contactId)
{
...
}
[HttpPost]
public ActionResult AddFirm([FromBody] string firmName)
{
...
}
}
or even better, add a FirmModel for adding a new firm.
Also give https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design a read for properly designing an API.
So, the optimal solution would be this (Changes are explained in code comments):
[HttpPost]
public ActionResult AddFirm(string FIRM_NAME) // Can accept value from body AND query string. If you have more firm names then simply use List<string>/IEnumable<string> to represent it. If you have more parameters you want to pass, then simply write them like this: string FIRM_NAME, int NUM_OF_EMPLYEES and so on.
{
// Why are you using class String, when you can use the string keyword that does exactlly the same thing?
String query = "exec dbo.storeprocedername #FIRM_NAME";
System.Diagnostics.Debug.WriteLine("value:" + FIRM_NAME);
using (var connection = new SqlConnection(connectionString))
{
var json = connection.QuerySingle<string>(query, new { FIRM_NAME = FIRM_NAME});
return Content(json, "application/json"); // When sending JSON answear, be aware that every parameter from object will start with lowercased letter.
}
}
If you incist on using the [FromBody] tag, then you sadlly have to use models.

Route attribute Name property

I have ProductsController and OwnersController:
public class ProductsController : ApiController
{
//constructor is here
// GET /api/products
public IHttpActionResult GetProducts()
{
return Ok(new ApiResponse());
}
// GET /api/products/{productCode}
[HttpGet, Route("api/products/{productCode}")]
public IHttpActionResult GetProductByCode(string productCode)
{
return Ok(new ApiResponse());
}
// POST /api/products
public IHttpActionResult PostProduct(Product product /*my class*/)
{
return CreatedAtRoute("DefaultApi", new { id = product.Id }, product);
}
}
It works perfectly.
But now I create second controller and do the same things but I get the error when trying POST method. Another methods work well!
Lets take a look at the code first:
public class OwnersController : ApiController
{
// constructor
// GET /api/owners/{label}
// GET /api/owners/{label}?skip=1&take=4
[Route("api/owners/{label}")]
public IHttpActionResult GetOwnersExamples(string label, int skip=0, int take=10)
{
return Ok(new ApiResponse());
}
// POST /api/owners/{productCode}
//[HttpPost]
[Route("api/owners/{productCode}"/*, Name = "CreateOwner"*/)]
public IHttpActionResult PostOwner(string productCode, Owner owner)
{
return CreatedAtRoute("DefaultApi", new { id = Owner.Id }, owner);
}
}
Error message:
UrlHelper.Link must not return null
My RouteConfig:
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
As I understand problem that CreateAtRoute method must get another RouteName. As you see I can solve the problem by adding Route Name parameter (commented now) and replace "DefaultApi" with "CreateOwner" but it looks like a hack. I believe there is another method to do it avoiding Name Property.
P.S. Looks like my Web API can see only first controller (ProductsController) - any other methods doesn't work if I delete explicit Route [Route("...")]...
As I understand problem that CreateAtRoute method must get another
RouteName. As you see I can solve the problem by adding Route Name
parameter (commented now) and replace "DefaultApi" with "CreateOwner"
but it looks like a hack. I believe there is another method to do it
avoiding Name Property.
Your understanding is almost correct. However you should specify a name not for the current route, but for the one that points to created resource. CreatedAtRoute fills response Location header that should contain a GET-able URI for newly created resource.
Here is a working sample:
[HttpGet]
[Route("api/owners/{id}", Name = "GetOwnerById")]
public IHttpActionResult GetOwner(int id)
{
// Obtain the owner by id here
return Ok(new ApiResponse());
}
[HttpPost]
[Route("api/owners/{productCode}"/*, Name = "CreateOwner"*/)]
public IHttpActionResult PostOwner(string productCode, Owner owner)
{
return CreatedAtRoute("GetOwnerById", new { id = owner.Id }, owner);
}
(Note: For get this example working, you should comment GetOwnersExamples action, otherwise multiple action will match your GET request.)
You said that it looks like a hack, but it is not. CreatedAtRoute takes a route name as argument and you should provide it. How otherwise the proper action will be selected and Location header will be built?
I solve the problem using next steps:
Delete all [RoutePrefix] for controller - let them work by default - it works perfectly for simple requests.
IMPORTANT: check all methods for duplicates! Problem was I had 2 methods with routes api/controller/{label} and api/controller/{parameter} - it can't understand which of them to use by default. (Or use explicit uri: api/controller?label=1)
IMPORTANT: avoid to put into api methods a lot of complex types - create Wrapper for them and put only one parameter!
All this actions let me delete excess attributes and make methods more readable.
Here is the result:
public IHttpActionResult PostOwner(OwnerWrapper modelWrapper)
{
string productCode = modelWrapper.Product.Code;
Owner owner = modelWrapper.Owners[0];
return CreatedAtRoute("DefaultApi", new { id = Owner.Id }, owner);
}
It is just test case, so we can see productCode is never used, but my real realization is more difficult.

How to pass/receive multiple args to a RESTful Web API GET method?

The usual examples of GET RESTful methods that take a parameter (returning a scalar value rather than a dataset) are shown like so:
public string Get(int id)
{
//get and return the value
}
...where the val passed is typically an ID, so you can use it to get a scalar value based on that unique value.
What, though, if you want to pass multiple values, such as a string and an int? Is it simply a matter of defining a method like so:
public string Get(string someString, int someInt)
{
//get and return the value
}
...and calling it like so:
//const string uri = "http://192.112.183.42:80/api/platypusItems/someString/someInt";, zB:
const string uri = "http://192.112.183.42:80/api/platypusItems/DuckbilledPlatypisAreGuysToo/42";
var webRequest = (HttpWebRequest) WebRequest.Create(uri);
?
IOW, will the routing mechanism figure out that, since two args are passed, it should call the Get() method with two args ("convention over configuration"), or is there more that has to be done to route things appropriately?
If you use Web API 2, then you can use Attribute Routing to route requests like http://192.112.183.42:80/api/platypusItems/DuckbilledPlatypisAreGuysToo/42
public class ItemsController : ApiController
{
[Route("api/{controller}/{id}")]
public string GetItemById(int id)
{
// Find item here ...
return item.ToString();
}
[Route("api/{controller}/{name}/{id}")]
public string GetItemByNameAndId(string name, int id)
{
// Find item here ...
return item.ToString();
}
}
http://192.112.183.42:80/api/platypusItems/DuckbilledPlatypisAreGuysToo/42 will be mapped to GetItemByNameAndId while http://192.112.183.42:80/api/platypusItems/42 will be mapped to GetItemById.
Note, that you need to enable attribute routing in configuration like this:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
But generally you should pass arguments as additional parameters. It is especially easy with GET requests. This will work in Web API 1&2:
public class ItemsController : ApiController
{
public string GetItemById(int id)
{
// Find item here ...
return item.ToString();
}
public string GetItemByNameAndId(string name, int id)
{
// Find item here ...
return item.ToString();
}
}
Assuming that you have default mapping configuration, http://192.112.183.42:80/api/platypusItems/42 will be mapped to GetItemById while http://192.112.183.42:80/api/platypusItems/42?name=DuckbilledPlatypisAreGuysToo will be mapped to GetItemByNameAndId because Web API can map 2 parameters instead of 1 for GetItemById.
More information can be found in Mike Wasson articles on Attribute Routing, Routing and Action Selection and Routing in Web API.

How to implement Request / Response pattern with Web Api?

I'm new to MVC Web Api but I have some experience working with ServiceStack framework. Some of the web api examples look a lot like RPC with more action methods and less parameters. In fact most examples seem to limit the request parameter to an id. I would like to create some reporting services using request/response because the request gets quite complex with all the reporting criteria.
Here's my simplified request / response types:
public class PendingRequest
{
public string Id { get; set; }
public int AccountId { get; set; }
public DateTime? FromDate { get; set; }
public DateTime? ToDate { get; set; }
}
public class PendingResponse
{
public string Id { get; set; }
public IEnumerable<Pending> Data { get; set; }
}
And my outline reports controller:
public class ReportsController : ApiController
{
public async Task<PendingResponse> GetPending(PendingRequest request)
{
return new PendingResponse
{
Id = request.Id,
// Data = await Repo.GetPending()
};
}
public async Task<ShippedResponse> GetShipped(ShippedRequest request)
{
return new ShippedResponse
{
Id = request.Id,
// Data = await Repo.GetShipped()
};
}
public async Task<ProductsResponse> GetProducts(ProductsRequest request)
{
return new ProductsResponse
{
Id = request.Id,
// Data = await Repo.GetProducts()
};
}
}
And my routing and config for a self-hosting app:
class Program
{
static void Main()
{
var config = new HttpSelfHostConfiguration("http://localhost:8080");
config.Routes.MapHttpRoute(
name: "Reports",
routeTemplate: "api/reports/{action}",
defaults: new
{
controller = "Reports"
});
using (var server = new HttpSelfHostServer(config))
{
server.OpenAsync().Wait();
Console.WriteLine("Press Enter to quit.");
Console.ReadLine();
}
}
}
So with this routing I intend the action to be the name of the report. Note i've not included the request parameter here. Not sure if I should or not.
The problem is actually on the client side. With the HttpClient the GetAsync method does not allow me to include the JSON request object. I see an extension method PostAsJsonAsync but surely this shouldn't be a POST? Without a GetAsJsonAsync or similar then I can't see a way of including a request with a GET?
To be honest I prefer ServiceStack but there appears to be no async support yet. I need to develop some high performance services which are I/O intensive and I want to reduce the blocking as much as possible.
UPDATE:
It seems Web Api will perform the model binding if, rather than using the request body, I include the model's parameters as part of the query string. In order for this to work, the Get method needs to prefix the model type with the [FromUri] attribute, which informs the model binder that the model should be constructed from the request query string. Messy, but works.
[ActionName("Pending")]
public async Task<PendingResponse> GetPending([FromUri] PendingRequest request)
{
return new PendingResponse
{
Id = request.Id,
// query = ...build from request params
// Data = await Repo.GetPending(query)
};
}
And now on the client side I perform the following:
var result = await _httpClient.GetAsync("api/reports/pending?Id=123&AccountId=456");
result.EnsureSuccessStatusCode();
var response = await result.Content.ReadAsAsync<PendingResponse>();
A call to the service results in the GetPending method being called with a copy of the PendingRequest object materialised from the supplied query string parameters.
The fundamental problem here is you are trying to provide a request body to HTTP GET, which is not allowed. Well, you can still go ahead and format a GET with request body and submit it but that is not according to the spec. Strictly speaking, HTTP spec does not forbid GET requests from having a body but the response for a GET request must not change based on the request body, which basically means you cannot use search criteria in the GET request body. You can use the URI path and query string to specify the search criteria and if you want to bind them into a complex type parameter, you will need to use [FromUri] like this: public async Task<PendingResponse> GetPending([FromUri]PendingRequest request).
Bit of a guess, but what about just URL encoding the JSON request object and putting it in the query string segment of the GET URI?
Also, you might need [FromUri] attribute on your request method parameter in the action as it is a complex type. I may have missed something, but this article on parameter binding doesn't mention anything about GET being a special case for complex types, hence I think they will always try and read from the POST body.

Accessing URL parameters on MVC framework

public ActionResult Edit(string param)
{
}
http://localhost/Edit/5,someothervalue,etc
How can i reach 'param' from a global function. Basically I am trying to access the parameter in my url from a statinc method that I am building which will be global. That method breaks down the parameter and returns id and other things that I am passing inside param
I think you're looking for a way to get the route values from a given URL. Here's some code that might help you do this with a URL string. Just note that I put a IRouteRegistrant interface that just has a Register function that takes a route collection. Basically replace that with your registration mechanism.
public static RouteData GetRouteValues(IRouteRegistrant registrant, string url)
{
var routes = new RouteCollection();
registrant.Register(routes);
var context = new FakeHttpContext(url);
return routes.GetRouteData(context);
}
So to get at the values (for your param example) you just need the following:
public static void MyFn()
{
var values = GetRouteValues(....., "~/Edit/5");
var paramValue = values.Values["param"];
.....
}
Hope this helps.

Categories

Resources