Asp.net core MVC post parameter always null - asp.net-mvc

I am new to MVC core.
I have created a project with MVC core which has a controller. This controller has Get and Post action methods. If i pass data to Get method using query string it works fine, but when i pass complex JSON to post method, then it always shows me null.
Here what i am doing:
Post Request
URL: http://localhost:1001/api/users
Content-Type: application/json
Body:
{
"Name":"UserName",
"Gender":"Gender of the user",
"PhoneNumber":"PhoneNumber of the user"
}
Here is the Post action method
[HttpPost]
[Route("api/users")]
public async Task<IActionResult> Post([FromBody]User newUser)
{
...
}
When post request is called, then newUser always shows me null. And if i remove [FromBody] attribute then i receive newUser object but all of its fields are null.
Please help me and guide me in this issue.
EDITED
Here is my User class
public class User{
public int Id { get; set; }
public string Name { get; set; }
public string Gender { get; set; }
public string PhoneNumber { get; set; }
}
I had done same as described here for json data, but still receives null.

This could be because of how the null values are being handled. Set NullValueHandling to Ignore in AddJsonOptions and see if that works.
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.AddJsonOptions(jsonOptions=>
{
jsonOptions.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
});
}

Note the original method Post([FromBody] User newUser)
For future readers from google, this same issue could arise if the method was Post(User newUser)
Note the lack of [FromBody]. This is a departure from previous versions of MVC where these parameters were generally inferred.
If you're an existing MVC5 developer who finds this page regarding AspNetCore.MVC, make sure to double check that you have [FromBody] decorated where relevant.

I created new ASP.NET Core project, added your functionality, and it works. Please, checkout this project on github.
Also, see screenshot of log with simple communication with this controller from browser console: Console output

Are you on Microsoft.AspNetCore.Mvc 1.0.0?
If you are, try sending this object as your body in a request (camel cased properties):
{
"name":"UserName",
"gender":"Gender of the user",
"phoneNumber":"PhoneNumber of the user"
}

Related

How to document a wrapped response to be displayed in swagger ui using a Swashbuckle in asp.net core web api

I working on a ASP.NET Core 3.1 web api project. I'm using Swashbuckle.AspNetCore 5.0.0 for documenting my API. Things are working good. However I got stuck with generating response types as my api is using an middleware to wrap every response for consistency. I'm not able to generate correct response type in my swagger ui.
Here is an simple example,
My Action Method:
[HttpGet]
[ProducesResponseType(200, Type = typeof(IEnumerable<WeatherForecast>))]
public IEnumerable<WeatherForecast> Get()
...
As I mentioned, the project has response middleware which will wrap all the response as shown in the below format,
{
"Version": "1.0.0.0",
"StatusCode": 200,
"Message": "Request successful.",
"Result": [
"value1",
"value2"
]
}
Because of this I'm getting mismatch in response value in my swagger ui.
Example of response schema shown in swagger ui as per [ProducesResponseType(200, Type = typeof(IEnumerable<WeatherForecast>))]
But the actual wrapped response looks like,
Is it possible to handle these wrapped response using Swashbuckle.AspNetCore 5.0.0. Please assist me.
After some analysis and research, I found the solution. It's pretty simple using the [ProducesResponseType] attribute.
I created a separate class named ResponseWrapper<T>,
public class ResponseWrapper<T>
{
public int StatusCode { get; set; }
public string Message { get; set; }
public T Result { get; set; }
}
And then decorated my action method as follows,
[HttpGet]
[ProducesResponseType(200, Type = typeof(ResponseWrapper<IEnumerable<WeatherForecast>>))]
public IEnumerable<WeatherForecast> Get()
...
And that works. Hope this helps someone.

Entity Framework Core Error: No parameterless constructor defined for this object

