ASP.NET Core can't get object from body - c#

Code:
[HttpPost("user/register")]
public IActionResult Register([FromBody] User user)
{
if (user?.Name is null || user?.Password is null)
{
return BadRequest(new {message = "User is null and/or name and password are not provided"});
}
else
{
// Add to db
return Json(user);
}
}
Also the User class:
public class User
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Password { get; set; }
public string Role { get; set; }
}
It should basically get a user and add it to the database. I tried sending this json:
{ "Name": "Batman", "Password": "IronmanSucks"}
The app caught the request, but the user object was null. I also tried with:
{ "user": { "Name": "Batman", "Password": "IronmanSucks"} }
But according to this documentation, the first json should have worked fine.
Here is a link to an example http request in postman
Does this have to do with the headers or is it a bug in .NET Core 2.0?

This can only happen if the type does not have a parameterless constructor, thus this can be simply fixed by adding such.

I believe that the Model is coming up as invalid hence why it is null.
You should try adding a [BindNever] attribute into the User class for the Role and Guid properties, seeing as you aren't using them.
If that doesn't work you may try using extended classes like so:
public class User
{
public string Name { get; set; }
public string Password { get; set; }
}
public class DataUser : User
{
public Guid Id { get; set }
public string Role { get; set; }
}

If you're using MVC Core instead of MVC, make sure you add Json Formaters (from Microsoft.AspNetCore.Mvc.Formatters.Json). In your Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvcCore()
.AddJsonFormatters();
}
This should help the [FromBody] to de-serialize your Post content

Related

Endpoint is not hitting if there is a null property in the parameter object

I am calling a .NET Core (.NET6) API into a blazor application. this is the point where I am calling API in blazor application
public class OrderManager : IOrderManager
{
private readonly HttpClient _httpClient;
public OrderManager(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task SaveOrder(Order order)
{
try
{
var response = await _httpClient.PostAsJsonAsync(.../api/Order/createGenericOrder, order);
}
catch (Exception exp)
{
}
}
}
then in my API, I have 2 custom middlewares. before coming to the endpoint both middleware execute first. then this is my API controller
[BLAuthorize]//custome authorize attribute
[Route("api/[controller]")]
[ApiController]
public class OrderController : ControllerBase
{
ILogger<OrderController> _logger;
public OrderController(ILogger<OrderController> logger)
{
_logger = logger;
}
[HttpPost("createGenericOrder")]
public IActionResult CreateGenericOrder(Order order)
{
//
}
}
public class Order
{
public int OrderKey { get; set; } = 1;
public CodeBaseResponse Location { get; set; }
public string OrderName{get;set;}
}
public class CodeBaseResponse
{
public int CodeKey { get; set; }
public string CodeName { get; set; }
}
I am passing this Order type object parameter to this above controller action. Following you can see a JSON object Order object that has been got in one API call.
{
"OrderKey":45897,
"Location":{
"CodeKey":14258,
"CodeName":null,
},
"OrderName":null
}
now my problem is when we are going to pass the above type object my endpoint is not executing. the reason is this Location.CodeName and OrderName are being null.when I pass an empty string or some text with those they are working fine. I couldn't identify what is this issue. please help if there is anyone who knows a solution for this.
It would have been nice if you had posted the response you are getting, but for this particular case I think I know what you got: You got an HTTP response code of 400 BAD REQUEST stating OrderName or the other guy is a required field.
This happens because .Net6 comes with nullable reference types enabled. If you open your project file (the one with the .csproj extension), you'll find this: <Nullable>enable</Nullable>. Delete it if you want to opt out of nullable reference types. It will now work as you expect.
Alternatively, if you do not want to opt out of nullable reference types, simply make your properties optional with a question mark, like this:
public class Order
{
public int OrderKey { get; set; } = 1;
public CodeBaseResponse Location { get; set; }
public string? OrderName { get; set; }
}
public class CodeBaseResponse
{
public int CodeKey { get; set; }
public string? CodeName { get; set; }
}

Correct way to pass object reference into .NET Core Web API

I'm just starting out with .NET Web API programming, and I have a question for seasoned .NET developers - what is the "correct" way to pass an object reference into a Create endpoint?
I have the following models:
public class Task
{
public int ID { get; set; }
public string Title { get; set; }
public virtual User User { get; set; }
}
public class User
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
And my Controller - endpoint to create a Task:
[HttpPost]
public async Task<IActionResult> PostTask([FromBody] Models.Task task)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
_context.Task.Add(task);
await _context.SaveChangesAsync();
return CreatedAtAction("GetTask", new { id = task.ID }, task);
}
By default, this has some interesting behavior. It expects an entire User model to be passed into the POST request (see below), and will actually create the user when passed:
{
"id": 0,
"title": "string",
"user": {
"id": 0,
"firstName": "string",
"lastName": "string"
}
}
I understand technically why it would do this, but this is definitely not acceptable behavior in a real app - so my question is - what is the "correct/appropriate" way to pass a UserID and do validation on the model in .NET? Should I forego the use of "ModelState.IsValid" and do manual validation?
As a secondary question - I am using NSwag to generate Swagger docs from my API, and it's showing "id" as a parameter that needs to be passed into the POST method. Obviously, ID cannot be passed as it's generated in code, but is there a way to get Swagger to not show ID as being a passable property?
Then create a data transfer model that exposes only the data you want sent over the wire.
public class NewTaskDto {
[Required]
public string Title { get; set; }
[Required]
public int UserId { get; set; }
}
and map the data to your models on the server, along with what ever validation is required. For example checking that the UserId exists and is valid.
[HttpPost]
public async Task<IActionResult> PostTask([FromBody] NewTaskDto data) {
if(data != null) {
validateUserId(data.UserId, ModelState);
}
if (!ModelState.IsValid) {
return BadRequest(ModelState);
}
Models.Task task = MapDataToTask(data); //create Task and copy members
await saveTask(task);
return CreatedAtAction("GetTask", new { id = task.ID }, task);
}
That way the doc will only see the and report on the exposed members.
The attribute [Required] is mandatory and then you can check parameter.

