dotnet core get dictionairy from form data - c#

How do I get a Dictionairy<string, string> from form-data?
View model:
public class SubmitRequestModel
{
public Dictionairy<string, string> InputFields { get; set; }
public List<IFormFile> Attachments { get; set; }
}
Action:
[HttpPut("{id:int}")]
public async Task<IActionResult> Submit(int id, [FromForm] SubmitRequestModel model)
{
// model.InputFields is initialized but it's count is 0
// do whatever
}
This is an API controller. Not cshtml or razor related.
So the model.InputFields is not null but it's count is 0. When I look at the raw request. I can see that the input is received, but it is not bound to the dictionairy in the model.
The values of the Request.Form collection: https://prnt.sc/11x532p
I need to use form data because we are uploading files with the request. This requires multipart/form-data.
How do I successfully parse the data to the model?
Info on how I tested this:
I have swagger set-up. I import the generated Swagger OpenAPI json in to Postman and test this way. I believe that this should be the correct request, that Swagger generated. But I'm not sure if it formatted the dictionairy the right way. It would be the right way for a JSON data request. But I'm not sure if that applicates here.

You can achieve this with custom IModelBinder:
public class DictionaryBinder<TKey, TValue> : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
if (bindingContext.HttpContext.Request.HasFormContentType)
{
var form = bindingContext.HttpContext.Request.Form;
var data = JsonSerializer.Deserialize<Dictionary<TKey, TValue>>(form[bindingContext.FieldName]);
bindingContext.Result = ModelBindingResult.Success(data);
}
return Task.CompletedTask;
}
}
The model class will become:
public class SubmitRequestModel
{
[FromForm]
[ModelBinder(BinderType = typeof(DictionaryBinder<string, string>))]
public Dictionary<string, string> InputFields { get; set; }
public List<IFormFile> Attachments { get; set; }
}

I think your problem is the same as the given link, just go through the link it may help you.
How to bind form inputs to dictionary values
or
Bind dynamic form data

So one solution, which is not my favourite but it works, is to parse the dictionairy manually.
First thing in the action:
if (model.InputFields == null || model.InputFields.Count == 0)
{
var rawInputFields = Request.Form["InputFields"].ToString();
model.InputFields = JsonSerializer.Deserialize<Dictionary<string, string>>(rawInputFields);
}

Related

How to Send Object Values in PostMan From FromData Calling a Web Api .net Method

I have a post Api method in my web api net core application thats has a Dto Class with the follow parameters:
public class ClassDto
{
public int Id { get; set; }
public Comunicacao Comunicacao { get; set; }
}
Comunicacao Class:
public class Comunicacao
{
public int Id { get; set; }
public string Name { get; set; }
}
Api Action(route has been setted correctly):
[HttpPost]
public async Task<IActionResult> Add([FromForm]ClassDto bannerDto, IFormFile imgDesktop)
{
try
{
if (ModelState.IsValid)
{
var result = await _banner.Add(classDto, imgDesktop);
return Ok(new { message = result.messageReturning, result.classDto });
}
else
{
return BadRequest(ModelState);
}
}
catch (Exception ex)
{
return BadRequest(ex.ToLogString(Environment.StackTrace));
}
}
So my question is, how can i send in postman using FormData passing Comunicacao Object Values? Because when i sent "Id", it works fine, but i cant find a way to send objects!
what i have tried yet
I cannot use FromBody because as you all can see im sending a File as well.
Finally got it.
Have to use the object.property!
put the iformfile object inside your dto making it a single dto method then ur endpoint will look like public async Task Add([FromForm]ClassDto bannerDto)
if you have put the iformfile outside cos of automapper then u can use [ignore] attribute over the property
sorry wanted to put this as comment for the previous answer but i am typing from mobile phone so ...meh
you can set body type to form-data and then in key value pair side just select file instead of text for your file upload

ASP.NET Core Pass Enumerable of objects to Get Action on Controller

