multiple routes in one controller causes 400:bad request - javascript

Let me give an example of my problem,
I have registered my routes as following(RouteConfig.cs):
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
if you look at my controller, it has the following function;
[Route("all")]
public HttpResponseMessage Get(HttpRequestMessage request)
{
return CreateHttpResponse(request, () =>
{
HttpResponseMessage response = null;
var HolidayCalendars = _holidayCalendarsRepository.GetAll().ToList();
IEnumerable<HolidayCalendarViewModel> holiVm = Mapper.Map<IEnumerable<HolidayCalendar>, IEnumerable<HolidayCalendarViewModel>>(HolidayCalendars);
response = request.CreateResponse<IEnumerable<HolidayCalendarViewModel>>(HttpStatusCode.OK, holiVm);
return response;
});
}
up to this point everything is going great. My page loads with the requested data. Now, when I go and add another function, for example;
[Route("allHolidays/{id:int}")]
public HttpResponseMessage GetHolidays(HttpRequestMessage request, int id)
{
return CreateHttpResponse(request, () =>
{
HttpResponseMessage response = null;
HolidayCalendar Calendar = _holidayCalendarsRepository.GetSingle(id);
var Holidays = Calendar.Holidays.OrderBy(s => s.HolidayDate).ToList();
IEnumerable<HolidayViewModel> holidayVm = Mapper.Map<IEnumerable<Holiday>, IEnumerable<HolidayViewModel>>(Holidays);
response = request.CreateResponse<IEnumerable<HolidayViewModel>>(HttpStatusCode.OK, holidayVm);
return response;
});
}
I will get the following error in my webpage;
Failed to load resource: the server responded with a status of 400 (Bad Request)
Strange thing is, my request did not change, there is only a new Controller in my api.
This should not be happening because my code is requesting different routes, for example;
function loadData() {
apiService.get('/api/HolidayCalendars/all', null,
HolidayCalendarLoadCompleted,
HolidayCalendarLoadFailed);
}
or
function loadData() {
apiService.get('/api/HolidayCalendars/allHolidays?id=' + $routeParams.id, null,
HolidaysLoadCompleted,
HolidaysLoadFailed);
}
Any ideas?
constructor class WebApiConfig:
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new {id = RouteParameter.Optional }
);
}

Your route for allHolidays implies this format
/api/HolidayCalendars/allHolidays/123
According to your route attribute
[Route("allHolidays/{id:int}")]
But you've passed the id as a querystring parameter
api/HolidayCalendars/allHolidays?id=123

Looks like you're using AttributeRouting (http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2) on the Controllers :
[Route("all")]
but you're using standard routing in the config:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Try activate the AttributeRouting with:
configuration.MapHttpAttributeRoutes();
where configuration is the actual instance of HttpConfiguration.

The issue is in your WebApiConfig. In the routeTemplate, you haven't specified an action.
routeTemplate: "api/{controller}/{id}",
If I remember correctly, this is the default config for WebAPI. It filters requests on a controller by verb. That's why when you call
apiService.get('/api/HolidayCalendars/all'.....)
It returns the Get() method on your HolidayCalendars controller.
To fix the issue, add the {action} parameter to your routeTemplate:
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new {id = RouteParameter.Optional }
);
}

I finally found the solution!
At the top of my code i had a reference to System.Web.Mvc. This way the routing and RESTful functions were not interpreted as it should in a web Api. This caused some strange functioning in my app.
Solution:
Change
using System.Web.Mvc;
To
using System.Web.Http;
This evaded me for three days,until I came along the following answer:
https://stackoverflow.com/a/21999235/6033193

Related

MVC controller is not being called, but template controller works?

