How to get GET parameters with ASP.NET MVC ApiController - asp.net-mvc

I feel a bit absurd asking this but I can't find a way to get parameters for a get request at
/api/foo?sort=name for instance.
In the ApiController class, I gave a public string Get(). Putting Get(string sort) makes /api/foo a bad request. Request instance in the ApiController is of type System.Net.Http.HttpRequestMessage. It doesn't have a QueryString or Parameters property or anything.

The ApiController is designed to work without the HttpContext object (making it portable, and allowing it to be hosted outside of IIS).
You can still access the query string parameters, but it is done through the following property:
Request.GetQueryNameValuePairs()
Here's an example loop through all the values:
foreach (var parameter in Request.GetQueryNameValuePairs())
{
var key = parameter.Key;
var value = parameter.Value;
}

You could just use
HttpContext.Current.Request.QueryString

Here's an example that gets the querystring q from the request and uses it to query accounts:
var q = Request.GetQueryNameValuePairs().Where(nv => nv.Key =="q").Select(nv => nv.Value).FirstOrDefault();
if (q != null && q != string.Empty)
{
var result = accounts.Where(a=>a.Name.ToLower().StartsWith(q.ToLower()));
return result;
}
else
{
throw new Exception("Please specify a search query");
}
This can be called then like this:
url/api/Accounts?q=p

Get all querystring name/value pairs into a variable:
IEnumerable<KeyValuePair<string, string>> queryString = request.GetQueryNameValuePairs();
Then extract a specified querystring parameter
string value = queryString.Where(nv => nv.Key == "parameterNameGoesHere").Select(nv => nv.Value).FirstOrDefault();

You can also use the following
var value = request.GetQueryNameValuePairs().Where(m => m.Key == "paramName").SingleOrDefault().Value;

if we have a proper model for that request
for example
public class JustModel
{
public int Id {get;set;}
public int Age {gets;set;}
}
and query like this
/api/foo?id=1&Age=10
You could just use [FromUri] attribute
For example
public IHttpActionResult GetAge([FromUri] JustModel model){}

You're trying to build an OData webservice? If so, just return an IQueryable, and the Web API will do the rest.

Adding a default value does the job:
public string Get(string sort="")

Related

How to map querystring to action method parameters in MVC?

I have a url http://localhost/Home/DomSomething?t=123&s=TX and i want to route this URL to the following action method
public class HomeController
{
public ActionResult DoSomething(int taxYear,string state)
{
// do something here
}
}
since the query string names does not match with action method's parameter name, request is not routing to the action method.
If i change the url (just for testing) to http://localhost/Home/DomSomething?taxYear=123&state=TX then its working. (But i dont have access to change the request.)
I know there is Route attribute i can apply on the action method and that can map t to taxYear and s to state.
However i am not finding the correct syntax of Route attribute for this mapping, Can someone please help?
Option 1
If Query String parameters are always t and s, then you can use Prefix. Note that it won't accept taxYear and state anymore.
http://localhost:10096/home/DoSomething?t=123&s=TX
public ActionResult DoSomething([Bind(Prefix = "t")] int taxYear,
[Bind(Prefix = "s")] string state)
{
// do something here
}
Option 2
If you want to accept both URLs, then declare all parameters, and manually check which parameter has value -
http://localhost:10096/home/DoSomething?t=123&s=TX
http://localhost:10096/home/DoSomething?taxYear=123&state=TX
public ActionResult DoSomething(
int? t = null, int? taxYear = null, string s = "", string state = "")
{
// do something here
}
Option 3
If you don't mind using third party package, you can use ActionParameterAlias. It accepts both URLs.
http://localhost:10096/home/DoSomething?t=123&s=TX
http://localhost:10096/home/DoSomething?taxYear=123&state=TX
[ParameterAlias("taxYear", "t")]
[ParameterAlias("state", "s")]
public ActionResult DoSomething(int taxYear, string state)
{
// do something here
}

Adding new parameter to a web API action method without disturbing the existing contract