I am trying to define a Controller Action in ASP.NET Core 2.2.
The tricky part is that I prefer this to be a GET endpoint, and the data that it must recieve is a collection of custom objects. Here is my sample code:
[Route("api/example")]
[ApiController]
public class ExampleController : ControllerBase
{
[HttpGet("getData")]
[ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
public async Task<IActionResult> GetMarketData([FromQuery] MyObject[] queryData)
{
return this.Ok(0);
}
}
public class MyObject
{
public int A { get; set; }
public int B { get; set; }
}
I am expecting this to bind to something like
http://localhost/api/example/getData/?queryData=[A=1,B=1],queryData=[A=2,B=2]
However I can't seem to get it to work.
(Sending a request to the URL, does not parse the objects, and I receive an empty array in my controller)
I'm not sure if this is the best way to approach this, and maybe I need to change the place where I bind the data from?
The only thing I care about is being able to recieve an array (or some kind of a collection) of MyObject that I can process and return a response. I would also prefer for this to be a GET request, as, after all, I am trying to query this API and get data from it.
I know I can get it to work with using a [FromBody] attribute, but as far as I know GET requests should not use the body.
Any help is gladly appreciated.
Your GET request must be constructed as follows:
GET: /api/example/getData?queryData[0].A=1&queryData[0].B=2&queryData[1].A=3
Very similar to model binding when using <form>s :)
Your QueryString should look like:
/TestMe?queryData[0].A=1&queryData[0].B=1&queryData[1].A=2&queryData[1].B=2
If your code looks like:
public class MyObject
{
public int A { get; set; }
public int B { get; set; }
}
[Route("/TestMe")]
public IActionResult TestMe([FromQuery] MyObject[] queryData)
{
return Json(queryData);
}
Note that [FromQuery] isn't even required.
it's not going to work since there's not a default binder for collection types, you'd have to use a custom binder. I made my own implementation of a generic array model binder, here it goes:
// generic array model binder
public class ArrayModelBinder<TType> : IModelBinder {
public Task BindModelAsync(ModelBindingContext bindingContext) {
if (bindingContext.ModelMetadata.IsEnumerableType) {
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).ToString();
if (!string.IsNullOrEmpty(value)) {
var elementType = typeof(TType);
var typeConverter = TypeDescriptor.GetConverter(elementType);
var splittedValues = value.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);
var values = splittedValues.Select(t => typeConverter.ConvertFromString(t.Trim())).ToArray();
var typedValues = Array.CreateInstance(elementType, values.Length);
values.CopyTo(typedValues, 0);
bindingContext.Model = typedValues;
return SuccessBinding(bindingContext, typedValues);
}
return SuccessBinding(bindingContext, null);
}
return FailedBinding(bindingContext);
}
private static Task SuccessBinding(ModelBindingContext bindingContext, Array typedValues) {
bindingContext.Result = ModelBindingResult.Success(typedValues);
return Task.CompletedTask;
}
private static Task FailedBinding(ModelBindingContext bindingContext) {
bindingContext.Result = ModelBindingResult.Failed();
return Task.CompletedTask;
}
To use it on your Action you'll just have to use this piece of code:
public async Task<IActionResult> GetMarketData([ModelBinder(BinderType = typeof(ArrayModelBinder<object>))] MyObject[] queryData)
{
return this.Ok(0);
}
I have the source of this implementation and other things in a repo of my own Library feel free to check it out CcLibrary.AspNetCore

WebAPI post JSON string and map it to model