At the point of creating a new MVC Controller:
after I click Add button, I get the following Error:
Here is my simple Context class:
public class MainDbContext : DbContext
{
public MainDbContext(DbContextOptions<MainDbContext> options) : base(options)
{
}
public DbSet<Todo> Todo { get; set; }
}
and my simple model:
public partial class Todo
{
public int Id { get; set; }
public string TaskName { get; set; }
}
I have made some search on this issue, most of the posts point to a dropdown list or a SelectList method using MVC, but for my case it is a Controller creation fail, so it seems to be an Entity Framework Core issue
Any help ?
Thanks to #poke comment above, and to this link: "Use Code First with connection by convention", by modifying the context class as follows C# will call base class parameterless constructor by default
public class MainDbContext : DbContext
{
public MainDbContext()
// C# will call base class parameterless constructor by default
{
}
}
It's a tooling error. Most likely, you're running Visual Studio 2015, which doesn't have full .NET Core support. Basically, in previous versions of EF, DbContext had a parameterless constructor, and this version of the scaffold generator is depending on that. In EF Core, DbContext does not have a parameterless constructor, so the generator is choking on that.
If you're using VS2015, upgrade to 2017. It's time. Aside from that, you don't need this anyways, and it's only leading you down a bad path. All the scaffold does is create a new class under Controller, named {Name}Controller that inherits from Controller. Then it creates a folder named {Name} in Views and adds some basic HTML for doing CRUD. You'll end up replacing most of this HTML anyways. Additionally, the scaffold requires you to work with an actual entity class, which is the last thing you should ever be doing. You should always accept user input via a view model and then map that posted data onto your entity class before finally saving the entity. Look at the scaffold being broken as an excellent opportunity to start learning how to create good code.
Here's the solution from Microsoft. It suggest to create a design-time class that instantiates the connection to a database.
A solution
Because DbContext constructor is expecting DbContextOptions, AddDbContext must be set within the Startup Configuration method.
public class MainDbContext : DbContext
{
public MainDbContext(DbContextOptions<MainDbContext> options) : base(options)
{
}
public DbSet<Todo> Todo { get; set; }
}
Within projects startup.cs set AddDbContext
services.AddDbContext<MainDbContext>(o => o.UseSqlServer(#"Data Source=SOURCE;Initial
Catalog=DBCatalog;User ID=ZX;Password=******;Connect
Timeout=30;Encrypt=False;TrustServerCertificate=False;
ApplicationIntent=ReadWrite;MultiSubnetFailover=False"));
ConfigureServices method:
Set database:
UseSqlServer,
UseInMemeoryDatabase,
UseSqlite,
etc...
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MainDbContext>(o => o.UseSqlServer(#"Data Source=SOURCE;Initial
Catalog=DBCatalog;User ID=ZX;Password=******;Connect
Timeout=30;Encrypt=False;TrustServerCertificate=False;
ApplicationIntent=ReadWrite;MultiSubnetFailover=False"));
}
Make sure your project builds and runs without errors before scaffolding.
In Visual Studio 2019, I received this error while attempting to scaffold a new controller because I had a missing comma in my JSON in appsettings.json file.
Eventually I built and tried to run and got a System.FormatException, "Could not parse the JSON file" during runtime.
Since appsettings.json was the only JSON file I was editing recently I knew it had to be appsettings.json.
Scaffolding, code generators, and EF migrations invoke runtime code, this means even if your code compiles, if it throws runtime errors those could cause a problem for such actions.
FYI -
As of EF Core 2.1 parameterized constructors are allowed.
See this Microsoft article for more information.
https://learn.microsoft.com/en-us/ef/core/modeling/constructors
the solution is check the file Startup.cs if you have in the void ConfigureServices the DataContext, for example in SQLServer my Startup.cs is
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<YourDataContext>(options => options.UseSqlServer(Configuration.GetConnectionString("YourConnectionStrings")));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
});
}
}
if you not have this services the error is
no parameterless constructor defined for type YourDataContextName
I had the same problem and I add this line to Startup.cs on the ConfigureServices method.
It worked fine for me:
services.AddControllersWithViews();
Just add an empty constructor to your dbcontext and this solves the problem.

Attribute routing is failing for MVC/WebApi combo project

I am trying to create an ASP.NET app that is both MVC and Web Api. The default controller (HomeController) returns a view that is composed of some HTML and jQuery. I would like to use the jQuery to call the API that is part of the same project.
I have the API setup and have been testing it with Postman but I get the following error when trying to reach the endpoints in the API.
{
"Message": "No HTTP resource was found that matches the request URI 'http://localhost:19925/api/encryption/encrypt'.",
"MessageDetail": "No action was found on the controller 'Values' that matches the request."
}
I am attempting to use attribute routing so I am pretty sure that is where I am going wrong.
[RoutePrefix("api/encryption")]
public class ValuesController : ApiController
{
[HttpPost]
[Route("encrypt")]
public IHttpActionResult EncryptText(string plainText, string keyPassPhrase)
{
// Method details here
return Ok(cipherText);
}
}
I have the route prefix set to api/encryption. I also have the method using the route encrypt and marked as a HttpPost. Below is my WebApiConfig which I think is configured properly for attribute routing.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
// Default MVC routing
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
By my understanding a POST to the following URL should reach the method ..
http://localhost:19925/api/encryption/encrypt
yet it isn't. I am posting the two string values to the method via Postman. I have attached a screen capture (and yes the keyPassPhrase is fake).
Here is the global.asax as requested ...
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
One other thing to note ... that when I change from GET to POST in Postman it works .. as long as I am sending the parameters along in the query string. If I send the parameters in the body I get the original error.
The problem was that I was trying to POST two values to an API method that accepted two parameters. This is not possible with the API (well not without some work arounds) as the API method is expecting an object rather than two different primitive types (i.e. String).
This means on the server side I needed to create a simple class that held the values I wanted to pass. For example ...
public class EncryptionPayload
{
public string PlainText { get; set; }
public string PassPhrase { get; set; }
}
I then modified my API method to accept a type of this class
[Route("encrypt")]
[HttpPost]
public IHttpActionResult EncryptText(EncryptionPayload payload)
{
string plainText = payload.PlainText;
string passPhrase = payload.PassPhrase
// Do encryption stuff here
return Ok(cipherText);
}
Then inside that controller I pulled the Strings I needed from the EncryptionPayload class instance. On the client side I needed to send my data as a JSON string like this ..
{"plainText":"this is some plain text","passPhrase":"abcdefghijklmnopqrstuvwxyz"}
After changing these things everything worked in Postman. In the end I wasn't taking into account Model Binding, thinking instead that an API endpoint that accepted POST could accept multiple primitive values.
This post from Rick Strahl helped me figure it out. This page from Microsoft on Parameter Binding also explains it by saying At most one parameter is allowed to read from the message body.
Try the following code. It will work :
[RoutePrefix("api/encryption")]
public class ValuesController : ApiController
{
[Route("encrypt"),HttpPost]
public IHttpActionResult EncryptText(string plainText, string keyPassPhrase)
{
// Method details here
return Ok(cipherText);
}
}
Sorry dear it was really compile time error. I edit my code. Please copy it and paste it in yourcode. Mark as answer If i Helped.