I have an action method already written in my web api 2.0 project. I would like to add a new parameter without disturbing the existing contract. What is the best way to do that? Appreciate any best practice hints on this :)
Here's the code sample of what I intend to do:
Existing code:
[Route("{myId}",Name="MyId")]
Public IHttpActionResult Get(String myId)
{
//Some more code here
}
Url: http://localhost:8888/webapi/1111
Expecting to do something like the below:
//I want to keep the route name same for backwards compatibility.
[Route("{myId}/{myName}",Name="MyId")]
Public IHttpActionResult Get(String myId,string? myName)
{
//Some more code here
}
Url: http://localhost:8888/webapi/1111/John
The Url mentioned above hits the method rightly, but I never get the second parameter (myName) populated with John.
Thanks everyone for any help towards this.
Sree.
In your example you have myName as string? which is not allowed as:
The type 'string' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'System.Nullable'
A test controller was created to implement you action
[RoutePrefix("webapi")]
public class TestsController : ApiController {
[HttpGet]
[Route("{myId}/{myName}", Name = "MyId")]
public IHttpActionResult Get(string myId, string myName) {
//Some code to show the values of the parameters
return Ok(new { myId = myId, myName = myName });
}
}
When tested with webapi/1111/John the following response is returned
{"myId":"1111","myName":"John"}
which does include the value for MyName as John
If backwards uri webapi/1111 is tried, a NotFound response is returned as the template does not match the new action.
To fix this you need to make the myName parameter optional. To learn more about that check
Optional URI Parameters and Default Values
The new route will look like
//NOTICE THE `?` ON THE {myName} TEMPLATE
[Route("{myId}/{myName?}", Name = "MyId")]
public IHttpActionResult Get(string myId, string myName = null) {...}
You will notice that myName was made optional in the route {myId}/{myName?} and in the action parameter (string myId, string myName = null)
Now when tested with webapi/1111 the following response is returned
{"myId":"1111","myName":null}
Which would match your expected result for backwards compatibility.
String is a reference type so you don't need to make it nullable, it already is. Remove the '?' and remove the Name from the attribute. What happens then?

Ninject Call Method to Return Constructor Argument

I'm trying to bind a service and specifying a constructor argument using Ninject in our application. The constructor argument is a value that can be pulled from the query string or a cookie. The code we currently have is something like this
kernel.Bind<SomeService>()
.ToSelf()
.InRequestScope()
.WithConstructorArgument("someID", ctx =>
// Try to get it from the posted form values
System.Web.HttpContext.Current.Request.Form["someID"] != null ?
long.Parse(System.Web.HttpContext.Current.Request.Form["someID"]) :
// Try to get it from the query string
System.Web.HttpContext.Current.Request.QueryString["someID"] != null ?
long.Parse(System.Web.HttpContext.Current.Request.QueryString["someID"])
: 0);
This works but is pretty ugly. I realize there are other ways of accomplishing this such as passing in the Form value or the QueryString value as a parameter, but we like having it defined in the Binding. What we would ideally like to do is something like this:
kernel.Bind<SomeService>()
.ToSelf()
.InRequestScope()
.WithConstructorArgument("someID", ctx => GetSomeID());
From what I can tell, this is not possible. Is there another way to break out the constructor argument injection logic into another method so we don't have to nested one line if statements?
I'd suggest binding the dependency on the Query String / HTTP form via an interface. This approach seems more in line with the dependency injection pattern (de-coupling code from specific implementations and classes).
public interface IParameters
{
string SomeID { get; }
}
public class ParametersFromHttpContext
{
IQueryString _queryString;
IRequestForm _requestForm;
public ParametersFromHttpContext(IQueryString queryString, IRequestForm requestForm)
{
_queryString = queryString;
_requestForm = requestForm;
}
public string SomeID
{
get
{
return
// Try to get it from the posted form values
_requestForm["someID"] != null ?
long.Parse(_requestForm["someID"]) :
// Try to get it from the query string
_queryString["someID"] != null ?
long.Parse(_queryString["someID"])
: 0;
}
}
}
Now logic you want can be contained in the binding, without the need to reference HttpContext in the kernel.
kernel.Bind<IParameters>().To<ParametersFromHttpContext>();

How do I include a model with a RedirectToAction?

