How to get optional parameters in mvc action C# - c#

There is an TestAction in HomeController as below:
public ActionResult TestAction(string a = "a", int b = 2, int c = 1)
{
var DATA = Request.Params;//Can't get param neither a nor b
return View();
}
If my visit link is "/Home/TestAction?c=23". Then DATA will be {c=23}, but not contain a and b.
Does there any way to get these two params to make DATA like {a="a", b=2, c=23} by visit link "/Home/TestAction?c=23". (These params are different in different page, so can't hard-code).

You can do it by passing the params in the url as following. The model binder will read the query string and pass these parameter values to the action method.
/Home/TestAction?a=valuea&b=valueb
You can also use the route data to pass the value. An appropriate route will need to be defined to do that.

Take all the paramters as nullable
int?,
string accepts null by default.
So after changes your method will look like
public ActionResult TestAction(string a, int? b, int? c)
{
//Check conditions for null here
return View();
}

Related

Web API with complex array parameters

Need help on this one. I have a WebAPI who can receive multiple ids as parameters. The user can call the API using 2 route:
First route:
api/{controller}/{action}/{ids}
ex: http://localhost/api/{controller}/{action}/id1,id2,[...],idN
Method signature
public HttpResponseMessage MyFunction(
string action,
IList<string> values)
Second route:
"api/{controller}/{values}"
ex: http://localhost/api/{controller}/id1;type1,id2;type2,[...],idN;typeN
public HttpResponseMessage MyFunction(
IList<KeyValuePair<string, string>> ids)
Now I need to pass a new parameter to the 2 existing route. The problem is this parameter is optional and tightly associated with the id value. I made some attempt like a method with KeyValuePair into KeyValuePair parameter but its results in some conflict between routes.
What I need is something like that :
ex: http://localhost/api/{controller}/{action}/id1;param1,id2;param2,[...],idN;paramN
http://localhost/api/{controller}/id1;type1;param1,id2;type2;param2,[...],idN;typeN;paramN
You might be able to deal with it by accepting an array:
public HttpResponseMessage MyFunction(
string action,
string[] values)
Mapping the route as:
api/{controller}/{action}
And using the query string to supply values:
GET http://server/api/Controller?values=1&values=2&values=3
Assumption: You are actually doing some command with the data.
If your payload to the server is getting more complex than a simple route can handle, consider using a POST http verb and send it to the server as JSON instead of mangling the uri to shoehorn it in as a GET.
Different assumption: You are doing a complex fetch and GET is idiomatically correct for a RESTFUL service.
Use a querystring, per the answer posted by #TrevorPilley
Looks like a good scenario for a custom model binder. You can handle your incoming data and detect it your self and pass it to your own type to use in your controller. No need to fight with the built in types.
See here.
From the page (to keep the answer on SO):
Model Binders
A more flexible option than a type converter is to create a custom
model binder. With a model binder, you have access to things like the
HTTP request, the action description, and the raw values from the
route data.
To create a model binder, implement the IModelBinder interface. This
interface defines a single method, BindModel:
bool BindModel(HttpActionContext actionContext, ModelBindingContext
bindingContext);
Here is a model binder for GeoPoint objects.
public class GeoPointModelBinder : IModelBinder {
// List of known locations.
private static ConcurrentDictionary<string, GeoPoint> _locations
= new ConcurrentDictionary<string, GeoPoint>(StringComparer.OrdinalIgnoreCase);
static GeoPointModelBinder()
{
_locations["redmond"] = new GeoPoint() { Latitude = 47.67856, Longitude = -122.131 };
_locations["paris"] = new GeoPoint() { Latitude = 48.856930, Longitude = 2.3412 };
_locations["tokyo"] = new GeoPoint() { Latitude = 35.683208, Longitude = 139.80894 };
}
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(GeoPoint))
{
return false;
}
ValueProviderResult val = bindingContext.ValueProvider.GetValue(
bindingContext.ModelName);
if (val == null)
{
return false;
}
string key = val.RawValue as string;
if (key == null)
{
bindingContext.ModelState.AddModelError(
bindingContext.ModelName, "Wrong value type");
return false;
}
GeoPoint result;
if (_locations.TryGetValue(key, out result) || GeoPoint.TryParse(key, out result))
{
bindingContext.Model = result;
return true;
}
bindingContext.ModelState.AddModelError(
bindingContext.ModelName, "Cannot convert value to Location");
return false;
} } A model binder gets raw input values from a value provider. This design separates two distinct functions:
The value provider takes the HTTP request and populates a dictionary
of key-value pairs. The model binder uses this dictionary to populate
the model. The default value provider in Web API gets values from the
route data and the query string. For example, if the URI is
http://localhost/api/values/1?location=48,-122, the value provider
creates the following key-value pairs:
id = "1" location = "48,122" (I'm assuming the default route template,
which is "api/{controller}/{id}".)
The name of the parameter to bind is stored in the
ModelBindingContext.ModelName property. The model binder looks for a
key with this value in the dictionary. If the value exists and can be
converted into a GeoPoint, the model binder assigns the bound value to
the ModelBindingContext.Model property.
Notice that the model binder is not limited to a simple type
conversion. In this example, the model binder first looks in a table
of known locations, and if that fails, it uses type conversion.
Setting the Model Binder
There are several ways to set a model binder. First, you can add a
[ModelBinder] attribute to the parameter.
public HttpResponseMessage
Get([ModelBinder(typeof(GeoPointModelBinder))] GeoPoint location)
You
can also add a [ModelBinder] attribute to the type. Web API will use
the specified model binder for all parameters of that type.
[ModelBinder(typeof(GeoPointModelBinder))] public class GeoPoint {
// .... }
I found a solution.
First, I created a class to override the
KeyValuePair<string, string>
type to add a third element (I know it's not really a pair!). I could have use Tuple type also:
public sealed class KeyValuePair<TKey, TValue1, TValue2>
: IEquatable<KeyValuePair<TKey, TValue1, TValue2>>
To use this type with parameter, I create an
ActionFilterAttribute
to split (";") the value from the url and create a KeyValuePair (third element is optional)
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ActionArguments.ContainsKey(ParameterName))
{
var keyValuePairs = /* function to split parameters */;
actionContext.ActionArguments[ParameterName] =
keyValuePairs.Select(
x => x.Split(new[] { "," }, StringSplitOptions.None))
.Select(x => new KeyValuePair<string, string, string>(x[0], x[1], x.Length == 3 ? x[2] : string.Empty))
.ToList();
}
}
And finally, I add the action attribute filter to the controller route and change the parameter type:
"api/{controller}/{values}"
ex: http://localhost/api/{controller}/id1;type1;param1,id2;type2,[...],idN;typeN;param3
[MyCustomFilter("ids")]
public HttpResponseMessage MyFunction(
IList<KeyValuePair<string, string, string>> ids)
I could use some url parsing technique, but the ActionFilterAttribute is great and the code is not a mess finally!