How do I specify multiple parameters in Asp.Net MVC 5 Route attributes?

I'm using the Kendo AutoComplete client javascript widget, which sends server requests such as the following:
https://domainName/Proto2/api/Goal/Lookup?text=ABC&goalId=8b625c56-7b04-4281-936f-b88d7ca27d76&filter%5Blogic%5D=and&filter%5Bfilters%5D%5B0%5D%5Bvalue%5D=&filter%5Bfilters%5D%5B0%5D%5Boperator%5D=contains&filter%5Bfilters%5D%5B0%5D%5Bfield%5D=Description&filter%5Bfilters%5D%5B0%5D%5BignoreCase%5D=true&_=1423833493290
The MVC server side method to receive this is:
[Route("api/Goal/Lookup")]
[HttpGet] // if the action name doesn't start with "Get", then we need to specify this attribute
public ICollection<IAllegroGoalContract> Lookup(Guid goalId, string text = "")
The problem occurs if the client sends an empty value for the text parameter (ex: text=&goalId=8b625c56-7b04-4281-936f-b88d7ca27d76). In this case .net returns the following error.
"System error - unable to process parameters
(goalId,text,text.String) - invalid data detected"
I've tried various Route attribute values:
[Route("api/Goal/Lookup/{goalId:guid},{text?}")]
[Route("api/Goal/Lookup/{text?}")]
Looks like your parameters are used as a filter, so instead of the GoalId and Text parameters to be part of the route, define a class like this:
public class LookupOptions
{
public Guid GoalId { get; set; } // change this to Guid? if the client can send a nullable goalId.
public string Text { get; set; }
}
So your method signature will be :
[Route("api/Goal/Lookup")]
[HttpGet]
public ICollection<IAllegroGoalContract> Lookup([FromUri]LookupOptions options)
{
// Note that [FromUri] will allow the mapping of the querystring into LookupOptions class.
}
Now, you can pass your options from the client as part of the Query string and it will be assigned to the LookupOptions parameter.
Hope this helps.

Custom validation of HttpPostedFileBase

I use MVC5. I've got some issue with file uploading using HttpPostedFileBase. I've got a form where I can can choose a file from my disk and type some information about it(in textbox). When I submit a form the controller action is called. In this action I open file and check if it has some specific data(related with data from textbox). So I do some validation here. I can't do it using JQuery - it's complex. The server side validation is the only option. Finally if validation fails I return model(with file) to the view but after that I've got validation error next to file field but file field is empty. I've read that's hard to return file to the view. I don't want to use ajax to upload file. I want to do it simple. If you got an article that can help, please share it with me.
How can I solve my problem?
I know you mentioned not using AJAX to do file upload, but I think this solution is a very simple one.
Using the following jQuery plugin (https://blueimp.github.io/jQuery-File-Upload/), you can automate that process and if there are any validation issues in your file, then you can return the following model with the error.
string errors = "Errors returned from complex logic";
if (!String.IsNullOrEmpty(errors))
{
// error response
status = new ViewDataUploadFilesResult()
{
name = Path.GetFileName(hpf.FileName),
size = hpf.ContentLength,
error = errors
};
}
Here is the class needed for the response that matches the jQuery file upload documentation: https://github.com/blueimp/jQuery-File-Upload/wiki/Setup
public class ViewDataUploadFilesResult
{
public string name { get; set; }
public int size { get; set; }
public string type { get; set; }
public string url { get; set; }
public string error { get; set; }
}
If I'm understanding correctly, since the file is already on the users computer, you only need to associate the file to the current file they're attempting to upload to returns errors. And to make it so they don't have to reselect the file to upload. I don't see any other reason to need to return the actual file to the user as they already have the file they're uploading.

Resources