I m new to WebAPI and just exploring its default sample "value" controller which is there out of box with project.
I see it was already having two Get methods:
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
public string Get(int id)
{
return "value";
}
I tried and changed int id with a complex type and received "Multiple actions were found that match the request"
Why is that it was working fine beofre ?
My route is defuatl:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
I m passing a complex object in body using Get methoed, I know it is not Restful way but please help me understand it.
Much appreciated.
You can use ActionName annotation for this issue. For example use:
[ActionName("IEnumerableGet")]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
So you can call IEnumerableGet to have this method get called.
Related
I have the simplest setup:
An empty asp.net MVC application with one controller:
public class HomeController : Controller
{
public ActionResult Edit(int id)
{
return View();
}
public ActionResult Commit(int id)
{
return View();
}
}
My Edit.cshtml has a call to ActionLink() like so:
#Html.ActionLink("Commit Data", "Commit")
If I now access the Edit-Action through "/Home/Edit/2" I would expect that the rendered link directs the user to "/Home/Commit/2".
It does not :( ... The link is created to "Home/Commit", completely disregarding the current RouteData entries.
I am using the default routing configuration (have not added any routes).
One way to fix this would be to add an explicit route for both actions:
routes.MapRoute(
name: null,
url: "Home/Edit/{id}",
defaults: new { controller = "Home", action = "Edit" }
);
routes.MapRoute(
name: null,
url: "Home/Commit/{id}",
defaults: new { controller = "Home", action = "Commit" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
This works - but I really dont want to explicitly define every single route in the app - if I am using the "default" pattern...
The second solution would be to just add the routing-values manually like so:
#Html.ActionLink("Commit Data", "Commit", "Home", new {id = Model.Id})
But this also seems not right - ActionLink SHOULD use the current routing information, should it not?
What am I missing?
Ok, in case someone else is wondering the same thing - or just wants to have a solution that works...
I simply created my own #ActionLink() helper method on my custom ViewPage base class:
protected MvcHtmlString ActionLink(string linkText, string actionName)
{
var routeData = ViewContext.RequestContext.RouteData;
var id = routeData.Values["id"];
if (id != null)
return Html.ActionLink(linkText, actionName, new {id = id});
return Html.ActionLink(linkText, actionName);
}
This is exactly what I wanted. Now I can call
#ActionLink("Commit", "Commit")
and when I'm in the context of something with an id, the link will point to the appropriate route for this Id.
I am trying to implement three methods controller
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
public IEnumerable<string> Get(int id)
{
return new string[] { "sa1", "sa2" };
}
[HttpGet]
public IEnumerable<string> password()
{
return new string[] { "password", "password" };
}
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
But When i try to call http://localhost:49365/api/mycontrollername/password
it's always showing The request is invalid.
if you want to call an api function like "http://localhost:49365/api/mycontrollername/password"
you have to add ActionName attribute on the controller and add route on the Routeconfig.cs;
here is the example
[ActionName("GetEmployees")]
public IEnumerable<Employee> GETEmployees()
{
return _db.Employees.AsNoTracking();
}
routes.MapRoute(
name: "Default",
url: "api/{controller}/{action}/{id}",
defaults: new { controller = "Employees", action = "GetEmployees", id = UrlParameter.Optional }
);
I suspect it is attempting to call Get(int id) and then trying to pass the word "password" as the integer parameter. From what I can recall it's down to convention-over-configuration in that when you make a GET request it looks for a method named Get. In this case it finds one. Then based on the routing, i.e. after the controller name comes an ID, it looks at the next part of the URL, in this case "password", and then uses that as a value for the id argument.
If you were to remove the two Get methods you probably find that your URL works, but if you add other HttpGet methods you will run into other issues related to "multiple actions found". The following discussion may help if you decide you need to have multiple HttpGet methods:
Web Api Routing for multiple Get methods in ASP.NET MVC 4
Given the following routing:
routes.MapRoute(
"RouteName", // Route name
"ViewFoo/{FooId}",
new { controller = "Foo", action = "View"}
);
corresponding to the following action method:
public ActionResult View(string fooId = null)
{
...blah...
}
Precisely how would I call Url.Action() to get the string '/ViewFoo/4'? Or do I need to modify the routing somehow?
Url.Action("View", "Foo", new { FooId = 4 })
I get a 404 error when I navigate to the following URL using the route below:
http://localhost:53999/properties/
However, all the following are correctly routed to the List action in my controller:
http://localhost:53999/properties/usa/new-york/manhattan/12
http://localhost:53999/properties/usa/new-york/manhattan
http://localhost:53999/properties/usa/new-york
http://localhost:53999/properties/usa
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//properties
routes.MapRoute(
"Properties",
"Properties/{country}/{state}/{city}/{id}",
new
{
controller = "Properties",
action = "List",
country = UrlParameter.Optional,
state = UrlParameter.Optional,
city = UrlParameter.Optional,
id = UrlParameter.Optional
}
);
//default
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
}
In PropertiesController.cs:
public ActionResult List(string country, string state, string city, string id)
{
return View();
}
Anyone know what I'm missing? Looks like it should just go to the default action, but it obviously doesn't...
You can also try the following (since you're using MVC 1.0).
Add a route above the current route:
routes.MapRoute(
"Properties", "Properties", new { controller = "Properties", action = "List"}
);
And add an overloaded ActionResult List() method to your controller:
public ActionResult List()
{
return View();
}
Have you tried this Route Debugger from Phil Haack? It may help you determine what is going on.
Instead of passing
(string)null
try passing
UrlParameter.Optional
as specified in Phil Haacks post here. I don't know if this will solve the issue as I'm not currently in a position to test it.
Brad Wilson answered it on this post: http://forums.asp.net/p/1527697/3690295.aspx#3690295
"No, the problem is that you have a Properties folder on your disk, so it's dispatching through the standard dispatcher (not MVC), and then 404ing because it can't find a default document."
I've got ASP.NET MVC routing question.
I prepared following routing table to map such url
mywebsite/mycontroller/myaction/14-longandprettyseoname
to parameters:
14 => id (integer)
longandprettyseoname -> seo_name (string)
routes.MapRoute(
"myname",
"mycontroller/myaction/{id}-{seo_name}",
new { controller = "mycontroller", action = "myaction", id = 0, seo_name = (string)null });
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" });
It works for URL above but it has problems for following type of urls
mywebsite/mycontroller/myaction/14-long-and-pretty-seo-name
Is that possible to make it working?
EDIT:
"mycontroller/myaction/{seo_name}-{id}"
seems to be working
The most obvious way to do this is to use constraints.
Since that your id is an integer, you can add a constraint which will look for an integer value:
new { id = #"\d+" }
and here is the whole route:
routes.MapRoute("myname","mycontroller/myaction/{id}-{seo_name}",
new { controller = "mycontroller", action = "myaction" },
new { id = #"\d+"});
My solution is define route as:
routes.MapRoute("myname","mycontroller/myaction/{id}",
new { controller = "mycontroller", action = "myaction"});
and parse id and seoname manualy using Regex in HTTP handler:
var routeData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(context));
var match = System.Text.RegularExpressions.Regex.Match((string)routeData.Values["id"], #"^(?<id>\d+)-(?<seoname>[\S\s]*)$");
if (!match.Success)
{
context.Response.StatusCode = 400;
context.Response.StatusDescription = "Bad Request";
return;
}
int id = Int32.Parse(match.Groups["id"].Value);
string seoname = match.Groups["seoname"].Value;
I don't think the route will be distinguishable as it will not be able to figure which "-" to split at to specify the {id} and the {seo-name}.
How about using underscores for your SEO name? Or you could just use the SEO name as the actual {id}. If the SEO name is something that is going to be unique, this is a very viable option you can use as a pseudo primary key to that entry in your db (assuming it's pulling something from a DB)
Also, utilize Phil Haack's route debugger to see what works and doesn't work.
Define a specific route such as:
routes.MapRoute(
"TandC", // Route controllerName
"CommonPath/{controller}/Terms-and-Conditions", // URL with parameters
new { controller = "Home", action = "Terms_and_Conditions" } // Parameter defaults
);
But this route has to be registered BEFORE your default route.
What you could do is create a custom controller factory. That way you can have custom code to decide which controller needs to be called when.
public class CustomControllerFactory : IControllerFactory
{
#region IControllerFactory Members
public IController CreateController(RequestContext requestContext, string controllerName)
{
if (string.IsNullOrEmpty(controllerName))
throw new ArgumentNullException("controllerName");
//string language = requestContext.HttpContext.Request.Headers["Accept-Language"];
//can be used to translate controller name and get correct controller even when url is in foreign language
//format controller name
controllerName = String.Format("MyNamespace.Controllers.{0}Controller",controllerName.Replace("-","_"));
IController controller = Activator.CreateInstance(Type.GetType(controllerName)) as IController;
controller.ActionInvoker = new CustomInvoker(); //only when using custominvoker for actionname rewriting
return controller;
}
public void ReleaseController(IController controller)
{
if (controller is IDisposable)
(controller as IDisposable).Dispose();
else
controller = null;
}
#endregion
}
To use this custom controllerfactory, you should add this in your global.asax
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(typeof(CustomControllerFactory));
}
Note that this only works for the controller, not for the actions... To hook up custom rewriting on actions before they get executed, use this code:
public class CustomInvoker : ControllerActionInvoker
{
#region IActionInvoker Members
public override bool InvokeAction(ControllerContext controllerContext, string actionName)
{
return base.InvokeAction(controllerContext, actionName.Replace("-", "_"));
}
#endregion
}
I got most of this code from this blog and adjusted it to my needs. In my case, I want dashes to separate words in my controller name but you can't create an action with a dash in the name.
Hope this helps!