Data sent through post method results in a null object - c#

this is my first time ever asking a question on stackoverflow. I have searched through this site many times to look for answers, but this is my first time asking my own! Hopefully I will give enough information so you guys can understand my question.
So basically I have created a new table in our database on sqlserver. I have created a new entity in our entity framework to map to this table as well. The problem is my post method in my odata controller for this database table. Here is the method:
[HttpPost]
[ODataRoute("TerminalPersonnelEmails/LoadEmail()")]
[ResponseType(typeof(TerminalPersonnelEmail))]
public async Task<IHttpActionResult> PostAsync(TerminalPersonnelEmail email)
{
if (email == null)
{
throw new ArgumentException("The data entry given is invalid");
}
portalDatabaseContext.TerminalPersonnelEmails.Add(email);
await portalDatabaseContext.SaveChangesAsync();
//return Created(email);
return Ok("test"); //will be replaced once post method is fixed
}
Here is what I am sending through postman:
{"TerminalEmail" :{
"Id" : 1,
"Name" : "NewEmail",
"Email" : "email#email.com",
"TerminalNumber" : 23084093284 } }
(Sorry for weird indentations/placement of brackets. When i pasted in the code, 1 or 2 indentations were off and my postman code was messed up so I fixed it as best as I could to get it into the code block box).
I have tried several ways to send this data. I have tried just sending the data without clumping it into "TerminalEmail", not sending Id because it is autonum in the database, and I have messed with the content type (json, text, etc.). None of this has worked.
I have made sure that the data matches up with the object so that it should be passing in a valid object to the c# method, but it still is null. I have tired out all of my options and need your help. Thanks!
edit: Here is declaration of terminalpersonnelemail class in the entityframework as requested:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Marketing.EntityFramework.Portal
{
[Table("TerminalPersonnelEmails", Schema = "Portal")]
public class TerminalPersonnelEmail
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required, MaxLength(50)]
public string Name { get; set; }
[Required, MaxLength(50)]
public string Email { get; set; }
[Required]
public int TerminalNumber { get; set; }
}
}

Ok, so I found out how to fix it, but I couldn't tell you why this works. I took out the odata route, and in my odata config took out everything except the entityset that I had created. Here is the working code:
[HttpPost]
[ResponseType(typeof(TerminalPersonnelEmail))]
public async Task<IHttpActionResult> Post(TerminalPersonnelEmail email)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (email == null)
{
throw new ArgumentException("The data entry given is invalid");
}
portalDatabaseContext.TerminalPersonnelEmails.Add(email);
await portalDatabaseContext.SaveChangesAsync();
return Created(email);

Related

Web API + OData - Possible to use different models for GET and POST?

I'm using Web API with Microsoft's OData implementation to build a RESTful service. For a given resource (here, Employee) I would like to return one representation for GET requests (i.e. when retrieving the collection or a single record), but accept a different representation for POST and PUT/PATCH requests.
After building out my API project in this manner and attempting to create a new Employee via a POST request, I get this exception returned back:
{
"error": {
"code": "",
"message": "The request is invalid.",
"innererror": {
"message": "employee : The entity type 'Api.DataContracts.Manage.EmployeeCreate'
is not compatible with the base type 'Api.DataContracts.View.Employee'
of the provided entity set 'MyProject.MyContainer.Employees'. When an entity
type is specified for an OData resource set or resource reader, it has to be
the same or a subtype of the base type of the specified entity set.\r\n",
"type": "",
"stacktrace": ""
}
}
}
To further elaborate on my scenario, here's the model I use to return results for GET requests:
namespace Api.DataContracts.View
{
public class EmployeeView
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string CompanyName { get; set; } // fetched from a related table
}
}
So far, so good. But when I want to create a new Employee I don't want an Id field (the database creates this). Nor do I want to accept a CompanyName field, as what I need at create time is a Company ID so I can properly set the relationship in the database. So that model looks like:
namespace Api.DataContracts.Manage
{
public class EmployeeCreate
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Guid CompanyId { get; set; }
}
}
The relevant signatures on my EmployeeController OData controller are:
[HttpGet]
[ODataRoute("({key})")]
public IHttpActionResult GetEmployee([FromODataUri] Guid key)
// returns an Ok() with a EmployeeView model as the payload
[HttpPost]
[ODataRoute]
public IHttpActionResult AddEmployee(DataContracts.Manage.EmployeeCreate employee)
Lastly, I have this EDM model configuration:
var builder = new ODataConventionModelBuilder();
builder.EntitySet<DataContracts.View.EmployeeView>("Employees");
// Had to add this to avoid a Media Formatter error
builder.AddEntityType(typeof(DataContracts.Manage.EmployeeCreate));
In my research, both here on SO and on the Web at large, I've seen people ask similar (but not exactly the same) questions. The answers tend to point that this scenario should be possible, but it appears the use of Microsoft's OData libraries may make this impossible.
I have not been able to figure out a solution or workaround to this problem. I've considered making one of the models a sub-type of the other (since inheritance is supported in OData) but that feels like it will potentially raise a new set of issues.
Has anyone successfully used a similar architecture with Web API + OData?