I can't make heads or tails of what I'm doing wrong here. I'm calling a new controller I'm setting up using JavaScript fetch(). I'm using MVC 5 within the React template.
The template controller (WeatherForecastController) works, if I call it using fetch, it'll hit the controller. Yet using the exact same implementation but for a new controller it won't touch the new controller. However Network debugger shows that it called and returned OK. If I call the controller directly through the browser, it's as if there isn't a route for it at all! No json is returned from my new controller vs the template controller. I'm simply trying to define a new controller I can fetch data from my Middle tier (.NET 6). Using .Net 6.0 as well.
Naming convention is correct as it ends with Controller in call.
DEFAULT TEMPLATE CONTROLLER CALL FROM JS
async populateWeatherData() {
const response = await fetch('weatherforecast');
const data = await response.json();
this.setState({ forecasts: data, loading: false });
}
DEFAULT TEMPLATE CONTROLLER
using Microsoft.AspNetCore.Mvc;
namespace ReactTest.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
}
Yet I call my controller I setup with the exact same implementation. If I try to call it I get exactly nothing!
NEW CONTROLLER TEMPLATE CALL FROM JS
async populateWeatherData() {
const response = await fetch('database');
const data = await response.json();
this.setState({ forecasts: data, loading: false });
}
IMPLEMENTATION OF NEW CONTROLLER (That I actually want to call)
using Microsoft.AspNetCore.Mvc;
namespace ReactTest.Controllers
{
[ApiController]
[Route("[controller]")]
public class DatabaseController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<DatabaseController> _logger;
public DatabaseController(ILogger<DatabaseController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
}
I've tried everything but modifying the default routing (the controller lives in the same folder as the template controller, so this shouldn't be why).
Before anyone asks:
ROUTING CONFIG:
app.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
ProjectFileDirectory
DatabaseControllerResponseHeader
So what exactly am I doing wrong? Yes I have read the documentation, no it hasn't helped.
Thanks in advance!

Make Ajax call to C# Controller Method

This is the code in my javascript file called "data handling.js" located in a folder called "JS":
document.getElementById('submit-new-project').addEventListener("click", function () {
var ProjectName = document.getElementById('new-project').value;
if (ProjectName) {
$.ajax({
type: "POST",
url: "Controllers/HomeController/AddProject",
data: {"ProjectName": ProjectName},
success: function () {
hideElement("add-button-popup");
location.reload();
},
error: function (errorThrown) {
console.log(errorThrown)
}
});
}
else {
alert("Text Field Must Not Be Empty");
}
})
And here is my controller method:
public partial class HomeController : Controller
{
[HttpPost]
public static void AddProject(string ProjectName)
{
using (AutomationMethodDirectoryEntities db = new AutomationMethodDirectoryEntities())
{
try
{
PROJECT project = new PROJECT();
project.ProjectName = ProjectName;
db.PROJECTS.Add(project);
db.SaveChanges();;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
}
}
The controller method is located in "Adders.cs" which is a partial class to "HomeController.cs" in the "Controllers" folder. Both the "JS" and "Controllers" folders are at the root level of the project. I have tried many variations of this ajax call and my biggest problem is with the URL. No matter what I try, I am always getting a 404 error because the server can't find the requested URL.
I've tried changing it to:
url: "Controllers/HomeController/AddProject"
url: "/Controllers/HomeController/AddProject"
url: "../Controllers/HomeController/AddProject"
url: "Home/AddProject"
url: "/Home/AddProject"
url: "../Home/AddProject"
url: "#(Url.Action(AddProject, HomeController))"
And I've swapped "HomeController" with "Adders" in different variations.
What am I doing wrong?
Is there anything I need to add/change to my controller method?
EDIT
Here is my "RouteConfig.cs" that I haven't made any changes to yet:
namespace RTCAD
{
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 }
);
}
}
}
When calling your AddProject action, ASP.NET MVC creates an instance of the controller (using ControllerFactory), and since your method is static, it is not related to any instance, and thus, it will not be called by MVC.
Remove the static from the method declaration and use /Home/AddProject as the URL.

Add an extra get method in asp.net web api

