MVC actions with generic parameter names AND specific names - asp.net-mvc

I would like to have a method that takes either specific parameter names, or generic ones:
//generic
public ActionResult Delete(string param1, string param2, int param3)
{
//do stuff
}
//specific
public ActionResult Delete(string sAssetCode, string sLockout, int Seq)
{
//do stuff
}
The generic method naming allows me to setup routes like the below one, which allows me to easily create a URL to delete objects via almost any controller:
routes.MapRoute(
name: "AssetLockoutDelete",
url: "{controller}/Delete/{param1}/{param2}/{param3}",
defaults: new { action = "Delete" }
);
But this means that the methods are a little harder to read and follow, as the parameters are generic now.
How can I have both methods exist in my controller while still being elegant?
One possibility is the following, but it seems not as elegent:
//generic
public ActionResult Delete_Helper(string param1, string param2, int param3)
{
//do stuff
}
//specific
public ActionResult Delete(string sAssetCode, string sLockout, int Seq)
{
//do stuff
}
//redirects all deletes to the "helper" method that uses generic parameter names
routes.MapRoute(
name: "AssetLockoutDelete",
url: "{controller}/Delete/{param1}/{param2}/{param3}",
defaults: new { action = "Delete_Helper" }
);

Related

How can I convert MVC Route to parameter of type Boolean?

Change route segment into boolean
I have a simple action method in my controller and would like to pass a boolean value to my action when the string "Company" is passed in the url.
public ActionResult DoSomething(Boolean ? isCompany) { }
I would like to call it like this.
http://mysite/Feature/Configure/Company
So basically, when "Company" is in the url then pass isCompany as true boolean.
Is this possible?
According to the answer below I think I should then use the following mapping.
routes.MapRoute(
name: "FeatureConfigure",
url: "Feature/Configure/{company}",
defaults: new { controller = "Feature", action="Configure",
company = UrlParameter.Optional }
);
public ActionResult Configure(String company) {
var isCompany = company == "Company";
// maybe use a switch if there are multiple values company can be
// and probably rename the variable to something more generic.
}
Not directly, you'd have to handle it like:
public ActionResult DoSometing(string foo)
{
var isCompany = foo == "Company";
}
Really comes back to what you are trying to achieve.
Having a check on an input like this, suggests that you will be planning to have a fork in your logic and possibly display different content/views.
Simpler option would be to let MVC handle it and keep your code cleaner.
Option 1:
Changing the {company} portion of your route to {action} and fork your logic at the action level.
routes.MapRoute(
name: "FeatureConfigure",
url: "Feature/Configure/{action}",
defaults: new { controller = "Feature", action="Index" }
);
public ActionResult Company() {
// Do company stuff
}
public ActionResult Branch() {
// Do branch stuff
}
Option 2:
Same as above but using route attributes (assuming you're using MVC5).
To do that remove the FeatureConfigure route from the config.
Then include the following on the controller and actions:
[RoutePrefix("features/configure")]
public class FeatureController : Controller
{
....
[Route("{type:regex(company)}")]
public ActionResult Company()
{
// do company stuff
}
[Route("{type:regex(branch)}")]
public ActionResult Branch()
{
// do branch stuff
}
...
}
Note: in either case it would probably make more sense to rename FeatureController to ConfigureContoller but that's up to you

web api 2.1 routing not working but same works on mvc

I'm sending multiple requests to the same action method in a controller all these requests have some common querystring attributes and some specific to that request.
request1 : http://localhost/home/test?a=a1&b=b1&c=c1&d=d1....around 25 parameters
request2 : http://localhost/home/test?a=a1&b=b1&j=j1&k=k1...around 20 parameters
similarly request 3 , request4,etc...
My action method in mvc in homecontroller is as below..
public string test(string a, string b, string c, string d, ...around 50 parameters)
This is working perfectly..
But when I take this code and move it to web api, it no longer works..
Moreover, if I try with just two parameters, it works and I can get the two parameters..
public string test(string a, string b)
I have no control on the requests that I receive in my application as it is coming from a 3rd party host application, so the method name and parameters can not change ...
The route configured in mvc in route.config is standard..
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
I have configured a separate route for webapi in webapiconfig on similar lines..
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Any ideas how to solve this..
Thanks
Arnab
The reason is that Web API does action overloading and all these parameters are required, if they are not provided you are ending up with 404. The simple answer to your question is to make them optional by giving them a default value, so your signature will look like this:
public IHttpActionResult Get(string a = null, string b = null, ...)
However this code seems very elaborate for what you are doing, it's probably also not the most efficient and you end up with a lot of if statements.
Consider alternatively just parsing the query string yourself and get a more convenient to use data set.
public class ValuesController : ApiController
{
public IHttpActionResult Get()
{
var collection = Request.RequestUri.ParseQueryString();
foreach (var key in collection.Keys)
{
var value = collection[(string)key];
// do something with key & value
}
return Ok();
}
}
and as another option is to build a model including all the parameters, something like:
public class Settings
{
public string A { get; set; }
public string B { get; set; }
...
}
and bind to the model using the FromUri:
public IHttpActionResult Get([FromUri]Settings settings)
{
...
}
Here is a link from Mike Stall's blog - http://blogs.msdn.com/b/jmstall/archive/2012/04/16/how-webapi-does-parameter-binding.aspx

can't figure out routing in asp.net mvc

I'm doing my first steps in asp.net mvc trying to develop web api.
I have the following routing function:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "cdApiDefault",
url: "api/{controller}/{action}/{token}/{mid}/{id}",
defaults: new {
token = UrlParameter.Optional,
mid = UrlParameter.Optional,
id = RouteParameter.Optional }
);
}
and the following controller:
namespace cdapi.Controllers
{
public class PostsController : ApiController
{
// GET api/posts
public IEnumerable<string> Get()
{
return new string[] { "GET_value1", "GET_value2" };
}
// GET api/posts/5
public string Get(int id)
{
return "value!!!!!!!!!!!!!!!!!";
}
// POST api/posts
public void Post([FromBody]string value)
{
}
// PUT api/posts/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/posts/5
public void Delete(int id)
{
}
public String GetTest(String token, String mid)
{
return token + " - " + mid;
}
}
}
the following call
hxxp://localhost:52628/api/posts/5
(in my browser) yields some result, i.e., the function GET is being called and return a value.
However, when I try
hxxp://localhost:52628/api/posts/GetTest/MyTestToken/myTestMid
comes back with 'the resource can not be found' error message.
I thought that the {Action} should contain the function to call and that the 'token' and 'mid' should contain the values I specify. What do I do wrong?
ApiControllers work differently than regular MVC Controllers. Here, method names (GET, POST, PUT, DELETE) represent HTTP VERBS, not url fragment. In your first call,
/api/posts/5
this invokes Get(int).
To do routing like you want, switch to standard MVC by inheriting from System.Web.Mvc.Controller instead of System.Web.Http.ApiController and modify your methods to return ActionResult