How to: Parameter binding from multiple sources

Currently I'm trying to create a web api based on asp.net core 2.0 and I'd like to create a nested route. In case of a put request it sends a part of the information in the route and another part in the body.
Requirements
The desired url to call would be
https://localhost/api/v1/master/42/details
If we'd like to create a new detail below our master 42 I would expect to send the data of the details in the body while the id of the master comes out of the route.
curl -X POST --header 'Content-Type: application/json' \
--header 'Accept: application/json' \
-d '{ \
"name": "My detail name", \
"description": "Just some kind of information" \
}' 'https://localhost/api/v1/master/42/details'
The outcoming response of the request would be
{
"name": "My detail name",
"description": "Just some kind of information",
"masterId": 42,
"id": 47
}
and a location url within the response header like
{
"location": "https://localhost/api/v1/master/42/details/47
}
Work done so far
To get this to work I created this controller:
[Produces("application/json")]
[Route("api/v1/master/{masterId:int}/details")]
public class MasterController : Controller
{
[HttpPost]
[Produces(typeof(DetailsResponse))]
public async Task<IActionResult> Post([FromBody, FromRoute]DetailCreateRequest request)
{
if(!ModelState.IsValid)
return BadRequest(ModelState);
var response = await Do.Process(request);
return CreatedAtAction(nameof(Get), new { id = response.Id }, response);
}
}
Which uses these classes:
public class DetailCreateRequest
{
public int MasterId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class DetailResponse
{
public int Id { get; set; }
public int MasterId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
The problem
So far most of the stuff works as expected. The only thing that really doesn't work is merging the MasterId from the route into the DetailCreateRequest that comes from the body.
First try: Use two attributes on the parameter
I tried to combine these two things by this action call:
public async Task<IActionResult> Post([FromBody, FromRoute]DetailCreateRequest request)
But the incoming object only had a MasterId of zero. If I change the order of the two attributes, then only the id from the route will be taken and all values within the body are ignored (so seems to be first attribute wins).
Second try: Use two different parameters in action
Another approach that I tried was this action call:
public async Task<IActionResult> Post([FromRoute]int masterId, [FromBody]DetailCreateRequest request)
In the first spot this looks okay, cause now I have both values within the controller action. But my big problem with this approach is the model validation. As you can see in the above code I check ModelState.IsValid which was filled through some checks from FluentValidation, but these checks can't be really done, cause the object wasn't build up correctly due to the missing master id.
(Not-working) Idea: Create own attribute with merge parameters
Tried to implement something like this:
public async Task<IActionResult> Post([FromMultiple(Merge.FromBody, Merge.FromRoute)]DetailCreateRequest request)
If we already would have something like this, that would be great. The order of the arguments within the attribute would give out the order in which the merge (and possible overwrites) would happen.
I already started with implementing this attribute and creating the skeleton for the needed IValueProvider and IValueProviderFactory. But it seems to be a quite lot of work. Especially finding all the nifty details to make this work seamlessly with the whole pipeline of asp.net core and other libraries I'm using (like swagger through swashbuckle).
So my question would be, if there already exists some mechanism within asp.net core to achieve such a merge or if anybody is aware about an already existing solution or about a good example on how to implement such a beast.
Solution so far: Custom ModelBinder
After getting the answer from Merchezatter I look into how to create a custom model binder and came up with this implementation:
public class MergeBodyAndValueProviderBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
var body = bindingContext.HttpContext.Request.Body;
var type = bindingContext.ModelMetadata.ModelType;
var instance = TryCreateInstanceFromBody(body, type, out bool instanceChanged);
var bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
var setters = type.GetProperties(bindingFlags).Where(property => property.CanWrite);
foreach (var setter in setters)
{
var result = bindingContext.ValueProvider.GetValue(setter.Name);
if (result != ValueProviderResult.None)
{
try
{
var value = Convert.ChangeType(result.FirstValue, setter.PropertyType);
setter.SetMethod.Invoke(instance, new[] { value });
instanceChanged = true;
}
catch
{ }
}
}
if (instanceChanged)
bindingContext.Result = ModelBindingResult.Success(instance);
return Task.CompletedTask;
}
private static object TryCreateInstanceFromBody(Stream body, Type type, out bool instanceChanged)
{
try
{
using (var reader = new StreamReader(body, Encoding.UTF8, false, 1024, true))
{
var data = reader.ReadToEnd();
var instance = JsonConvert.DeserializeObject(data, type);
instanceChanged = true;
return instance;
}
}
catch
{
instanceChanged = false;
return Activator.CreateInstance(type);
}
}
}
It tries to deserialize the body into the desired object type and afterwards tries to apply further values from the available value providers. To get this model binder to work I had to decorate the destination class with the ModelBinderAttribute and made the MasterId internal, so that swagger doesn't announce it and JsonConvert doesn't deserialize it:
[ModelBinder(BinderType = typeof(MergeBodyAndValueProviderBinder))]
public class DetailCreateRequest
{
internal int MasterId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
Within my controller the action method parameters are still containing the [FromBody] flag, cause it is used by swagger to announce on how the method can be called, but it never will be called, cause my model binder has a higher priority.
public async Task<IActionResult> Post([FromBody]DetailCreateRequest request)
So it is not perfect, but works good so far.
That is looks like a right choice:
[HttpPost]
[Produces(typeof(DetailsResponse))]
public async Task<IActionResult> Post([FromRoute]int masterId, [FromBody]DetailCreateRequest request) {
//...
}
But if you have some problems with domain model validation, create custom Dto object without master Id.
Otherwise you can use custom model binder, and then work with arguments from action and binding contexts.
I'm not sure if this works in Asp.Net-Core 2.0, but we use the following in 3.1 to have a single request object which gets its properties from multiple locations:
// Annotate the action parameter with all relevant attributes
public async Task<IActionResult> Post([FromBody][FromRoute][FromQuery]DetailCreateRequest request) { ... }
// Annotate each property separately, so the binder(s) don't overwrite
public class DetailCreateRequest
{
[FromRoute]
public int MasterId { get; set; }
[FromBody]
public string Name { get; set; }
[FromQuery]
public string Description { get; set; }
}
It works with .Net 6:
[HttpPost]
[Route("{id}")]
public async Task<ActionResult<CustomerResponse>> Post([FromRoute, FromBody] CustomerPostRequest request)
{
return Ok();
}
public class CustomerPostRequest
{
[FromRoute(Name = "id")]
public int Id { get; set; }
[FromBody]
public string Name { get; set; }
}
Set the your required "source" attributes on the single request object parameter, and inside this object add each property the relevant "source" attribute.
Make sure the FromBody is the last one (it didn't work when I switched them).

WEB API With database Post Method

I need to make an API Post method where I insert data into the database.
The database has 3 fields: One field has default values given by the database, one I have to insert with a new Guid.NewGuid() method, and one is inserted by the user.
How can I do this? Never seen anything like this in a tutorial.
If you can give me an example I will appreciate.
I'm new in web APIs and have seen a bunch of tutorials since Wednesday and can't reach a solution.
EDIT:
Here is my code:
public HttpResponseMessage Post(Company value)
{
try
{
if(ModelState.IsValid)
{
bd.Companies.Add(value);
bd.SaveChanges();
return Request.CreateResponse(HttpStatusCode.OK);
}
else
{
return Request.CreateResponse(HttpStatusCode.InternalServerError, "Invalid Model");
}
}
catch (Exception ex )
{
return Request.CreateResponse(HttpStatusCode.InternalServerError, ex.Message);
}
}
How can i put a Guid.NewGuid()in this code to give a value to one of my fields?
EDIT2: My class to receive the values from Post
public class CompanyPostViewModel : Company
{
public static Guid Guid.NewGuid(); -->how can i do this?
public string Name { get; set; }
public DateTime? CreatedDate { get; set; }
}
If you are looking for an example, that fully goes from the front-end, to using WEB API [Post], to writing to the database, please see the following site. It should provide you enough context to complete what you are trying to accomplish. If this is insufficient, please post your current code, and where you are having any issues.

ASP.NET MVC placing userID in hidden field

I am using ASP.NET MVC 5 Razor
I am trying to apply the membership userID to a hidden field so that I can associate table data to a spceific user.
(users completes a form that is stored in a table, userID used to associate to login profile)
I just don't know how to do this and is an important part of my current project and future projects.
Any guidance, advice, links to solutions would be of great help as I am completely at a loss with this.
I tried passing the data from the model class for the view but I get an error saying "The name 'User' does not exist in the current context"
this is an extract of my model class
using System;
using System.Web;
using System.Web.Mvc;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Globalization;
using System.Web.Security;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
namespace mySite_Site.Models
{
[Table("accountInfo")] // Table name
public class accountInfo
{
[Key]
public int AccountID { get; set; }
public int UserIdent { get; set; } //this is the field that would store the userID for association
public string LastName { get; set; }
public string FirstName { get; set; }
public string Locality { get; set; }
public string EmailAddress { get; set; }
public bool Active { get; set; }
public DateTime LastLoggedIn { get; set; }
public string UserIdentity = User.Identity.GetUserId();
}
Expanding on Brandon O'Dell's answer, using "Membership" in that block of code didn't work for me (unhandled errors). Nevertheless, I think his approach to this solution is great because it means you can call the current user's Id from practically anywwhere. So, I went on ahead and played a little bit with the code, and voilá!.
In case using "Membership" doesn't work for you as well, try this one:
using <your project's name>.Models
public class GeneralHelpers
{
public static string GetUserId()
{
ApplicationDbContext db = new ApplicationDbContext();
var user = db.Users.FirstOrDefault(u => u.UserName == HttpContext.Current.User.Identity.Name);
return user.Id;
}
}
This one gets the whole user, so, you can create even more methods inside this "GeneralHelper" class (or whatever name you wish to give it) to get the current user's info and use it in your application.
Thanks, Brandon!
You just need something like this, assuming your ViewModel has the user profile on it.
#Html.HiddenFor(m=>m.UserProfile.UserId)
Since your model is not in the controller, you need to explicitly tell the code Where the user object is, which is contained in the HttpContext. So, update this line here:
public string UserIdentity = User.Identity.GetUserId();
to the following
public string UserIdentity = HttpContext.Current.User.Identity.GetUserId();
The controller and view base classes have a reference to the current HttpContext, which is why you can shortcut in those items and simply use User.Identity. Anywhere else in your project, you will need the fully qualified HttpContext.Current.User.
Edit
In further looking at your code, it looks like you are trying to save the user Id as a column in your database. In that instance, I think (based on your code sample) that you should remove that last part - public string UserIdentity = User.Identity.GetUserId();. When you save a new account info object, that is where you would save the user id.
var info = new accountInfo();
accountInfo.UserIdent = HttpContext.Current.User.Identity.GetUserId();
db.accountInfos.Add(info);
db.SaveChanges();
Why not just create a static helper class?
public static class UserUtils
{
public static object GetUserId()
{
return Membership
.GetUser(HttpContext.Current.User.Identity.Name)
.ProviderUserKey;
}
}

Validation does not work if nothing is sent?

I annotated my model as this:
public class Instance
{
[Required]
public string Name { get; set; }
public string Description { get; set; }
[Required]
public string DBServer { get; set; }
[Required]
public string Database { get; set; }
}
In the post method I get a null for the value if nothing was sent but Model.State is true. How can the state be true if nothing was sent? The next problem is that the CreateErrorResponse method throws an exception when I call it (probably because the value was null).
public HttpResponseMessage Post([FromBody]Instance value)
{
if (value != null && ModelState.IsValid)
{
...
}
else
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
Edit:
As it seems I didn't explain it right. I try now with some screenshots.
Case 1
I post a correct value using Fiddle and everything works as expected. ModelState.IsValid is true.
Case 2
I post a value with a missing required field (DBServer) and then again everything works as expected. ModelState.IsValid is false.
Case 3
My question. I send a post request with NO information and ModelState.IsValid is true. This seems very strange and I would like to know the reason. Thank you all for your answers.
Try abstracting the ModelState check into a filter. You won't have to check for ModelState every time this way and if there's a problem
This code below comes from a great article on ModelState in WebAPI:
http://www.asp.net/web-api/overview/formats-and-model-binding/model-validation-in-aspnet-web-api
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using System.Web.Http.ModelBinding;
namespace MyApi.Filters
{
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid == false)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
}
However, what you need to know is that ModelState only checks internal values, so you need to supply a check to see if the item is null before calling on ModelState.
Check this answer for more details: ModelState.IsValid even when it should not be?

Categories