I try to make server-side processing for DataTables using Web API. There are two actions in my Web API controller with same list of parameters:
public class CampaignController : ApiController
{
// GET request handler
public dtResponse Get(int draw, int start, int length)
{
// request handling
}
// POST request handler
public void Post(int draw, int start, int length)
{
// request handling
}
}
If I use GET method to send AJAX request to the server, the Get action is activated. However, if I use POST method, then neither action are activated.
I tried to change POST handler signature to
public void Post([FromBody]object value)
{
// request handling
}
In this case the value is null. Note, the HttpContext.Current.Request.Form collection isn't empty. The draw, start, length variables are exist in this collection. Thus, I think the trouble is in model binding, but I cannot fix it. Help me, please.
Not knowing exactly what's going on, but appears there are a few missing elements. I've written a Post endpoint this morning, So hopefully will help pushing you in the right direction.
Also to note if you want "Data" use Get, if your inserting data then POST
[HttpPost]
[Route("orders")]
public async Task<IHttpActionResult> Post([FromBody]List<Models.Model.Order> orders)
{
if (orders == null)
return BadRequest("Unusable resources.");
if (validatedOrders.Count <= 0)
return BadRequest("Unusable resources.");
try
{
//Create abstracted Identity model to pass around layers
var identity = User.Identity as ClaimsIdentity;
var identityModel = IdentityModel.Create(identity);
if (identityModel == null)
return StatusCode(HttpStatusCode.Forbidden);
var response = await _orderService.AddAsync(validatedOrders, identityModel);
return Ok(response);
}
catch (System.Exception ex)
{
return InternalServerError();
}
finally
{
_orderService.Dispose();
}
}
Utilizing IHttpActionResult will expose the returning of a response
Verb attributes helps with building/designing Restful API's and same
name signatures
Attribute Routing saves writing in the config and change routes in
the class
To wrap it all up, replace the order collection with:
public sealed class Diagram
{
public int Draw { get; set; }
public int Start { get; set; }
public int Length { get; set; }
}
Rewrite the validation, remove the Identity creation, remove the insert and remove/replace the attribute routing.
With HttpContext.Current.Request.Form Try building a Diagram object from that and passing it up.
OR Alternatively passing in a form collection
[HttpPost]
[Route("something")]
// POST api/<controller>
public async Task<HttpResponseMessage> Post(FormDataCollection form)
{
string tid = form.Get("tid");
string sid = form.Get("sid");
string userid = form.Get("userid");
string udid = form.Get("udid");
}
Additional resource from the DataTable Docs
Git Hub Repo
What about this:
[HttpPost]
public void Post([FromBody]int draw, [FromBody]int start, [FromBody]int length)
{
// request handling
}
Related
I am working on .NET 6.0 Web API application. I need to pass object which is collection of string list to API with the purpose it will return data. I can do with HttpPost but since I am designing this API for the purpose of Get Record, what will be the right approach?
public class JobRoleDataView
{
public JobRoleDataView() { }
public List<string> Roles { get; set; }
}
this object will get more properties soon so it is not just List...
[HttpGet("SearchRecord")]
public async Task<IActionResult> SearchRecord(JobRoleDataView JobRoles)
{
//remaining code
return Ok(returnResponse);
}
error
TypeError: Failed to execute 'fetch' on 'Window': Request with GET/HEAD method cannot have body.
You can use [FromQuery] attribute and pass roles as querystring.
[HttpGet("SearchRecord")]
public async Task<IActionResult> SearchRecord([FromQuery]JobRoleDataView JobRoles)
{
//remaining code
return Ok(returnResponse);
}
}
The request url will be seen as below.
https://localhost:7009/WeatherForecast/SearchRecord?Roles=vishal&Roles=michel
I have the following action within an asp.net core 3.1 controller
[ApiController]
[Route("example")]
public class MyExampleController : ControllerBase
{
[HttpPost("{id}/value")]
public async Task<IActionResult> Post(string id, [FromBody] int? value)
=> Task.FromResult(Ok());
}
This works fine if I post a body value of int (for example: 1, 2, etc...)
However, I can't find a way to get a null value passed in.
If I pass in an empty body or null body I get a status code of 400 returned with a validation message of A non-empty request body is required. returned.
I've also tried to change the value parameter to be an optional argument with a default value of null:
public async Task<IActionResult> Post(string id, [FromBody] int? value = null)
How do I pass in null to this action?
Finally figured this out, big thanks to #Nkosi and #KirkLarkin helping fault find this.
Within the Startup.cs when configuring the controllers into the container we just need to alter the default mvc options to AllowEmptyInputInBodyModelBinding
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(x => x.AllowEmptyInputInBodyModelBinding = true);
}
This way we can pass in null into the body of the post and it works perfectly fine. It also still applies the normal model validation via the attributes without having to check the ModelState manually:
public async Task<IActionResult> Post(string id,
[FromBody][Range(1, int.MaxValue, ErrorMessage = "Please enter a value bigger than 1")]
int? value = null)
Reference Automatic HTTP 400 responses
The [ApiController] attribute makes model validation errors automatically trigger an HTTP 400 response
This would explain the returned response.
Remove the [ApiController] to allow the invalid request to still make it to the controller action and also if the additional features of having that attribute is not critical to the current controller.
It would however require that the desired featured be applied manually
[Route("example")]
public class MyExampleController : ControllerBase {
[HttpPost("{id}/value")]
public async Task<IActionResult> Post(string id, [FromBody] int? value) {
if (!ModelState.IsValid) {
//...
return BadRequest(ModelState);
}
//...
return Ok();
}
}
It seems it has something to do with the way a single value is conducted through JSON. It expects a value and the null just simply creates an empty request body. You should consider defining a class like this
public class MyInt{
public int Value { get; set; }
public bool IsNull { get; set; }
}
[ApiController]
[Route("example")]
public class MyExampleController : ControllerBase
{
[HttpPost("{id}/value")]
public IActionResult Post(string id, [FromBody]MyInt value)
{
if(value.IsNull){
}
else{
}
return Ok();
}
}
In other words when you POST you post not just use a default value. You could do that the other way like this
[HttpPost("{id}/value")]
public IActionResult Post(string id, [FromBody]int value)...
[HttpGet("{id}/value")]
public IActionResult Get(string id)...//use the default value here
I am making a small Asp.net Core Web API project. I have a model object that looks like this:
public class Order
{
public readonly int Quantity;
public Order(int quantity)
{
if (quantity < 0)
{
throw new ArgumentException("quantity");
}
this.Quantity = quantity;
}
}
and a controller that looks like this:
[Route("api/[controller]")]
public class OrderController : Controller
{
[HttpPost]
public IActionResult Post([FromBody]Order order)
{
// Do stuff...
return this.StatusCode(200);
}
}
If I pass an Order object with a positive quantity, the Post method works correctly. However, if I pass an Order with a negative quantity, the Order constructor throws an exception that is unhandled by my application or by asp.net.
What can I do to handle this exception in a graceful way? I notice that if I pass a request with invalid syntax (for instance, with badly formed JSON) there is no exception, and the order parameter has value 'null'. Can I set asp.net to do something similar? Or must I rewrite the Order constructor to not throw?
Use DataAnnotation to do what you are doing the MVC way.
public class Order
{
[Range(1, Int32.MaxValue)]
public int Quantity { get; set; }
}
Then in your controller do this:
[HttpPost]
public IActionResult Post([FromBody]Order order)
{
if (!ModelState.IsValid)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
// Do stuff...
return new HttpResponseMessage(HttpStatusCode.OK);
}
The other plus side is if you ever decide to use the Order model in an MVC web application (with views), you will get client side validation because of the Range attribute.
I'm creating an API using C# and ASP.NET Web API and I want it to return an error when a parameter is used that isn't recognised.
For example:
/api/Events
should a list of events
/api/Events?startTime={{startTime}}
should return a list of events that started at a particular time
/api/Events?someRandomInvalidParameter={{something}}
should return an error
Is there a nice config way to do this? If not, how can I get a list of parameters to check myself.
You could create an ActionFilter to automate this:
public class InvalidQueryStringRejectorAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var arguments = actionContext.ActionArguments.Keys;
var queryString = actionContext.Request.GetQueryNameValuePairs()
.Select(q => q.Key);
var invalidParams = queryString.Where(k => !arguments.Contains(k));
if (invalidParams.Any())
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, new
{
message = "Invalid query string parameters",
parameters = invalidParams
});
}
}
}
That filter will reject any request with query string parameters that do not match the method signature.
You may use it like this:
[InvalidQueryStringRejector]
public IHttpActionResult Get(string value)
{
return Ok(value);
}
Or apply to any action by registering it inside your HttpConfiguration object:
config.Filters.Add(new InvalidQueryStringRejectorAttribute());
try strongly typed actions like this
public string Get()
{
return "I'm alive empty";
}
public string Get([FromUri] int id)
{
return "I'm alive";
}
So normal call will return I'm alive or I'm alive empty
http://localhost:1578/api/alive?id=1 //OR
http://localhost:1578/api/alive
But if you try to call it like this
http://localhost:1578/api/alive?blablabla=1
You will occure this error
The requested resource does not support http method 'GET'.
I think you should be override methods
Example:
[Route("/app/Events/{startTime})"]
public ApiResponse Get(string startTime)
{
}
[Route("/app/Events/{startTime}/{someRandomeInvalid}")]
public ApiResponse Get(string startTime, string someRandomeInvalid)
{
}
Don't set explicit parameters name. You should be manage/check by order of parameter
I'm new to MVC Web Api but I have some experience working with ServiceStack framework. Some of the web api examples look a lot like RPC with more action methods and less parameters. In fact most examples seem to limit the request parameter to an id. I would like to create some reporting services using request/response because the request gets quite complex with all the reporting criteria.
Here's my simplified request / response types:
public class PendingRequest
{
public string Id { get; set; }
public int AccountId { get; set; }
public DateTime? FromDate { get; set; }
public DateTime? ToDate { get; set; }
}
public class PendingResponse
{
public string Id { get; set; }
public IEnumerable<Pending> Data { get; set; }
}
And my outline reports controller:
public class ReportsController : ApiController
{
public async Task<PendingResponse> GetPending(PendingRequest request)
{
return new PendingResponse
{
Id = request.Id,
// Data = await Repo.GetPending()
};
}
public async Task<ShippedResponse> GetShipped(ShippedRequest request)
{
return new ShippedResponse
{
Id = request.Id,
// Data = await Repo.GetShipped()
};
}
public async Task<ProductsResponse> GetProducts(ProductsRequest request)
{
return new ProductsResponse
{
Id = request.Id,
// Data = await Repo.GetProducts()
};
}
}
And my routing and config for a self-hosting app:
class Program
{
static void Main()
{
var config = new HttpSelfHostConfiguration("http://localhost:8080");
config.Routes.MapHttpRoute(
name: "Reports",
routeTemplate: "api/reports/{action}",
defaults: new
{
controller = "Reports"
});
using (var server = new HttpSelfHostServer(config))
{
server.OpenAsync().Wait();
Console.WriteLine("Press Enter to quit.");
Console.ReadLine();
}
}
}
So with this routing I intend the action to be the name of the report. Note i've not included the request parameter here. Not sure if I should or not.
The problem is actually on the client side. With the HttpClient the GetAsync method does not allow me to include the JSON request object. I see an extension method PostAsJsonAsync but surely this shouldn't be a POST? Without a GetAsJsonAsync or similar then I can't see a way of including a request with a GET?
To be honest I prefer ServiceStack but there appears to be no async support yet. I need to develop some high performance services which are I/O intensive and I want to reduce the blocking as much as possible.
UPDATE:
It seems Web Api will perform the model binding if, rather than using the request body, I include the model's parameters as part of the query string. In order for this to work, the Get method needs to prefix the model type with the [FromUri] attribute, which informs the model binder that the model should be constructed from the request query string. Messy, but works.
[ActionName("Pending")]
public async Task<PendingResponse> GetPending([FromUri] PendingRequest request)
{
return new PendingResponse
{
Id = request.Id,
// query = ...build from request params
// Data = await Repo.GetPending(query)
};
}
And now on the client side I perform the following:
var result = await _httpClient.GetAsync("api/reports/pending?Id=123&AccountId=456");
result.EnsureSuccessStatusCode();
var response = await result.Content.ReadAsAsync<PendingResponse>();
A call to the service results in the GetPending method being called with a copy of the PendingRequest object materialised from the supplied query string parameters.
The fundamental problem here is you are trying to provide a request body to HTTP GET, which is not allowed. Well, you can still go ahead and format a GET with request body and submit it but that is not according to the spec. Strictly speaking, HTTP spec does not forbid GET requests from having a body but the response for a GET request must not change based on the request body, which basically means you cannot use search criteria in the GET request body. You can use the URI path and query string to specify the search criteria and if you want to bind them into a complex type parameter, you will need to use [FromUri] like this: public async Task<PendingResponse> GetPending([FromUri]PendingRequest request).
Bit of a guess, but what about just URL encoding the JSON request object and putting it in the query string segment of the GET URI?
Also, you might need [FromUri] attribute on your request method parameter in the action as it is a complex type. I may have missed something, but this article on parameter binding doesn't mention anything about GET being a special case for complex types, hence I think they will always try and read from the POST body.