RedirectToAction from different controller and with parameters/routevalues not working

I’m trying to call a action method from different controller but it´s not working. It simply skips the RedirectToAction
here's my code:
public ActionResult sendPolicy(TempPoliciesUpload TempPolicy)
{
return RedirectToAction("Insert", "Policies", new { tempPolicy = TempPolicy });
}
Please help.
You cannot send complex objects when redirecting. You will have to include each property as query string parameter. And this works only with simply scalar properties.
public ActionResult sendPolicy(TempPoliciesUpload TempPolicy)
{
return RedirectToAction("Insert", "Policies", new
{
id = TempPolicy.Id,
prop1 = TempPolicy.Prop1,
prop2 = TempPolicy.Prop2,
...
});
}
If you have complex properties you will have to include them as well so that the default model binder is able to bind the model in the target action from the query string parameters:
public ActionResult sendPolicy(TempPoliciesUpload TempPolicy)
{
return RedirectToAction("Insert", "Policies", new RouteValueDictionary
{
{ "id", TempPolicy.Id },
{ "prop1", TempPolicy.Prop1 },
{ "prop2", TempPolicy.Prop2 },
{ "prop3.subprop1", TempPolicy.Prop3.SubProp1 },
{ "prop3.subprop2", TempPolicy.Prop3.SubProp2 },
...
});
}
and your target action:
public ActionResult Insert(TempPoliciesUpload TempPolicy)
{
...
}
Another possibility is to persist this object in your backend before redirecting and then pass only the id:
public ActionResult sendPolicy(TempPoliciesUpload TempPolicy)
{
int id = Repository.Save(TempPolicy);
return RedirectToAction("Insert", "Policies", new { id = id });
}
and in your target action:
public ActionResult Insert(int id)
{
TempPoliciesUpload TempPolicy = Repository.Get(id);
...
}
I hope you have
public ActionResult Insert(TempPoliciesUpload tempPolicy)
action method in PoliciesController class.
Please see the overload of RedirectToAction here
Remove the parameter from the controller you're redirecting to and remove new { tempPolicy = TempPolicy }. See if that works (and then you localized the problem to parameter).
Most likely you need to cast it to the type of the action you redirecting to (hence Mark asked you for that signature) or play with it otherwise, maybe put in quotes (I doubt but good to try)
If even that doesn't work, check your spellings (this is why I do not like magic strings and love T4MVC) - but I doubt that either, naming looks correct.
Another likely possibility is something that solved for others here: RedirectToAction not working
Has anyone tried the first solution with complex objects?
I mean this one:
"...and your target action:..."
public ActionResult Insert(TempPoliciesUpload TempPolicy)
{
...
}
I don't think a RouteValueDictionary will convert or cast into a complex object. (Serialization must be used, I think)
My solution was passing the parameters as a RouteValueDictionary and receiving each parameters individually in the target action.
If you need to send a complex object you can try just returning the view from another controller like this:
public ActionResult sendPolicy(TempPoliciesUpload TempPolicy)
{
return View("~Views/Policies/Insert.cshtml", TempPolicy );
}
If you want this view to post back to the correct controller method you will need to specify this in the 'BeginForm' Html Helper:
...
#using(Html.BeginForm("Insert", "Policy")
{
...
}
This really isn't best practice, but it is a workaround that you can use until you fix enough of the rest of your app so that you can use the redirects properly.
For decoupling, #Darin Dimitrov answer would be suited best. However if you do not wish to pass details in the URL, so that for example the user cannot fiddle with the data, you can use the short-lived persistence TempData feature like this:
public ActionResult sendPolicy(TempPoliciesUpload TempPolicy)
{
TempData["Policy"] = TempPolicy;
return RedirectToAction("Insert", "Policies");
}
Then retrieve it in the Insert:
public ActionResult Insert()
{
TempPoliciesUpload TempPolicy = (TempPoliciesUpload)TempData["Policy"];
}

