Handling a constructor that throws in a Controller method parameter? - c#

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.

Related

How to pass object to Web API as Parameter with HttpGet

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

How to pass null in body to endpoint within asp.net core 3.1

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

Issue with Model Binding on POST in API

I am having trouble understanding the model binding process in Asp.Net core 2. I have a very simple API that has a model. It has some basic validation on it. Whenever a user posts an incorrect model, I am trying to return a 422 unprocessableentity along with the error messages from the modelstate.
The 2 issues I am trying to understand are as follows:
If I post a request without an ID, a default ID of 0 is being created circumventing the required attribute. I am assuming this is C# functionality for providing default values to fields. Is there a way to circumvent this?
The other problem is that if I place a breakpoint in my post action and send a bad request, it does not even go into the method. It sends back a 400 bad request by using the validation attributes. How does this work? Does the request halt as soon as it tries to model bind to an invalid property (i.e. Name length > 10)? What I need it to do is send back a 422 unprocessable entity with the same error message instead of 400.
Does ASP.NET not even go into the method if the model state validation fails based on the validation attributes? What would be a better way to solve this issue to return a 422 error code?
Below is the code for my various classes (I used the API template when creating the project):
Startup.cs - Only thing I added here was the singleton instance of my in-memory context
public void ConfigureServices(IServiceCollection services)
{
//services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddMvc();
services.AddSingleton<IItemRepository, ItemRepository>();
}
IItemRepository.cs - My interface for DI
public interface IItemRepository
{
List<ItemModel> Items { get; set; }
void AddValue(ItemModel itemModel);
}
ItemRepository.cs - Concrete implementation
public class ItemRepository : IItemRepository
{
public List<ItemModel> Items { get; set; } = new List<ItemModel>();
public ItemRepository()
{
Items.AddRange(
new List<ItemModel> {
new ItemModel {Id = 1, Name = "Test1" },
new ItemModel {Id = 2, Name = "Test2" }
}
);
}
public void AddValue(ItemModel itemModel)
{
Items.Add(itemModel);
}
}
ItemModel.cs - My model class for user input
public class ItemModel
{
[Required]
public int Id { get; set; }
[MaxLength(10)]
public string Name { get; set; }
}
ValuesController.cs
[Route("api/[controller]")]
[ApiController]
public class ValuesController : Controller
{
private IItemRepository _context;
public ValuesController(IItemRepository context)
{
_context = context;
}
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return Ok(_context.Items);
}
// GET api/values/5
[HttpGet("{id}", Name = "GetSingle")]
public ActionResult<string> Get(int id)
{
return Ok(_context.Items.Where(x => x.Id == id));
}
// Problem here - placing a breakpoint in below method does not do anytthing as it will return a 400 bad request instead of 422
[HttpPost]
public ActionResult Post([FromBody] ItemModel itemModel)
{
if (!ModelState.IsValid)
{
return new UnprocessableEntityObjectResult(ModelState);
}
ItemModel addNew = new ItemModel { Id = itemModel.Id, Name = itemModel.Name };
_context.AddValue(addNew);
return Ok(addNew);
}
}
For your first issue, if you don't want to make the property nullable, you can also put a range attribute [Range(1, int.MaxValue)], but 0 will not be a valid value in this case.
For your second issue, if you still want the automatic model validation from ApiControllerAttribute but want a 422 response code instead of 400 you can use the InvalidModelStateResponseFactory configuration option.
services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory = ctx =>
new UnprocessableEntityObjectResult(ctx.ModelState);
});
If I post a request without an ID, a default ID of 0 is being created
circumventing the required attribute. I am assuming this is C#
functionality for providing default values to fields. Is there a way
to circumvent this?
As #StephenMuecke answered here, you need to change your model to
public class ItemModel
{
[Required]
public int? Id { get; set; }
[MaxLength(10)]
public string Name { get; set; }
}
The other problem is that if I place a breakpoint in my post action
and send a bad request, it does not even go into the method. It sends
back a 400 bad request by using the validation attributes. How does
this work? Does the request halt as soon as it tries to model bind to
an invalid property (i.e. Name length > 10)? What I need it to do is
send back a 422 unprocessable entity with the same error message
instead of 400.
This is because you applied the ApiControllerAttribute to the Controller. From the documentation:
Validation errors automatically trigger an HTTP 400 response. The following code becomes unnecessary in your actions:
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
You can either remove the attribute, or, as the same link explains, add this to the startup configuration:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
})
Your first issue can be solved by making the property nullable. As commented by Stepen Muecke.
Also take a look here, perhaps the BindRequired attribute can help. The article also describes how to tweak behaviour.
For your second issue, this is new (breaking) behaviour by Asp.Net Core 2.1. New is the automatic 400 response. That explains why your breakpoint isn't hit. You can suppress this as follows:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});

