Multiple action routing error in webapi MVC4 - asp.net-mvc-4

I have write the below configutation code in the webapiconfig.cs file for routing the multiple fintion. In the get methods I am getting the Multiple actions were found that match the request: System.String GetJobDetails(System.String) error.
Webapiconfig.cs code
config.Routes.MapHttpRoute(
name: "RendererAPi",
routeTemplate: "shared/{controller}/{id}",
defaults: new {id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "RendererAPiStatus",
routeTemplate: "shared/{controller}/{id}/status",
defaults: new { action = "getJobStatus", id = RouteParameter.Optional }
);
My controller code :
//post shared/rendererjob
[HttpPost]
public string createRendererJob(HttpRequestMessage request)
{
return "teststring";
}
//put shared/rendererjob/renderGUID
[HttpPut]
public string DoPutRequest([FromUri(Name="id")]string renderGUID)
{
return renderGUID;
}
//get shared/rendererjob/renderGUID
[HttpGet]
public string GetJobDetails([FromUri(Name = "id")]string renderGUID)
{
return renderGUID;
}
//get shared/rendererjob/renderGUID/status
[HttpGet]
public HttpResponseMessage getJobStatus([FromUri(Name = "id")]string jobid)
{
var response = Request.CreateResponse(HttpStatusCode.OK);
string uri = Url.Link("RendererAPiStatus", new { id = jobid });
response.Headers.Location = new Uri(uri);
return response;
}
the 3 URL are working fine, the //get shared/rendererjob/renderGUID[HttpGet] is not working and getting the multiple action error in the browser.
Any one please suggest me on this.
Note : the Route method is not working in the MVC4 VS2012, and unable to instal any patches into my system for this.

The problem is that you have two GET methods with the same signature, so you need to differentiate them somehow. Try adding a Default Action Name to your methods that use the RendererAPi route:
[HttpPost]
[ActionName("Default")]
public string createRendererJob(HttpRequestMessage request)
{
return "teststring";
}
//put shared/rendererjob/renderGUID
[HttpPut]
[ActionName("Default")]
public string DoPutRequest(string id)
{
return renderGUID;
}
//get shared/rendererjob/renderGUID
[HttpGet]
[ActionName("Default")]
public string GetJobDetails(string id)
{
return renderGUID;
}
//get shared/rendererjob/renderGUID/status
[HttpGet]
public HttpResponseMessage getJobStatus(string id)
{
var response = Request.CreateResponse(HttpStatusCode.OK);
string uri = Url.Link("RendererAPiStatus", new { id = id });
response.Headers.Location = new Uri(uri);
return response;
}
Then change the RendererAPi route as follows:
config.Routes.MapHttpRoute(
name: "RendererAPi",
routeTemplate: "shared/{controller}/{id}",
defaults: new {action = "Default", id = RouteParameter.Optional }
);
By the way you don't need to include the [FromUri] attribute for strings.

Related

Asp.Net core get RouteData value from url

I'm wokring on a new Asp.Net core mvc app. I defined a route with a custom constraint, which sets current app culture from the url. I'm trying to manage localization for my app by creating a custom IRequestCultureProvider which looks like this :
public class MyCustomRequestCultureProvider : IRequestCultureProvider
{
public Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
{
var language= httpContext.GetRouteValue("language");
var result = new ProviderCultureResult(language, language);
return Task.FromResult(result);
}
}
My MyCustomRequestCultureProvider is hit on every request, which is ok. My problem is that in the MVC pipeline, DetermineProviderCultureResult method from my provider is hit before the routing process, so httpContext.GetRouteValue("language") always return null.
In previous version of MVC, I had the possiblity to manually process my url through the routing process by doing this
var wrapper = new HttpContextWrapper(HttpContext.Current);
var routeData = RouteTable.Routes.GetRouteData(wrapper);
var language = routeData.GetValue("language")
I can't find a way to do the same thing in the new framewrok right now. Also, I want to use the route data to find out my langugae, analysing my url string with some string functions to find the language is not an option.
There isn't an easy way to do this, and the ASP.Net team hasn't decided to implement this functionality yet. IRoutingFeature is only available after MVC has completed the request.
I was able to put together a solution that should work for you though. This will setup the routes you're passing into UseMvc() as well as all attribute routing in order to populate IRoutingFeature. After that is complete, you can access that class via httpContext.GetRouteValue("language");.
Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// setup routes
app.UseGetRoutesMiddleware(GetRoutes);
// add localization
var requestLocalizationOptions = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US")
};
requestLocalizationOptions.RequestCultureProviders.Clear();
requestLocalizationOptions.RequestCultureProviders.Add(
new MyCustomRequestCultureProvider()
);
app.UseRequestLocalization(requestLocalizationOptions);
// add mvc
app.UseMvc(GetRoutes);
}
Moved the routes to a delegate (for re-usability), same file/class:
private readonly Action<IRouteBuilder> GetRoutes =
routes =>
{
routes.MapRoute(
name: "custom",
template: "{language=fr-FR}/{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
};
Add new middleware:
public static class GetRoutesMiddlewareExtensions
{
public static IApplicationBuilder UseGetRoutesMiddleware(this IApplicationBuilder app, Action<IRouteBuilder> configureRoutes)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
var routes = new RouteBuilder(app)
{
DefaultHandler = app.ApplicationServices.GetRequiredService<MvcRouteHandler>(),
};
configureRoutes(routes);
routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices));
var router = routes.Build();
return app.UseMiddleware<GetRoutesMiddleware>(router);
}
}
public class GetRoutesMiddleware
{
private readonly RequestDelegate next;
private readonly IRouter _router;
public GetRoutesMiddleware(RequestDelegate next, IRouter router)
{
this.next = next;
_router = router;
}
public async Task Invoke(HttpContext httpContext)
{
var context = new RouteContext(httpContext);
context.RouteData.Routers.Add(_router);
await _router.RouteAsync(context);
if (context.Handler != null)
{
httpContext.Features[typeof (IRoutingFeature)] = new RoutingFeature()
{
RouteData = context.RouteData,
};
}
// proceed to next...
await next(httpContext);
}
}
You may have to define this class as well...
public class RoutingFeature : IRoutingFeature
{
public RouteData RouteData { get; set; }
}
Based on Ashley Lee's answer, here is an optimized approach that prevents duplicate route configuration.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// setup routes
var mvcRouter = BuildMvcRouter(app, routes =>
{
routes.MapRoute(
name: "custom",
template: "{language=fr-FR}/{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
// add route data initialization middleware
app.Use(next => SetRouteData(next, mvcRouter));
// add localization middleware
var requestLocalizationOptions = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US")
};
requestLocalizationOptions.RequestCultureProviders.Clear();
requestLocalizationOptions.RequestCultureProviders.Add(
new MyCustomRequestCultureProvider()
);
app.UseRequestLocalization(requestLocalizationOptions);
// add mvc routing middleware
app.UseRouter(mvcRouter);
}
This depends on the following two methods that must be added to the StartUp class:
private static IRouter BuildMvcRouter(IApplicationBuilder app, Action<IRouteBuilder> configureRoutes)
{
if (app == null) throw new ArgumentNullException(nameof(app));
if (configureRoutes == null) throw new ArgumentNullException(nameof(configureRoutes));
app.ApplicationServices.GetRequiredService<MiddlewareFilterBuilder>().ApplicationBuilder = app.New();
var routeBuilder = new RouteBuilder(app)
{
DefaultHandler = app.ApplicationServices.GetRequiredService<MvcRouteHandler>()
};
configureRoutes(routeBuilder);
routeBuilder.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices));
return routeBuilder.Build();
}
private static RequestDelegate SetRouteData(RequestDelegate next, IRouter router)
{
return async context =>
{
var routeContext = new RouteContext(context);
await router.RouteAsync(routeContext);
if (routeContext.Handler != null)
{
context.Features[typeof(IRoutingFeature)] = new RoutingFeature
{
RouteData = routeContext.RouteData
};
}
await next(context);
};
}
This has been made easier with the addition of the Endpoint Routing feature.
This article explains how to do it using the Endpoint Routing feature https://aregcode.com/blog/2019/dotnetcore-understanding-aspnet-endpoint-routing/
var endpointFeature = context.Features[typeof(Microsoft.AspNetCore.Http.Features.IEndpointFeature)]
as Microsoft.AspNetCore.Http.Features.IEndpointFeature;
Microsoft.AspNetCore.Http.Endpoint endpoint = endpointFeature?.Endpoint;
//Note: endpoint will be null, if there was no
//route match found for the request by the endpoint route resolver middleware
if (endpoint != null)
{
var routePattern = (endpoint as Microsoft.AspNetCore.Routing.RouteEndpoint)?.RoutePattern
?.RawText;
Console.WriteLine("Name: " + endpoint.DisplayName);
Console.WriteLine($"Route Pattern: {routePattern}");
Console.WriteLine("Metadata Types: " + string.Join(", ", endpoint.Metadata));
}