I must create webhook endpoint that will consume JSON messages.
Messages is send as x-www-form-urlencoded in form:
key = json
value = {"user_Id": "728409840", "call_id": "1114330","answered_time": "2015-04-16 15:37:47"}
as shown in PostMan:
request looks like this:
json=%7B%22user_Id%22%3A+%22728409840%22%2C+%22call_id%22%3A+%221114330%22%2C%22answered_time%22%3A+%222015-04-16+15%3A37%3A47%22%7D
To get values from request as my class (model) I must create temporary object containing single string property:
public class Tmp
{
public string json { get; set; }
}
and method inside my controller that consumes that request:
[AllowAnonymous]
[Route("save_data")]
[HttpPost]
public IHttpActionResult SaveData(Tmp tmp)
{
JObject json2 = JObject.Parse(tmp.json);
var details = json2.ToObject<CallDetails>();
Debug.WriteLine(details);
//data processing
return Content(HttpStatusCode.OK, "OK", new TextMediaTypeFormatter(), "text/plain");
}
As You can see Tmp class is useless.
Is there a way to get request data as this class:
public class CallDetails
{
public string UserId { get; set; }
public string CallId { get; set; }
public string AnsweredTime { get; set; }
}
I'm aware of IModelBinder class, but before I start I'd like to know if there is an easier way.
I can't change web-request format, by format I mean that is will always be POST containing single key - JSON yhat has json string as value.
You can use JsonProperty attribute for mapping json object properties to c# object properties:
public class CallDetails
{
[JsonProperty("user_id")]
public string UserId { get; set; }
[JsonProperty("call_id")]
public string CallId { get; set; }
[JsonProperty("answered_time")]
public string AnsweredTime { get; set; }
}
Then it can be used without temp class:
[AllowAnonymous]
[Route("save_data")]
[HttpPost]
public IHttpActionResult SaveData(CallDetails callDetails)
Update. Because the data is sent as x-www-form-urlencoded - I think the way you handled it is most straightforward and not so bad. If you want to check another options here're some of them:
Option 1 - custom model binder. Something like this:
public class CustomModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
var body = actionContext.Request.Content.ReadAsStringAsync().Result;
body = body.Replace("json=", "");
var json = HttpUtility.UrlDecode(body);
bindingContext.Model = JsonConvert.DeserializeObject<CallDetails>(json);
return true;
}
}
And usage: SaveData([ModelBinder(typeof(CustomModelBinder))]CallDetails callDetails). Downside - you'll lose validation and maybe other stuff defined in web api pipeline.
Option 2 - DelegatingHandler
public class NormalizeHandler : DelegatingHandler
{
public NormalizeHandler(HttpConfiguration httpConfiguration)
{
InnerHandler = new HttpControllerDispatcher(httpConfiguration);
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var source = await request.Content.ReadAsStringAsync();
source = source.Replace("json=", "");
source = HttpUtility.UrlDecode(source);
request.Content = new StringContent(source, Encoding.UTF8, "application/json");
return await base.SendAsync(request, cancellationToken);
}
}
Usage:
[AllowAnonymous]
[HttpPost]
public IHttpActionResult SaveData(CallDetails callDetails)
Downside - you'll need to define custom route for it:
config.Routes.MapHttpRoute(
name: "save_data",
routeTemplate: "save_data",
defaults: new { controller = "YourController", action = "SaveData" },
constraints: null,
handler: new NormalizeHandler(config)
);
You don´t forget to decode the url encoded before use JObject.Parse ?, it´s maybe works. And the properties of the object don´t match the json atributes
Json.NET by NewtonSoft can help you deserialize an object. If your json property names don't match your actual class names you can write a custom converter to help.
EDIT
You could try this if you are on MVC6. Change your parameter from type Tmp to type CallDetails and mark it with attribute [FromBody], like this:
public IHttpActionResult SaveData([FromBody]CallDetails details)
For example look at the section "Different model binding" in this post. However, I'm still thinking that you will need to deserialize manually because the property names of class CallDetails don't exactly match the incoming JSON properties.

JSON.Net in Api controllers w/ param containing Dictionary is always null