Action method expected parameter named Id and nothing otherwise

The Error
The parameters dictionary contains a null entry for parameter 'UserId'
of non-nullable type 'System.Int64' for method
'System.Web.Mvc.ActionResult Predict(Int64)' in
'sportingbiz.Controllers.PredictionController'. An optional parameter
must be a reference type, a nullable type, or be declared as an
optional parameter. Parameter name: parameters
This does not work. Throws the error mentioned above
http://mysite/User/Profile/15
This works
http://mysite/User/Profile/?UserID=15
The Controller Action
public ActionResult Profile(long UserID)
{
}
When I changed the parameter name to Id it works. I think it's because Id was specified in the route collection (Global.asax). Is it possible to tell MVC that UserId should map to Id without changing it in the Global.asax
The only way to accomplish this (without getting into custom ModelBinders, which gets really hairy) is to:
make the parameter nullable
use the RouteData collection to set the property after-the-fact.
public ActionResult Profile(long? UserID)
{
UserID = UserID ?? long.Parse((string)RouteData.Values["id"]);
}
I would always make the id param on a controller something generic like Id, then you dont have to create lots of routes to match the different id types.
If you aren't using a string which is nullable for your id, you can provide a default value such as long id = 0 and handle the zero value as being the default starting sequence.
For example, in a paging method, you can set it to zero so you dont need any params on the first page, but passing it thereafter will request that page number etc etc
Si
I think the code already posted should work so I tested my version and it worked:
public ActionResult Profile(long? UserID)
{
if (UserID.HasValue)
{
}
else if (this.RouteData.Values["id"] != null)
{
long tempValue = 0;
if (long.TryParse(this.RouteData.Values["id"].ToString(), out tempValue))
{
UserID = tempValue;
}
}
return View();
}
Hope it helps.