I'm a very new comer to the asp.net web api world. I've got the basic understanding of get(), put(), post() and delete.
In my application, I require two more get() method. An explanation is given below-
public class StudentController : ApiController
{
public IEnumerable Get()
{
//returns all students.
}
//I would like to add this method=======================
[HttpGet]
public IEnumerable GetClassSpecificStudents(string classId)
{
//want to return all students from an specific class.
}
//I also would like to add this method=======================
[HttpGet]
public IEnumerable GetSectionSpecificStudents(string sectionId)
{
//want to return all students from an specific section.
}
public Student Get(string id)
{
//returns specific student.
}
}
There is already a $http.get(..) in angularjs controller.
My question is, how can I call the two additional get() methods from angular controller.
Well, I haven't used asp.net mvc in forever. But you be able to do something like:
public class StudentController : ApiController
{
[Route("students")]
public IEnumerable Get()
{
//returns all students.
}
//I would like to add this method=======================
[HttpGet]
[Route("students/class/{classId}")]
public IEnumerable GetClassSpecificStudents(string classId)
{
//want to return all students from an specific class.
}
//I also would like to add this method=======================
[HttpGet]
[Route("students/section/{sectionId}")]
public IEnumerable GetSectionSpecificStudents(string sectionId)
{
//want to return all students from an specific section.
}
[Route("students/{id}")]
public Student Get(string id)
{
//returns specific student.
}
}
You could also specify routes in the routeconfig like this:
routes.MapRoute(
name: "students",
url: "students/class/{classId}",
defaults: new { controller = "Student", action = "GetClassSpecificStudents", id = UrlParameter.Optional }
);
You have to try for your self. And you can read more about it here and here.
Not that you have your specified routes you can add angular $http.gets for each route.
var url = "whateverdoma.in/students/"
$http.get(url)
.success()
.error()
var url = "whateverdoma.in/students/class/" + classId;
$http.get(url)
.success()
.error()
var url = "whateverdoma.in/students/filter/" + filterId;
$http.get(url)
.success()
.error()
What you want to do is write costum angular resource method, to call your API.
Use angular $resource and not $http - > it is the more common usage (and more REST oriented: $resource wraps $http for use in RESTful web API scenarios).
Read about it
Find how to add a resource to the $resource service.
Here is an example:
.factory('Store', function ($resource, hostUrl) {
var url = hostUrl + '/api/v3/store/';
return $resource("", { storeId: '#storeId' }, {
getSpecific: {
method: 'GET',
url: hostUrl + '/api/v3/store-specific/:storeId'
}
});
})

Web API - Failed to load resource: the server responded with a status of 404

