I have an interesting issue. I have following model with required data annotation for some of the properties.
public class Consumer : MongoEntity
{
public Consumer()
{
Products = new List<Product>();
}
[Required]
public string Email { get; set; }
[Required]
public string Password { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
[Required]
public Location CurrentLocation { get; set; }
public List<Product> Products { get; set; }
}
Now in my web api controller I am accepting this as a parameter as show below:
[Route("")]
[ValidateModel]
public IHttpActionResult Post([FromBody]Consumer consumer)
{
try
{
if (ModelState.IsValid)
{
_consumerService.Create(consumer);
return Ok("User created sucessfully.");
}
return BadRequest("User object is not complete. It's missing mandatory fields");
}
catch
{
return InternalServerError(new Exception("Something went wrong while saving the data back to database."));
}
}
I am under the impression that if any of the required field of consumer model is null model state should be false, but it always return true. Web api only sets model state null when i send an empty body to the controller. Is there any logical explanation to this? Why web api don't take account of required properties of complex argument type?
Related
I am creating a simple API and I have a method that handles the update of a game:
[HttpPut("{Id}")]
public bool UpdateGame(Guid Id, [FromBody] Game game)
{
Game? oldGame = _unitOfWork.Games.GetById(Id);
if (oldGame != null)
{
oldGame.SeasonId = game.SeasonId;
oldGame.VenueId = game.VenueId;
oldGame.GameTypeId = game.GameTypeId;
oldGame.GameTitle = game.GameTitle;
oldGame.GameDateTime = game.GameDateTime;
oldGame.PublishResults = game.PublishResults;
oldGame.GameDetails = game.GameDetails;
oldGame.Buyin = game.Buyin;
oldGame.Fee = game.Fee;
_unitOfWork.Save();
return true;
}
else
{
return false;
}
}
I have tests against the repo and the controller and the update works fine.
I'm now implementing some CRUD pages using Blazor Server:
async Task OnSubmit()
{
if (Game != null)
{
try
{
var response = await _apiClient.httpClient.PutAsJsonAsync<Game>($"/api/Games/{Id}", Game);
bool updated = await response.Content.ReadFromJsonAsync<bool>();
if (updated)
{
_navManager.NavigateTo("/settings/games");
}
}
catch (Exception ex)
{
AlertIsVisible = true;
Message = ex.Message;
MessageType = AlertMessageType.Danger;
}
}
}
This is failing because of a bad request 400, but this is all the output is telling me. The put method in the controller doesn't hit as my breakpoint doesn't trigger.
Simpler models in my app post just fine using similar code.
The difference here I believe is this Game entity:
public class Game
{
public Guid Id { get; set; }
public Guid? SeasonId { get; set; }
public Guid? GameTypeId { get; set; }
public Guid? VenueId { get; set; }
public string? GameTitle { get; set; }
public DateTime GameDateTime { get; set; }
public bool PublishResults { get; set; }
public string? GameDetails { get; set; }
public double Buyin { get; set; }
public double Fee { get; set; }
public virtual Season Season { get; set; } = default!;
public virtual GameType GameType { get; set; } = default!;
public virtual Venue Venue { get; set; } = default!;
public virtual ICollection<Result> Results { get; set; } = new HashSet<Result>();
}
I think there is a serialisation issue with the navigation properties (Season, GameType and Venue - not the Results collection). If I remove the navigation properties the update succeeds. I'm at a loss at this point on how to handle the put request so that it works with the navigation properties on my model.
You declare Season, GameType and Venue as not Nullable [no ?], and then forcibly set to default!, which for any object is null.
You normally use default! when you know an object will be set to a value before any attempt is made to use it, which is not the case here.
I am using .net 5.0 and AspNetCore.
I want my API to receive a JSON Body that is being converted to a class. I do this by using following code in my controller:
[HttpPost("languages")]
public IActionResult Update([FromBody] LanguagesDto updatedDto)
{
if (!ModelState.IsValid)
{
// Log Error and return BadRequest
return BadRequest(ModelState);
}
// Do something
return Ok();
}
Lets say this is my DTO-Class:
public class LanguagesDto
{
[Required]
public bool English{ get; set; }
[Required]
public bool Spanish { get; set; }
[Required]
public bool French { get; set; }
}
With above code, I would expect the ModelState to be invalid, when one of the properties is missing in the json body.
So this JSON should return a bad request.
{
"english": true
}
but what happens instead is, that the default values for booleans are being added to the object and thus the ModelState being valid!
I have tried to use different Attributes but it didn't work out. Any idea?
change LanguagesDto to this :
public class LanguagesDto
{
[Required]
public bool? English { get; set; }
[Required]
public bool? Spanish { get; set; }
[Required]
public bool? French { get; set; }
}
When the code has been detected by Fortify Security with the above possible error.
I cannot understand how the models can be manipulated to avoid the possible error.
What sort of checks could be done to avoid the error ?
How do we determine if it is an actual error.
eg :
public class TestObject
{
public string ID { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string TestService { get; set; }
public int selected { get; set; }
public TestObject()
{
}
}
[HttpPost, ValidateAntiForgeryToken()]
public ActionResult TestObject DataModel, string command)
{
}
Since the TestObject has [Required] and not required attributes.Fortify could be raising the error.
I am trying to create within MVC 5 and am getting a validation error even though the ModelState is coming back valid.
Error message
Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.
and when I look at the message, it shows....
The name 'e' does not exist in the current context
When I look at the POST data, the model that was created has all required fields filled in. I did notice that the model ID was assigned 0. I'm not sure if that is the error or if it is supposed to pass a zero for the ID.
What might the problem be?
WosController.cs
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include = "id,woNumber,woDescription,dueDate,qty,item_id,releaseDate,parentWO_id,wip_id")] Wo wo)
{
if (ModelState.IsValid)
{
db.Wos.Add(wo);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(wo);
}
Wo.cs
public partial class Wo
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Wo()
{
this.WoParts = new HashSet<WoPart>();
this.WoStatuses = new HashSet<WoStatus>();
}
public int id { get; set; }
public string woNumber { get; set; }
public string woDescription { get; set; }
public Nullable<System.DateTime> dueDate { get; set; }
public string qty { get; set; }
public Nullable<int> item_id { get; set; }
public Nullable<System.DateTime> releaseDate { get; set; }
public string status { get; set; }
public Nullable<int> parentWO_id { get; set; }
public int wip_id { get; set; }
public Nullable<int> part_id { get; set; }
public virtual Item Item { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<WoPart> WoParts { get; set; }
public virtual Wo woParentWO { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<WoStatus> WoStatuses { get; set; }
public virtual Part Part { get; set; }
public virtual Wip Wip { get; set; }
}
Wrap your call to SaveChangesAsync in a try...catch like so:
try
{
await db.SaveChangesAsync();
}
catch (DbEntityValidationException e)
{
var errorMessages = e.EntityValidationErrors
.SelectMany(x => x.ValidationErrors)
.Select(x => x.ErrorMessage);
var fullErrorMessage = string.Join("; ", errorMessages);
var exceptionMessage = string.Concat(e.Message, " The validation errors are: ", fullErrorMessage);
throw new DbEntityValidationException(exceptionMessage, e.EntityValidationErrors);
}
That will show you the actual properties causing the validation issues. Then, update your question with the results, if you still need assistance.
Likely, your database is out of sync with your entities. The status property is not required on your entity, and by default properties of type string are nullable. That would explain why you're passing validation on post, but failing on actually saving the entity.
Generally, it's best not to rely on the database setting a default value in the first place. Instead, have the property itself have a default value, and then it will always be fine, regardless of what's going on at the database level:
private string _status;
public string status
{
get { return _status ?? "Default Value"; }
set { _status = value;
}
Short of that, if status is truly not required, then you should ensure that the status column on your table is nullable.
Revised entire post.
I'm trying to post the following JSON POST request via Fiddler:
{Username:"Bob", FirstName:"Foo", LastName:"Bar", Password:"123", Headline:"Tuna"}
However I'm getting this error:
Message "Cannot insert the value NULL into column 'Id', table 'xxx_f8dc97e46f8b49c2b825439607e89b59.dbo.User'; column does not allow nulls. INSERT fails.\r\nThe statement has been terminated." string
Though if I manually send a random Id along with the request then all is good. Like so:
{Id:"1", Username:"Bob", FirstName:"Foo", LastName:"Bar", Password:"123", Headline:"Tuna"}
Why does Entity Framework not generate and auto increment the Id's? My POCO class is as follows:
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; }
public string Username { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Password { get; set; }
public string Headline { get; set; }
public virtual ICollection<Connection> Connections { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
public virtual ICollection<Phonenumber> Phonenumbers { get; set; }
public virtual ICollection<Email> Emails { get; set; }
public virtual ICollection<Position> Positions { get; set; }
}
public class Connection
{
public string ConnectionId { get; set; }
public int UserId { get; set; }
public virtual User User { get; set; }
}
public class Phonenumber
{
public string Id { get; set; }
public string Number { get; set; }
public int Cycle { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
Here is the controller method. When in debug mode and I send the request via Fiddler it breaks at db.SaveChanges(); and gives the error seen a bit above.
// POST api/xxx/create
[ActionName("create")]
public HttpResponseMessage PostUser(User user)
{
if (ModelState.IsValid)
{
db.Users.Add(user);
db.SaveChanges();
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, user);
response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = user.Id }));
return response;
}
else
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
}
What's wrong?
Solution
Change string Id to int instead and remove Data annotations. Renamed the Id to UserId, still following convention, and made changes where necessary in other POCO's to match up with the changes.
This is a guess :)
Is it because the ID is a string? What happens if you change it to int?
I mean:
public int Id { get; set; }
You have a bad table design. You can't autoincrement a string, that doesn't make any sense. You have basically two options:
1.) change type of ID to int instead of string
2.) not recommended!!! - handle autoincrement by yourself. You first need to get the latest value from the database, parse it to the integer, increment it and attach it to the entity as a string again. VERY BAD idea
First option requires to change every table that has a reference to this table, BUT it's worth it.