Routing Issue with my asp.net MVC 4 application

I'm having hectic last couple of days due to this problem. I'm trying to pass route data to the view as a matter of navigation. However routeInfo contains no route information. i.e. routeInfo.RouteData.Values.Count = 0. I have another application with the same code which is working fine.
I'm not sure what i'm missing here.
Any help would be really appreciated!!
public ActionResult Index(int type)
{
UrlHelper u = new UrlHelper(this.ControllerContext.RequestContext);
string url = u.Action("Action", "Controller", new { type = type }, Request.Url.Scheme);
Uri uri = new Uri(url);
RouteInfo routeInfo = new RouteInfo(uri, HttpContext.Request.ApplicationPath);
Session["_ReturnURL"] = routeInfo.RouteData.Values;
ViewBag.ReturnURL = Helpers.GetSessionKey("_ReturnURL");
return View();
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.RouteExistingFiles = true;
routes.IgnoreRoute("elmah.axd");
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Controller",
url: "Controller/{type}/{action}/{id}",
defaults: new { controller = "Controller", action = "Action", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "CBlah", action = "ABlah", id = UrlParameter.Optional, returnUrl = "~/Blah2/Blah2" }
);
}
public class RouteInfo
{
public RouteData RouteData { get; private set; }
public RouteInfo(RouteData data)
{
RouteData = data;
}
public RouteInfo(Uri uri, string applicationPath)
{
RouteData = RouteTable.Routes.GetRouteData(new InternalHttpContext(uri, applicationPath));
}
private class InternalHttpContext : HttpContextBase
{
private readonly HttpRequestBase _request;
public InternalHttpContext(Uri uri, string applicationPath)
{
_request = new InternalRequestContext(uri, applicationPath);
}
public override HttpRequestBase Request { get { return _request; } }
}
private class InternalRequestContext : HttpRequestBase
{
private readonly string _appRelativePath;
private readonly string _pathInfo;
public InternalRequestContext(Uri uri, string applicationPath)
{
_pathInfo = uri.Query;
if (String.IsNullOrEmpty(applicationPath) || !uri.AbsolutePath.StartsWith(applicationPath, StringComparison.OrdinalIgnoreCase))
_appRelativePath = uri.AbsolutePath.Substring(applicationPath.Length);
else
_appRelativePath = uri.AbsolutePath;
}
public override string AppRelativeCurrentExecutionFilePath { get { return String.Concat("~", _appRelativePath); } }
public override string PathInfo { get { return _pathInfo; } }
}
}
Route.GetRouteData Definition
public override RouteData GetRouteData(HttpContextBase httpContext)
{
string virtualPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo;
RouteValueDictionary values = this._parsedRoute.Match(virtualPath, this.Defaults);
if (values == null)
{
return null;
}
RouteData data = new RouteData(this, this.RouteHandler);
if (!this.ProcessConstraints(httpContext, values, RouteDirection.IncomingRequest))
{
return null;
}
foreach (KeyValuePair<string, object> pair in values)
{
data.Values.Add(pair.Key, pair.Value);
}
if (this.DataTokens != null)
{
foreach (KeyValuePair<string, object> pair2 in this.DataTokens)
{
data.DataTokens[pair2.Key] = pair2.Value;
}
}
return data;
}
The problem lies in this line:
string virtualPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo;
You are appending a query string as the httpContext.Request.PathInfo via:
_pathInfo = uri.Query;
I know for a fact that the Route class does not consider the query string, so appending it to the path is a mistake. There is also some information here that backs up the fact that PathInfo always should return an empty string.
Per MSDN:
For the URL http://www.contoso.com/virdir/page.html/tail, the PathInfo value is /tail.
So, adding a query string is making this line fail (when there is a query string) because _parsedRoute.Match isn't expecting one.
RouteValueDictionary values = this._parsedRoute.Match(virtualPath, this.Defaults);
My guess is that one of your applications "works" because it is not being passed a URL with a query string, and therefore matches correctly. But whatever the case, you should return an empty string from PathInfo in your InternalRequestContext class to make it work 100% of the time (unless you have crazy URLs with dots in them, then you may need to do some extra work).