I am developing angular js with web api.
I have this cotroller: Controller/MasterController, and thiis in my WebApi Config:
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I call this function from Global.asax, Application_Start event.
I call my web api from service.js like that:
var service = function ($http) {
var _$http = $http;
self = this;
self.getMenuItems = function () {
var promise = _$http({
method: "GET",
url: 'api/Master'
}).success(function (data, status, headers, config) {
}).error(function (data, status, headers, config) {
});
return promise;
};
In debug mode, I saw that I reach this area.
I get this error in chrome console:
"Failed to load resource: the server responded with a status of 404"
and this is what he was trying to get to: http://localhost:12345/api/Master
Also, I tried to get to the web api controller directly through the browser, and I couldn't find it.
Thank You
Just to be sure your API is working try the following:
Global.asax.cs:
public class WebApiApplication : HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
MasterController:
using System.Web.Http;
namespace WebApi.Controllers
{
public class MasterController : ApiController
{
public string Get()
{
return "Hello world";
}
}
}
Calling http:// localhost:[SomePort]/api/Master in your browser should result in this:
"Hello world"
The configurations above are all standard when creating a new WebApi.
After trying some new directions, I received new error details.
The final error I received + its solution you can find
here

Bundling API requests

I am creating a REST API and I have been playing with the idea of allowing bundling of requests from clients. By bundling I mean they can send one request, containing multiple "real" requests, and they get delivered to the client together. Typically javascript ajax requests. Something like this:
POST /bundlerequest
["/person/3243", "/person/3243/friends", "/comments/3243?pagesize=10&page=1", "/products", "/product/categories" ]
(The bundled request can only be GET requests, as of now at least)
This is intended to return something like this
{
"success" : ["/person/3243", "/person/3243/friends", "/comments/3243?pagesize=10&page=1", "/products", "/product/categories" ],
"error" : [],
"completiontime" : 94,
other relevant metadata...
"responses" : [
{"key" : "/person/3243" , "data" : {"name" : "John", ...} },
{"key" : "/person/3243/friends" , "data" : [{"name": "Peter", "commonfriends" : 5, ...}] },
etc...
]
}
The benefits of this bundling is that it reduces the number of requests and that is especially important on mobile devices for instance.
So my first question is, is my approach to this a good one? Does anyone have experience with doing something like this?
AFAIK the common way of solving this is to write server side code to return combined data, that I believe is relevant for the client(s). (The twitter user stream for instance does this, combining person info, latest tweets, latest personal messages etc.) But this makes the API very opinionated and when the client needs changes the server might need to change to accomodate to optimize.
And the second question is how to implement this?
My backend is ASP.NET MVC 3 and IIS 7. Should I implement it in the application, having an bundlerequest action that internally calls the other actions specified in the request?
Could it be implemented in IIS 7 directly? Writing a module that transparently intercepts requests to /bundlerequest and then calls all the corresponding sub requests, making the application totally unaware of the bundling that happens? This would also allow me to implement this in an application-agnostic way.
You could use an asynchronous controller to aggregate those requests on the server. Let's first start by defining a view model that will be returned by the controller:
public class BundleRequest
{
public string[] Urls { get; set; }
}
public class BundleResponse
{
public IList<string> Success { get; set; }
public IList<string> Error { get; set; }
public IList<Response> Responses { get; set; }
}
public class Response
{
public string Key { get; set; }
public object Data { get; set; }
}
then the controller:
public class BundleController : AsyncController
{
public void IndexAsync(BundleRequest request)
{
AsyncManager.OutstandingOperations.Increment();
var tasks = request.Urls.Select(url =>
{
var r = WebRequest.Create(url);
return Task.Factory.FromAsync<WebResponse>(r.BeginGetResponse, r.EndGetResponse, url);
}).ToArray();
Task.Factory.ContinueWhenAll(tasks, completedTasks =>
{
var bundleResponse = new BundleResponse
{
Success = new List<string>(),
Error = new List<string>(),
Responses = new List<Response>()
};
foreach (var task in completedTasks)
{
var url = task.AsyncState as string;
if (task.Exception == null)
{
using (var response = task.Result)
using (var stream = response.GetResponseStream())
using (var reader = new StreamReader(stream))
{
bundleResponse.Success.Add(url);
bundleResponse.Responses.Add(new Response
{
Key = url,
Data = new JavaScriptSerializer().DeserializeObject(reader.ReadToEnd())
});
}
}
else
{
bundleResponse.Error.Add(url);
}
}
AsyncManager.Parameters["response"] = bundleResponse;
AsyncManager.OutstandingOperations.Decrement();
});
}
public ActionResult IndexCompleted(BundleResponse response)
{
return Json(response, JsonRequestBehavior.AllowGet);
}
}
and now we can invoke it:
var urls = [
'#Url.Action("index", "person", new { id = 3243 }, Request.Url.Scheme, Request.Url.Host)',
'#Url.Action("friends", "person", new { id = 3243 }, Request.Url.Scheme, Request.Url.Host)',
'#Url.Action("index", "comments", new { id = 3243, pagesize = 10, page = 1 }, Request.Url.Scheme, Request.Url.Host)',
'#Url.Action("index", "products", null, Request.Url.Scheme, Request.Url.Host)',
'#Url.Action("categories", "product", null, Request.Url.Scheme, Request.Url.Host)'
];
$.ajax({
url: '#Url.Action("Index", "Bundle")',
type: 'POST',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(urls),
success: function(bundleResponse) {
// TODO: do something with the response
}
});
Of course some tweaking might be necessary to adapt this to your specific needs. For example you mentioned sending AJAX requests with session expired which might redirect to the Logon page and thus not capturing the error. That's indeed a PITA in ASP.NET. Phil Haack blogged a possible way to circumvent this undesired behavior in a RESTful manner. You just need to add a custom header to the requests.
I suggest looking wcf web api

Categories