Sending data from DataTables to Web API controller using POST method

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
}

How do I pass data from a database from one controller to another controller that has its own database in ASP.NET Core?

I have two controllers:
AaController have a view named Index and a database context that we can call for dbContextAa
RrController also has its own database, which we can call for dbContextRr and an action method GetData ()
What I wonder is; how do I do if I want to take the data from the method GetData () and display it in the index view.
Previously, I tested with Temp Data and Sessions without any result. I had no value at all in the view from RrDatabase.
If I understand it right, I have to use Dependency Injections.
So here I have created dependency injection (not sure if it is correct)
AaController:
public class AaController : Controller
{
private RrController rc;
public AaController(RrController rr, AaContext aaContext)
{
rc = rr;
_aaContext = aaContext;
}
public IActionResult Index()
{
var dataFromRr= rs.GetData();
ViewBag.rrData = dataFromRr;
return View();
}
RrController:
public class RrController : Controller
{
private readonly RrContext _rrContext;
public RrController(RrContext rrContext)
{
_rrContext = rrContext;
}
public IActionResult GetData()
{
var data = _rrContext.RrData.Count(x => x.Pid == x.Pid);
return RedirectToAction("Index", "Aa", data );
}
Instead of the value of the view I get Microsoft.AspNetCore.Mvc.ViewResult.
How do I solve this?
Because GetData method returns an IActionResult type response(specifically MVC action result, RedirectResult).
Ideally what you should be doing is, extract the code which gives you the relevant data to a method in a separate class, which you can use in multiple places as needed (ControllerA and ControllerR).
Let's create an interface and a class to implement it.
public interface IRService
{
int GetRDataCount(int pId);
}
public class RService : IRService
{
private RrContext _rrContext;
public RService (RrContext rrContext)
{
this._rrContext=rrContext;
}
public int GetRDataCount(int pId)
{
return _rrContext.RrData.Count(x => x.Pid == pId);
}
}
You can inject IRService implementation to your controllers and call the GetRDataCount method to get the int value.
public class AaController : Controller
{
private IRService rService;
public AaController(IRService rService)
{
this.rService=rService;
}
public IActionResult Index()
{
int countVal = this.rService.GetRDataCount(234); // pass actual PId
ViewBag.rrData = countVal;
return View();
}
}
Make sure to add the IRservice to RService dependency mapping in your Startup class.
Now wherever you want to get the RData count for a specific pId, you can inject an IRservice implementation and use it (Do it in your RController as well)
There are a few different approaches depending on the structure of your projects and how (and if) they can communicate with one another.
What I wonder is; how do I do if I want to take the data from the
method GetData () and display it in the index view.
Your GetData() method is actually attempting to return a View on it's own, however it looks like what you want to do is actually just return the data (i.e. the count) and pass that to your other action:
public int GetData()
{
var data = _rrContext.RrData.Count(x => x.Pid == x.Pid);
// This will pass a parameter that contains your count
return RedirectToAction("Index", "Aa", new { count = data });
}
You need to actually pass your data to the View as a "model". This can easy be done by simply decorating your View with the type of data that you are going to be passing in:
public IActionResult Index(int count)
{
// The count parameter will have the current result of your previous call in it
return View(count);
}
Then you would just need to tell your actual View how you wanted to present the data (i.e. what type it was) via the #model directive at the top of the view:
#model int
There were #Model records found.
Consider A Service
A better implementation would be to consider using a service that decouples your logic from any one specific controller. This would allow you to simply instantiate the service and call it from wherever it was needed as Shyju's answer indicates.

Categories

Resources