How to pass List in Redirecttoaction

I want to pass more then one parameter from RedirectToAction method
how can I pass?
My One Action Method
[HttpPost, ActionName("SelectQuestion")]
public ActionResult SelectQuestion(string email,List<QuestionClass.Tabelfields> model)
{
List<QuestionClass.Tabelfields> fadd = new List<QuestionClass.Tabelfields>();
for (int i = 0; i < model.Count; i++)
{
if (model[i].SelectedCheckbox == true)
{
List<QuestionClass.Tabelfields> f = new List<QuestionClass.Tabelfields>();
fadd.Add(model[i]);
}
}
return RedirectToAction("Question", new { email = email, model = fadd.ToList() });
}
My another Action Method
[HttpGet]
public ActionResult Question(string email,List<QuestionClass.Tabelfields> model)
{
}
I am not getting values in model.
You cannot pass a collection of complex objects in urls when redirecting.
One possibility would be to use TempData:
TempData["list"] = fadd.ToList();
return RedirectToAction("Question", new { email = email});
and then inside the Question action:
var model = TempData["list"] as List<QuestionClass.Tablefields>;
The way that I solved this problem was to serialize the list to a JSON object using the JsonConvert method from the Newtonsoft.Json nuget package. Then the serialized list can be passed as a parameter and then deserialized again to re-create the original list.
So in your SelectQuestion method you would use this code:
return RedirectToAction("Question",
new {
email = email,
serializedModel = JsonConvert.SerializeObject(fadd.ToList())
});
And in your Question method, you would use this code to deserialize the object.
[HttpGet]
public ActionResult Question(string email, string serializedModel)
{
// Deserialize your model back to a list again here.
List<QuestionClass.Tabelfields> model = JsonConvert.DeserializeObject<List<QuestionClass.Tabelfields>>(serializedModel);
}
Important, this adds the model as a query string parameter to your url, so only do this with really simple small objects, otherwise your url will be too long.
This is probably not even active anymore, but I'll leave how I did it here to maybe help someone else.
I solved this using a simple Redirect instead of a RedirectToAction:
List<int> myList = myListofItems;
var list = HttpUtility.ParseQueryString("");
myList.ForEach(x => list.Add("parameterList", x.ToString()));
return Redirect("/MyPath?" + list);
Then, on your other method:
public ActionResult Action(List<int> parameterList){}
RedirectToAction method Returns an HTTP 302 response to the browser, which causes the browser to make a GET request to the specified action.
You should either keep the data in a temporary storage like TempData / Session . TempData uses Session as the backing storage.
If you want to keep it real Stateless, you should pass an id in the query string and Fetch the List of items in your GET Action. Truly Stateless.
return RedirectToAction("Question", new { email = email,id=model.ID });
and in your GET method
public ActionResult Question(string email,int id)
{
List<QuestionClass.Tabelfields> fadd=repositary.GetTabelFieldsFromID(id);
//Do whatever with this
return View();
}
Assuming repositary.GetTabelFieldsFromID returns a List of TabelFields from the Id

How to send array to another controller method in asp.net mvc?