In the RedirectToAction below, I'd like to pass a viewmodel. How do I pass the model to the redirect?
I set a breakpoint to check the values of model to verify the model is created correctly. It is correct but the resulting view does not contain the values found in the model properties.
//
// model created up here...
//
return RedirectToAction("actionName", "controllerName", model);
ASP.NET MVC 4 RC
RedirectToAction returns a 302 response to the client browser and thus the browser will make a new GET request to the url in the location header value of the response came to the browser.
If you are trying to pass a simple lean-flat view model to the second action method, you can use this overload of the RedirectToAction method.
protected internal RedirectToRouteResult RedirectToAction(
string actionName,
string controllerName,
object routeValues
)
The RedirectToAction will convert the object passed(routeValues) to a query string and append that to the url(generated from the first 2 parameters we passed) and will embed the resulting url in the location header of the response.
Let's assume your view model is like this
public class StoreVm
{
public int StoreId { get; set; }
public string Name { get; set; }
public string Code { set; get; }
}
And you in your first action method, you can pass an object of this to the RedirectToAction method like this
var m = new Store { StoreId =101, Name = "Kroger", Code = "KRO"};
return RedirectToAction("Details","Store", m);
This code will send a 302 response to the browser with location header value as
Store/Details?StoreId=101&Name=Kroger&Code=KRO
Assuming your Details action method's parameter is of type StoreVm, the querystring param values will be properly mapped to the properties of the parameter.
public ActionResult Details(StoreVm model)
{
// model.Name & model.Id will have values mapped from the request querystring
// to do : Return something.
}
The above will work for passing small flat-lean view model. But if you want to pass a complex object, you should try to follow the PRG pattern.
PRG Pattern
PRG stands for POST - REDIRECT - GET. With this approach, you will issue a redirect response with a unique id in the querystring, using which the second GET action method can query the resource again and return something to the view.
int newStoreId=101;
return RedirectToAction("Details", "Store", new { storeId=newStoreId} );
This will create the url Store/Details?storeId=101
and in your Details GET action, using the storeId passed in, you will get/build the StoreVm object from somewhere (from a service or querying the database etc)
public ActionResult Details(string storeId)
{
// from the storeId value, get the entity/object/resource
var store = yourRepo.GetStore(storeId);
if(store!=null)
{
// Map the the view model
var storeVm = new StoreVm { Id=storeId, Name=store.Name,Code=store.Code};
return View(storeVm);
}
return View("StoreNotFound"); // view to render when we get invalid store id
}
TempData
Following the PRG pattern is a better solution to handle this use case. But if you don't want to do that and really want to pass some complex data across Stateless HTTP requests, you may use some temporary storage mechanism like TempData
TempData["NewCustomer"] = model;
return RedirectToAction("Index", "Users");
And read it in your GET Action method again.
public ActionResult Index()
{
var model=TempData["NewCustomer"] as Customer
return View(model);
}
TempData uses Session object behind the scene to store the data. But once the data is read the data is terminated.
Rachel has written a nice blog post explaining when to use TempData /ViewData. Worth to read.
Using TempData to pass model data to a redirect request in Asp.Net Core
In Asp.Net core, you cannot pass complex types in TempData. You can pass simple types like string, int, Guid etc.
If you absolutely want to pass a complex type object via TempData, you have 2 options.
1) Serialize your object to a string and pass that.
Here is a sample using Json.NET to serialize the object to a string
var s = Newtonsoft.Json.JsonConvert.SerializeObject(createUserVm);
TempData["newuser"] = s;
return RedirectToAction("Index", "Users");
Now in your Index action method, read this value from the TempData and deserialize it to your CreateUserViewModel class object.
public IActionResult Index()
{
if (TempData["newuser"] is string s)
{
var newUser = JsonConvert.DeserializeObject<CreateUserViewModel>(s);
// use newUser object now as needed
}
// to do : return something
}
2) Set a dictionary of simple types to TempData
var d = new Dictionary<string, string>
{
["FullName"] = rvm.FullName,
["Email"] = rvm.Email;
};
TempData["MyModelDict"] = d;
return RedirectToAction("Index", "Users");
and read it later
public IActionResult Index()
{
if (TempData["MyModelDict"] is Dictionary<string,string> dict)
{
var name = dict["Name"];
var email = dict["Email"];
}
// to do : return something
}
Another way to do it is to store it in the session.
var s = JsonConvert.SerializeObject(myView);
HttpContext.Session.SetString("myView", s);
and to get it back
string s = HttpContext.Session.GetString("myView");
myView = JsonConvert.DeserializeObject<MyView>(s);

asp.net mvc controller arguments a?b1,c2... how to parse variable number?

If a URL, in asp.net mvc, has args that are not catered for in the index/show method signature of a controller, is it possible to use something like a params string[] args in the signature to gather them up. Or what is a good way to do this when the arg is a list of separated values (ie not name/value pairs)?
We have users, ultimately, creating urls with a variable number of arguments & need to parse them.
This is the code we have at the moment, but we can't help thinking there's a better way without splitting the string ourselves:
var url = Request.RawUrl.Split('?');
if (url.Length > 1)
{
var queryString = url[1];
var queryStringArgs = queryString.Split('&');
var queryStringMembers = from arg in queryStringArgs
let c = arg.Split('=').Length == 1
where c
select arg;
ViewBag.QueryStringMembers = queryStringMembers.ToJson();
}
*Append: these args dont have name=value, it's just a list of values.
Request.QueryString doesn't seem to help us, as it treats these query string args differently because they are not name=value, they are just value. So it puts them in a Request.QueryString[null] key as comma separated
First things first what you have is a malformed URL. So you are totally on your own parsing it. You may also expect it to fail any time. Everything that is part of the querystring, i.e. following the first ?, must be url encoded, you should not have multiple ?.
This being said you could write a custom model binder:
public class CustomModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var tokens = controllerContext.HttpContext.Request.RawUrl.Split('?');
if (tokens.Length > 1)
{
return tokens.Skip(1).ToArray();
}
return null;
}
}
and then:
public ActionResult Index([ModelBinder(typeof(CustomModelBinder))]string[] args)
{
return View();
}
Request.QueryString.AllKeys will contain a string array of the argument names sent in on the QueryString. Is that what you're looking for?

Resources