Routing to the actions with same names but different parameters

I have this set of routes:
routes.MapRoute(
"IssueType",
"issue/{type}",
new { controller = "Issue", action = "Index" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
Here is the controller class:
public class IssueController : Controller
{
public ActionResult Index()
{
// todo: redirect to concrete type
return View();
}
public ActionResult Index(string type)
{
return View();
}
}
why, when i request http://host/issue i get The current request for action 'Index' on controller type 'IssueController' is ambiguous between the following action methods:
I expect that first one method should act when there is no parameters, and second one when some parameter specified.
where did i made mistake?
UPD: possible duplicate: Can you overload controller methods in ASP.NET MVC?
UPD 2: due to the link above - there is no any legal way to make action overloading, is it?
UPD 3: Action methods cannot be overloaded based on parameters (c) http://msdn.microsoft.com/en-us/library/system.web.mvc.controller%28VS.100%29.aspx
I would have one Index method that looks for a valid type variable
public class IssueController : Controller
{
public ActionResult Index(string type)
{
if(string.isNullOrEmpty(type)){
return View("viewWithOutType");}
else{
return View("viewWithType");}
}
}
EDIT:
How about creating a custom attribute that looks for a specific request value as in this post StackOverflow
[RequireRequestValue("someInt")]
public ActionResult MyMethod(int someInt) { /* ... */ }
[RequireRequestValue("someString")]
public ActionResult MyMethod(string someString) { /* ... */ }
public class RequireRequestValueAttribute : ActionMethodSelectorAttribute {
public RequireRequestValueAttribute(string valueName) {
ValueName = valueName;
}
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) {
return (controllerContext.HttpContext.Request[ValueName] != null);
}
public string ValueName { get; private set; }
}
I ran into a similar situation where I wanted my "Index" action to handle the rendering if I had an ID specified or not. The solution I came upon was to make the ID parameter to the Index method optional.
For example, I originally tried having both:
public ViewResult Index()
{
//...
}
// AND
public ViewResult Index(int entryId)
{
//...
}
and I just combined them and changed it to:
public ViewResult Index(int entryId = 0)
{
//...
}
You can do it using an ActionFilterAttribute that checks the parameters using reflection (I tried it) but it's a bad idea. Each distinct action should have its own name.
Why not just call your two methods "Index" and "Single", say, and live with the limitation on naming?
Unlike methods that are bound at compile time based on matching signatures, a missing route value at the end is treated like a null.
If you want the [hack] ActionFilterAttribute that matches parameters let me know and I'll post a link to it, but like I said, it's a bad idea.
All you have to do is mark your second Action with [HttpPost]. For instance:
public class IssueController : Controller
{
public ActionResult Index()
{
// todo: redirect to concrete type
return View();
}
[HttpPost]
public ActionResult Index(string type)
{
return View();
}
}

Resources