How to handle model binding exceptions in ASP.NET Core MVC?

Create a simple ASP.NET Core MVC web application from the template. Create a class called Human:
public class Human
{
public string Name { get; set; }
public int Age { get; set; }
public DateTime Birthday { get; set; }
}
Now create a HumanController to post a human instance:
[HttpPost]
public Human Post([FromBody] Human human)
{
return human;
}
Using Fiddler or PostSharp (or any other client) post these JSON objects to this service:
{
"Name": "someone"
}
and
{
"Name": "someone",
"Age": "invalid age"
}
and
{
"Name": "someone",
"Birthday": null
}
Since Birthday can't be null and "invalid age" can't be parsed as valid model properties, what we get in our service parameter is null. That has proved to be hard to debug.
Is there a way that we can configure ASP.NET Core MVC to either only partially bind models as much as it can, or to somehow let us hook into its default behavior so that we can catch the exception and notify client about wrong data it has sent?
I think if you add Proper Attributes to your Model you can Handel it and return Proper Error Message to Clients.
For example you can put some Requirments on Name like :
public class Human
{
[Required(ErrorMessage ="")]
[StringLength(10)]
public string Name { get; set; }
public int Age { get; set; }
public DateTime Birthday { get; set; }
}
In this way you can Check your model and if it is not valid return BadRequest like :
public IActionResult Index(Human model)
{
if (!ModelState.IsValid)
return BadRequest("message");
//to do
return View();
}

POCO object array inside AppSettings.json in ASP.NET Core