I've seen some tutorials out there that claim to work, but they are outdated or simply do not work.
How can I use JSON.Net to serialize and deserialize the data received to and sent from my API controllers?
We are using VS2012.
Update
I have a model like this
public class SearchModel
{
public int PageIndex { get; set; }
public int PageSize { get; set; }
public Dictionary<string, object> Terms { get; set; }
}
And an Api controller like this
public class ModelSearchApiController : ApiController
{
public List<Model> Get([FromUri] SearchModel search)
{
return new List<Model>();
}
}
However, search provides the correct value set in the Ajax request, the property Terms is always an empty dictionary.
I know we can provide a value like [ { Key:"foo", Value:123 } ] but why can't I just pass a normal JSON object (ie { foo:123 }) ??? Why can it serialize a Dictionary into a nice standard JSON object, but cannot take that exact same object and recreate a Dictionary. This is beyound me.
Edit
In other words, if the browser sends these arguments :
pageIndex: 0
pageSize: 100
terms[foo]: Bar
terms[buz]: 1234
What would be the required object signature? Because the object mentionned above does not work and the dictionary is just empty.
JSON.NET is the default serializer for ASP.NET Web API - it can convert between JSON and CLR objects, and does so for all JSON input. However, you're not trying to convert a JSON input to your SearchModel - you're trying to convert from the URI-based format which is similar to application/x-www-form-urlencoded, into the CLR type SearchModel, and that is not supported by JSON.NET (it's not JSON!). In general, the serializers are used to convert (on incoming requests) from the request body to the action parameter.
Let's look at this (complete) example below (assuming the default route, to "api/{controller}"). It's very similar to your question, but I also added a Post method in addition to the GET method.
public class ModelSearchApiController : ApiController
{
public List<Model> Get([FromUri] SearchModel search)
{
return new List<Model>
{
new Model { PageIndex = search.PageIndex, PageSize = search.PageSize, Terms = search.Terms }
};
}
public List<Model> Post(SearchModel search)
{
return new List<Model>
{
new Model { PageIndex = search.PageIndex, PageSize = search.PageSize, Terms = search.Terms }
};
}
}
public class Model
{
public int PageIndex { get; set; }
public int PageSize { get; set; }
public Dictionary<string, object> Terms { get; set; }
}
public class SearchModel
{
public int PageIndex { get; set; }
public int PageSize { get; set; }
public Dictionary<string, object> Terms { get; set; }
}
If you send this request to the server:
POST http://localhost:64699/api/ModelSearchApi HTTP/1.1
User-Agent: Fiddler
Host: localhost:64699
Content-Type: application/json
Content-Length: 65
{"PageIndex":1,"PageSize":10,"Terms":{"foo":"bar","foo2":"bar2"}}
It will be bound, as you expect, to the SearchModel parameter - the Terms property will be a dictionary with two entries (foo=bar, foo2=bar2).
Now, for the GET parameter. ASP.NET Web API has a concept of model binders and value provider, which would be the component which would convert between the query string into the action parameters. The default binder / provider do not support the "arbitrary" name/value pair syntax *for dictionary inside complex types. You can, as you pointed out, use the key/value pair syntax, and that will be understood, as shown below.
GET http://localhost:64699/api/ModelSearchApi?PageIndex=1&PageSize=10&Terms[0][key]=foo&Terms[0][value]=bar HTTP/1.1
User-Agent: Fiddler
Host: localhost:64699
Now, for your problem you have two options. You can change your API to use a custom model binder or value provider which knows how to understand the "simple" name/value syntax, as shown below:
public class ModelSearchApiController : ApiController
{
public List<Model> Get([ModelBinder(typeof(MySearchModelBinder))] SearchModel search)
{
return new List<Model>
{
new Model { PageIndex = search.PageIndex, PageSize = search.PageSize, Terms = search.Terms }
};
}
}
public class MySearchModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
SearchModel value = new SearchModel();
value.Terms = new Dictionary<string,object>();
foreach (var queryParams in actionContext.Request.GetQueryNameValuePairs())
{
if (queryParams.Key == "PageIndex")
{
value.PageIndex = int.Parse(queryParams.Value);
}
else if (queryParams.Key == "PageSize")
{
value.PageSize = int.Parse(queryParams.Value);
}
else if (queryParams.Key.StartsWith("Terms."))
{
value.Terms.Add(queryParams.Key.Substring("Terms.".Length), queryParams.Value);
}
}
bindingContext.Model = value;
return true;
}
}
Another option is to pre-process your input data on the client prior to sending to the server, using a function similar to the one below.
function objToKVPArray(obj) {
var result = [];
var k;
for (k in obj) {
if (obj.hasOwnProperty(k)) {
result.push({ key: k, value: obj[k] });
}
}
return result;
}
You can take reference the link below. Hope this help.
And here is sample using Json.net with web API.

ASP.NET MVC3: Displaying Validation Errors from Child View in Parent View