ASP.Net MVC 4 WebAPI POST returns 404

I've looked at the many similar issues posted but couldn't find a solution that worked for me. So the call to Get is working fine but call to POST returns 404. I created a simple WebAPI project (MVC 4).
public class CasesController : ApiController
{
[Inject]
public ICaseManager CaseManager { get; set; }
// GET api/cases
public IEnumerable<Case> Get()
{
return CaseManager.ListCases();
}
// POST api/cases
[HttpPost]
public void Post([FromBody]Case objCase)
{
}
}
So when I navigate to http://localhost:34645/api/cases I get the following:
[{"CaseID":1,"CaseCode":"one","CaseDescription":"case one"},{"CaseID":2,"CaseCode":"two","CaseDescription":"case two"}]
I created another project (ASP.Net) and have an html file within it with the following code:
<script src="Scripts/jquery-2.0.3.js"></script>
<script src="Scripts/jquery-2.0.3.intellisense.js"></script>
<script type="text/javascript">
function postData() {
$.post('http://localhost:34645/api/cases', { "CaseID": 3, "CaseCode": "three", "CaseDescription": "case three" }).done(function (data) { alert("Success " + data); }).fail(function (xhr, textStatus, errorThrown) { alert("Error " + xhr.status); });
}
</script>
Every time I click the button that invokes postData, I get an alert "Error 404".
Here are my routes:
Global.asax:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
WebAPIConfig.Register:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
//RA: to get JSON
var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
}
}
RouteConfig:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
Please advise.
Be careful about the order of the WebApi registration line. I found when I specifically had the Global.asax.cs code in this order it worked:
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
Otherwise, it failed with 404 error.
If these are two separate solutions, check they're both running - it's possible that they're trying to share a server instance, so the WebAPI you're trying to hit isn't running when the other app is. If they're projects within the same solution, check that they're both set to run on startup, or again, the WebAPI won't be running when the ASP.NET project tries to access it.
Try below. It works for me. I have removed some properties for brevity.
public class CasesController : ApiController {
// GET api/cases
public IEnumerable<Case> Get() {
var caseManager = new CaseManager();
return caseManager.ListCases();
}
// POST api/cases
[HttpPost]
public string Post([FromBody]Case objCase) {
return objCase.CaseName;
}
}
public interface ICaseManager {
IEnumerable<Case> ListCases();
}
public class CaseManager {
public IEnumerable<Case> ListCases()
{
return new List<Case>() { new Case() { CaseID = 1, CaseName = "one" } };
}
}
public class Case {
public int CaseID { get; set; }
public string CaseName { get; set; }
}
View
<script type="text/javascript">
//function postData() {
// $.post('http://localhost:58820/api/cases', { "CaseID": 3, "CaseCode": "three", "CaseDescription": "case three" })
// .done(function (data) { alert("Success " + data); }).fail(function (xhr, textStatus, errorThrown)
// { alert("Error " + xhr.status); });
//}
$(document).ready(function () {
$('#save-source').click(function (e) {
e.preventDefault();
var source = {
'ID': 0,
'CaseID': 3,
'CaseName': "three",
};
$.ajax({
type: "POST",
dataType: "json",
url: "/api/cases",
data: source,
success: function (data) {
alert(data);
},
error: function (error) {
jsonValue = jQuery.parseJSON(error.responseText);
}
});
});
});
</script>
#using (Html.BeginForm(null, null, FormMethod.Post, new { id = "myForm"}))
{
<input type="submit" id="save-source" name="save-source" value="Add" />
}
After different attempts, this article helped me the most:
WebAPI and CORS enabled REST services
I also installed the Ninject WebApi DependencyResolver package through NuGet.
You write that you post to $.post('http://localhost:34645/api/cases'...
Either you change the url to include the action method name explicitly, like: $.post('http://localhost:34645/api/cases/post'..
or you add in your config.Routes.MapHttpRoute a default action which will be used when none action specified in the url
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { action="Post", id = RouteParameter.Optional }
);
OR you can change your route to
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
);
(without {action} and then web api will reach the Post method when you use a post http verb (it knows to do it automatically, but if you set a default action it'll override it)

WebApiContrib Jsonp and Attribute Routing

Per the WebApiContrib.Formatting.Jsonp GitHub readme, it appears that in the RouteConfig.cs this should be entered:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}/{format}",
defaults: new { id = RouteParameter.Optional, format = RouteParameter.Optional }
);
I currently don't have a RouteConfig.cs file in my AppStart. I created it using the Web API 2 template and I don't think I changed anything structurally. I do have a WebApiConfig.cs where I have set:
public static void Register (HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
}
how do I include it such that all routes have the ability to return Jsonp?
You could create a custom route attribute which implements IHttpRouteInfoProvider (which Web API route builder looks for when adding routes to route table) and then modify the template that is being generated by appending {format}
Example:
[RoutePrefix("api/values")]
public class ValuesController : ApiController
{
[CustomRoute(Order = 1)]
public IEnumerable<string> GetAll()
{
return new string[] { "value1", "value2" };
}
[CustomRoute("{id}")]
public string GetSingle(int id)
{
return "value";
}
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public sealed class CustomRouteAttribute : Attribute, IHttpRouteInfoProvider
{
public CustomRouteAttribute()
{
Template = String.Empty;
}
public CustomRouteAttribute(string template)
{
if (template == null)
{
throw new ArgumentNullException("template");
}
if (template == string.Empty)
{
Template = template + "{format?}";
}
else
{
Template = template.TrimEnd('/') + "/{format?}";
}
}
public string Name { get; set; }
public int Order { get; set; }
public string Template { get; private set; }
}
I found this comment in a pull request but I don't understand if this is yet implemented into the production package nor if it got pulled at all.
If you are using Attribute Routing, you should add "/{format}" after each route if you plan to use the URI mapping for jsonp, e.g. [Route("api/value/{id:int}/{format?}")]. If you will require the Content-Type header to specify text/javascript, then you can leave your routes alone. (See the sample applications for examples.)

ASP.NET Web API - Multiple POST methods on one controller?

I've been trying to add a second POST method to the default ValuesController class that will take an id parameter and act identical to the PUT method, like so:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http;
namespace WebCalendar.Controllers {
public class ValuesController : ApiController {
// GET /values
public IEnumerable<string> Get() {
return new string[] { "value1", "value2" };
}
// GET /values/5
public string Get(int id) {
return "value";
}
// POST /values
public void Post(string value) {
}
// POST /values/5
public void Post(int id, string value) {
Put(id, value);
}
// PUT /values/5
public void Put(int id, string value){
}
// DELETE /values/5
public void Delete(int id) {
}
}
}
Problem is, when I add this second post method, any time I make a POST request, I get the error:
"No action was found on the controller 'values' that matches the request."
If I comment out one of the methods (doesn't matter which one), POST will work with the other method. I've tried renaming the methods, and even using [HttpPost] on both of them, but nothing has worked.
How can I have more than one POST method in a single ApiController?
EDIT
Here is the only route that I'm using:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{controller}/{id}",
defaults: new { controller = "values", id = RouteParameter.Optional }
);
You have to include the action in your route:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);