customers is a List<string>.
RedirectToAction("ListCustomers", new { customers = customers });
And when I send the list it contains 4 items, but when I receive it in my controller method it has only one item and it's of type generic list. That seems not be what I want. But how to pass more complex data than strings and integer between controller methods?
You cannot send complex objects when redirecting. When redirecting you are sending a GET request to the target action. When sending a GET request you need to send all information as query string parameters. And this works only with simple scalar properties.
So one way is to persist the instance somewhere on the server before redirecting (in a database for example) and then pass only an id as query string parameter to the target action which will be able to retrieve the object from where it was stored:
int id = Persist(customers);
return RedirectToAction("ListCustomers", new { id = id });
and inside the target action:
public ActionResult ListCustomers(int id)
{
IEnumerable<string> customers = Retrieve(id);
...
}
Another possibility is to pass all the values as query string parameters (be careful there's a limit in the length of a query string which will vary among browsers):
public ActionResult Index()
{
IEnumerable<string> customers = new[] { "cust1", "cust2" };
var values = new RouteValueDictionary(
customers
.Select((customer, index) => new { customer, index })
.ToDictionary(
key => string.Format("[{0}]", key.index),
value => (object)value.customer
)
);
return RedirectToAction("ListCustomers", values);
}
public ActionResult ListCustomers(IEnumerable<string> customers)
{
...
}
Yet another possibility is to use TempData (not recommended):
TempData["customer"] = customers;
return RedirectToAction("ListCustomers");
and then:
public ActionResult ListCustomers()
{
TempData["customers"] as IEnumerable<string>;
...
}

ASP.NET MVC - Extract parameter of an URL

I'm trying to extract the parameters of my URL, something like this.
/Administration/Customer/Edit/1
extract: 1
/Administration/Product/Edit/18?allowed=true
extract: 18?allowed=true
/Administration/Product/Create?allowed=true
extract: ?allowed=true
Someone can help? Thanks!
Update
RouteData.Values["id"] + Request.Url.Query
Will match all your examples
It is not entirely clear what you are trying to achieve. MVC passes URL parameters for you through model binding.
public class CustomerController : Controller {
public ActionResult Edit(int id) {
int customerId = id //the id in the URL
return View();
}
}
public class ProductController : Controller {
public ActionResult Edit(int id, bool allowed) {
int productId = id; // the id in the URL
bool isAllowed = allowed // the ?allowed=true in the URL
return View();
}
}
Adding a route mapping to your global.asax.cs file before the default will handle the /administration/ part. Or you might want to look into MVC Areas.
routes.MapRoute(
"Admin", // Route name
"Administration/{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
If it's the raw URL data you are after then you can use one of the various URL and Request properties available in your controller action
string url = Request.RawUrl;
string query= Request.Url.Query;
string isAllowed= Request.QueryString["allowed"];
It sounds like Request.Url.PathAndQuery could be what you want.
If you want access to the raw posted data you can use
string isAllowed = Request.Params["allowed"];
string id = RouteData.Values["id"];
public ActionResult Index(int id,string value)
This function get values form URL
After that you can use below function
Request.RawUrl - Return complete URL of Current page
RouteData.Values - Return Collection of Values of URL
Request.Params - Return Name Value Collections
You can get these parameter list in ControllerContext.RoutValues object as key-value pair.
You can store it in some variable and you make use of that variable in your logic.
I wrote this method:
private string GetUrlParameter(HttpRequestBase request, string parName)
{
string result = string.Empty;
var urlParameters = HttpUtility.ParseQueryString(request.Url.Query);
if (urlParameters.AllKeys.Contains(parName))
{
result = urlParameters.Get(parName);
}
return result;
}
And I call it like this:
string fooBar = GetUrlParameter(Request, "FooBar");
if (!string.IsNullOrEmpty(fooBar))
{
}
In order to get the values of your parameters, you can use RouteData.
More context would be nice. Why do you need to "extract" them in the first place? You should have an Action like:
public ActionResult Edit(int id, bool allowed) {}
I'm not familiar with ASP.NET but I guess you could use a split function to split it in an array using the / as delimiter, then grab the last element in the array (usually the array length -1) to get the extract you want.
Ok this does not seem to work for all the examples.
What about a regex?
.*(/|[a-zA-Z]+\?)(.*)
then get that last subexpression (.*), I believe it's $+ in .Net, I'm not sure

Categories

Resources