This seems like it should be really simple, I have been searching SO and a lot of other places for an answer to this, everything I have found and tried does not work.
I have an appsettings.json file that looks like this
"Email": {
"Port": "25",
"Host": "localhost",
"EnableSSL": "false",
"Credentials": {
"Username": "fakeuser",
"Password": "fakepassword"
},
"SystemFromAddress": "testsender#localhost.com",
"SystemFromDisplayName": "Test Sender",
"EmailTemplateRootDirectory": "Email\\EmailTemplates",
"EmailTemplates": [
{
"TemplateKey": "ResetPassword",
"TemplatePath": "ResetPassword.cshtml"
},
{
"TemplateKey": "NewAccount",
"TemplatePath": "NewAccount.cshtml"
},
{
"TemplateKey": "VerifyEmail",
"TemplatePath": "VerifyEmail.cshtml"
}
]
}
There are several models (EmailOptions being the parent) that I am trying to bind to, the EmailOptions class is expecting to have it's EmailTemplates list populated from the EmailTemplates list in the appsettings.json as seen above.
The parent class is being populated by the appsettings.json file as expected, the Child List of Email Templates in this class is always coming up empty.
Here are the classes I am binding to.
public class EmailOptions
{
public int Port { get; set; }
public string Host { get; set; }
public bool EnableSSL { get; set; }
public EmailCredentials Credentials { get; set; }
public string SystemFromAddress { get; set; }
public string SystemFromDisplayName { get; set; }
public string EmailTemplateRootDirectory { get; set; }
public IEnumerable<EmailTemplate> EmailTemplates { get; set; } = new List<EmailTemplate>();
}
public class EmailTemplate
{
public string TemplateKey { get; set; }
public string TemplatePath { get; set; }
}
public class EmailCredentials
{
public string Username { get; set; }
public string Password { get; set; }
}
I am using the following call I am making in my startup class in ASP.NET Core.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddOptions();
services.Configure<EmailOptions>( _configuration.GetSection("Email" ));
...
For some reason the IEnumerable property in my EmailOptions is not being deserialized from the appsettings.json into my options - when I attempt to use it anywhere in my controllers - the list is always set to an empty array.
FWIW: I have this working in a console application where I have more control over setting up my options from the appsettings.json. Here is what I am doing in the console app, (I am leaving out the code where I set up the options with the DI container for brevity)
var emailSection = configuration.GetSection( "Email" );
var emailOptions = emailSection.Get<EmailOptions>();
emailOptions.EmailTemplates = configuration.GetSection( "Email:EmailTemplates" ).Get<List<EmailTemplate>>();
as expected - in the console application, I get my Email Templates because i have the ability to get the child list separately and add it to the options before handing it over to the DI container. I don't seem to have that flexibility in the ASP.NET Core IServiceCollection.Configure() extension method (so maybe use another method to do this? which one? After a couple hours of searching I am crying uncle and asking for help).
So how does one get this to work using the ASP.NET Core "IServiceCollection.Configure()" method? Is there a better way to do this?
Thank you Joe for pointing out what needed to happen!
I made the false assumption that the serializer would happily create it's list from the json and assign that list to my IEnumerable - rather - you need to make sure to use List if you intend to deserialize a list of json objects into your Options (and other concrete dotnet types where applicable).
so instead of this
IEnumerable<EmailTemplate> EmailTemplates { get; set; }
I should have had this...
List<EmailTemplate> EmailTemplates { get; set; }

c# WebApi OData complex type is always null in post ODataRoute

My problem is that "siteSetup" is always null for the following odata action:
[HttpPost]
[ODataRoute("Setup")]
public IHttpActionResult Setup(SiteSetup siteSetup)
{
return BadRequest("Not yet working");
}
This is my complex type
public class SiteSetup
{
public SiteSetup()
{
}
public string Username
{
get;
set;
}
public string Password
{
get;
set;
}
public string CompanyName
{
get;
set;
}
}
And this is the fiddler for a request.
Action with complextype as parameter is support in OData/WebApi V4, you may use ODataActionParameters in your controller method, you can see this page for instruction, http://odata.github.io/WebApi/#04-07-action-parameter-support

Categories

Resources