I'm trying to add a form to allow users to comment on posts on my blogging application. So far, I've added a form to the post details view and I can submit comments, adding them to my database correctly. However, I have a problem with displaying validation errors to the user. The comment form is contained within a partial view and is rendered using Html.RenderAction inside the post details view. I'd like to stress that I don't want to use AJAX for this as I'd like to approach this from a progressive enhancement point-of-view.
Here's the relevant posting action:
[HttpPost, Authorize]
public ActionResult AddComment(CommentViewModel newComment)
{
if (ModelState.IsValid)
{
Comment comment = new Comment(_userRepository.GetByUsername(User.Identity.Name));
Mapper.Map(newComment, comment);
_commentRepository.Add(comment);
_postsRepository.CommentAdded(comment.Article);
return RedirectToAction("Index", new { id = newComment.PostID });
}
// What do I do here?
}
I've tried several ways of returning views here but my issue is further complicated by some controller parameter validation that I have going on in the parent action:
//
// GET: /Posts/5/this-is-a-slug
public ActionResult Index(int id, string slug)
{
PostViewModel viewModel = new PostViewModel();
var model = _postsRepository.GetByID(id);
if (model != null)
{
if (slug == null || slug.CompareTo(model.Slug) != 0)
{
return RedirectToActionPermanent("Index", new { id, slug = model.Slug });
}
else
{
_postsRepository.PostVisited(model);
Mapper.Map(model, viewModel);
viewModel.AuthorName = _userRepository.GetById(model.AuthorID);
}
}
return View(viewModel);
}
This action basically mimics how SO's URLs work. If a post ID is supplied, the post is fetched from the database along with a slug which is created when the post is created. If the slug in the URL doesn't match the one in the database, it redirects to include the slug. This is working nicely but it does mean I'm having issues trying to populate my parent viewmodel, which is the following:
public class PostViewModel
{
public int PostID { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public string Slug { get; set; }
public DateTime DatePublished { get; set; }
public int NumberOfComments { get; set; }
public int AuthorID { get; set; }
public string AuthorName { get; set; }
public List<CommentViewModel> Comments { get; set; }
public CommentViewModel NewComment { get; set; }
}
What I was hoping would work is to populate PostViewModel.NewComment, test to see if it has data and then using it to display any model errors. Unfortunately, I'm lost as to how to accomplish that. This question helped me shape my approach, but it didn't quite answer my problem.
Could someone give me a gentle push in the right direction? If my approach seems unreasonable, I'd love to find out why and what a potential fix would be.
Many thanks in advance.
Forgot to fill in my answer here. For anyone that happens to stumble on this, the answer was to use TempData to store the ModelState errors and then repopulating ModelState in the relevant controller action.
Firstly, I declared a key in the controller which would be used to reference the data inside TempData. I decided to base this on the CommentViewModel type as both actions depend on it.
public class PostsController : Controller
{
private static readonly string commentFormModelStateKey = typeof(CommentViewModel).FullName;
// Rest of class.
}
In this first action, the code checks to see if TempData contains data assigned to the key. If it does, it's copied into ModelState.
// GET: /posts/comment
[ChildActionOnly]
public PartialViewResult Comment(PostViewModel viewModel)
{
viewModel.NewComment = new CommentViewModel(viewModel.PostID, viewModel.Slug);
if (TempData.ContainsKey(commentFormModelStateKey))
{
ModelStateDictionary commentModelState = TempData[commentFormModelStateKey] as ModelStateDictionary;
foreach (KeyValuePair<string, ModelState> valuePair in commentModelState)
ModelState.Add(valuePair.Key, valuePair.Value);
}
return PartialView(viewModel.NewComment);
}
This action determines if the ModelState is valid before adding a comment to the database. If the ModelState is not valid, it is copied to TempData, which makes it available to the first action.
// POST: /posts/comment
[HttpPost, Authorize]
public ActionResult Comment(CommentViewModel newComment)
{
if (!ModelState.IsValid)
{
TempData.Add(commentFormModelStateKey, ModelState);
return Redirect(Url.ShowPost(newComment.PostID, newComment.Slug));
}
// Code to add a comment goes here.
}

Categories

Resources