I want to populate a viewmodel based on both Body content and Header content, I've tried to decorate with both attributes but only one or the other seems to be running and populating the value.
Moving the FromBody to the property causes the header value to be populated, having the FromBody in the param declaration on the controller causes the body Id to be populated.
Subsequently running TryUpdateModelAsync in the controller does populate both but this seems ugly and over the top.
Anyone have any ideas how to get this working?
public IActionResult GetAddress([FromBody]AddressDataViewModel model)
{
if (!this.ModelState.IsValid)
{
return this.BadRequest(this.ModelState);
}
return this.Ok(this.helper.GetAddress(model.Id));
}
public class AddressDataViewModel : BaseHttpRequestMeta
{
[Required]
public string Id { get; set; }
}
public class BaseHttpRequestMeta
{
[BindRequired]
[FromHeader(Name = "sessionid")]
public string SessionId { get; set; }
}
You could try and make your own binding source. Haven't tested this, but something like a hybrid binding source:
public class FromHeaderOrBodyAttribute : Attribute, IBindingSourceMetadata
{
public BindingSource BindingSource => new HeaderOrBodyBindingSource();
}
public class HeaderOrBindingSource : BindingSource
{
public HeaderOrBindingSource() : base("HeaderOrBody", "Header or Body binding source", true, true)
{
}
public override bool CanAcceptDataFrom(BindingSource bindingSource)
{
return bindingSource == BindingSource.Header
|| bindingSource == BindingSource.Body;
}
}
Then use the attribute on your action:
public IActionResult GetAddress([FromHeaderOrBody]AddressDataViewModel model)
{
}
Again, haven't tested this, but I thought the code was a bit too much just for a comment. Please respond with your results.
Related
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);
}
As title says
TypeScript model
export interface RigheOrdiniBuoniSpesaData {
id: number;
id_ordine: number;
id_taglio_buono_spesa: number;
quantita: number;
}
which is part of another bigger object:
export class OrdiniBuoniSpesaData {
id: number;
// OTHER FIELD
// OTHER FIELD
// OTHER FIELD
righe_ordine: RigheOrdiniBuoniSpesaTableData;
}
Save method
saveOrder(model: OrdiniBuoniSpesaData) {
const headerPost: HttpHeaders = new HttpHeaders();
headerPost.set('Content-type', 'application/x-www-form-urlencoded');
const formData: FormData = new FormData();
formData.append('id_cliente', model.id_cliente.toString());
// VARIOUS FORM FIELDS
//THIS IS ARRAY DATA
formData.append('righe_ordine', JSON.stringify(model.righe_ordine));
return this.http
.post<boolean>(
requestURL,
formData,
{ headers: headerPost }
)
.pipe(
catchError(this.handleError)
);
}
Order json (valid Json) is visible in Chrome request capture clearly along with all data:
[{"id":0,"id_ordine":0,"id_taglio_buono_spesa":1,"quantita":1},{"id":0,"id_ordine":0,"id_taglio_buono_spesa":1,"quantita":1},{"id":0,"id_ordine":0,"id_taglio_buono_spesa":1,"quantita":1},{"id":0,"id_ordine":0,"id_taglio_buono_spesa":3,"quantita":14}]
On API Side
Receiving model for JSON
public class RigheOrdiniBuoniSpesaViewModel
{
public long id { get; set; }
public long id_ordine { get; set; }
public long id_taglio_buono_spesa { get; set; }
public int quantita { get; set; }
}
Which is in
public class OrdiniBuoniSpesaViewModel
{
public long id { get; set; }
//OTHER VARIOUS FIELDS
//I TRIED ALSO WITH LIST INSTEAD OF IENUMERABLE
public IEnumerable<RigheOrdiniBuoniSpesaViewModel> righe_ordine {get;set;}
}
(I TRIED ALSO WITH LIST INSTEAD OF IENUMERABLE, STILL NO LUCK!)
Api controller signature:
[HttpPost]
public async Task<IActionResult> PostSaveOrder([FromForm] OrdiniBuoniSpesaViewModel model)
{.....code}
All the fields are binded correctly except for the righe_ordine array!
I can see every field correctly but the array has count = 0.
Strangely enough, if I examine the asp net request object (this.Request.Form) in the QuickWatch debug in visual studio:
this.Request.Form["righe_ordine"]
{[{"id":0,"id_ordine":0,"id_taglio_buono_spesa":1,"quantita":1},
{"id":0,"id_ordine":0,"id_taglio_buono_spesa":1,"quantita":1},
{"id":0,"id_ordine":0,"id_taglio_buono_spesa":1,"quantita":1},
{"id":0,"id_ordine":0,"id_taglio_buono_spesa":3,"quantita":14}]}
Microsoft.Extensions.Primitives.StringValues
is present and correctly populated..... but for some reason binding it to the OrdiniBuoniSpesaViewModel fails....
What am I doing wrong? Any idea?
EDIT:
For the moment the only solution I found is to directly catch the value just after entering the controller:
string righeJson = this.Request.Form["righe_ordine"].ToString();
if(!string.IsNullOrEmpty(righeJson))
model.righe_ordine = JsonConvert.DeserializeObject<IEnumerable<RigheOrdiniBuoniSpesaViewModel>>(righeJson);
I think you need to change your model name like this
In your js code
formData.append('righeOrdine', JSON.stringify(model.righe_ordine));
and c#
public class OrdiniBuoniSpesaViewModel
{
public long id { get; set; }
//OTHER VARIOUS FIELDS
//I TRIED ALSO WITH LIST INSTEAD OF IENUMERABLE
public IEnumerable<RigheOrdiniBuoniSpesaViewModel> RigheOrdine {get;set;}
}
Update: Just for enhancement
Maybe you need to parse it before using it in your controller
You can read my full code here
We will need to create interface and class to implement it.
public interface IJsonParseService<T> where T : class
{
T ToObject(string json);
}
public class JsonParseService<T> : IJsonParseService<T> where T : class
{
public T ToObject(string json)
{
return JObject.Parse(json).Root.ToObject<T>();
}
}
Then register it
services.AddScoped(typeof(IJsonParseService<>), typeof(JsonParseService<>));
So your code should be something like this
private readonly IJsonParseService<RigheOrdiniBuoniSpesaViewModel> _jsonParsePostOptionDefaultVm;
jsonParsePostOptionDefaultVm.ToObject(viewModel.RigheOrdiniBuoniSpesaViewModel);
I have two controllers(controllerA.actionA & controllerB.actionB) which share the same view & ViewModel(ViewModel_Shared).
So, in both Controller, I use RedirectToAction to redirect to a third controller(controllerC.actionC) which points to the shared view.
All three Actions are in different Controller.
When ActionA or ActionB was invoked All the parameters that post from view was sent to the modelC in actionC successfully.
However, when I try to feed data into the object(Item), NullReference exception was thrown. the object(Item) was NULL.
But I'm sure the constructor of ViewModel_Shared was hit TWICE while ActionA was called and ActionC was called.
So, basically, object(Item) was NEW twice.
I don't really understand why it's like that.
thank you all in advance!!!
public ActionResult actionA (ViewModel_Shared modelA)
{
return RedirectToAction("actionC", "controllerC", modelA);
}
public ActionResult actionB (ViewModel_Shared modelB)
{
return RedirectToAction("actionC", "controllerC", modelB);
}
public ActionResult actionC (ViewModel_Shared modelC)
{
modelC.FillEditedData();
// Send the data to view
ViewData.Model = modelC;
return View();
}
public class ViewModel_Shared
{
public ItemCustomer Item { get; set; }
public ViewModel_Shared()
{
Item = new ItemCustomer();
}
public void FillEditedData()
{
// NullReference exception was throw here, somehow, Item is null.
Item.LegalCost = "some value";
}
}
[Serializable]
public class ItemCustomer
{
public string Item_No { get; set; }
public string MaterialPricingGroup { get; set; }
public string MaterialGroup { get; set; }
public string Plant { get; set; }
public string LegalCost { get; set; }
}
As mentioned, complex objects cannot travel with requests.
Instead you need to serialize the object and pass the serialized data to the destination action method:
public ActionResult actionA (ViewModel_Shared modelA)
{
var serializedModel = JsonConvert.SerializeObject(modelA);
return RedirectToAction("actionC", "controllerC", serializedModel);
}
UPDATE
My bad. Indeed the parameters, sent through RedirectToAction() are sent as route values so even serialization cannot help here. At least not by itself - the routing must be setup appropriately ...
A different approach must be taken and during actionA, place your modelA into the TempData, then do redirect. In the actionC retrieve the model and carry on.
public ActionResult actionA (ViewModel_Shared modelA)
{
TempData["shared_model"] = modelA;
return RedirectToAction("actionC", "controllerC");
}
Later, in actionC:
public ActionResult actionC (ViewModel_Shared modelA)
{
var modelC = TempData["shared_model"];
modelC.FillEditedData();
ViewBag.Model = modelC;
return View();
}
Do note, that the objects in he TempData are alive only to the next request and then gone. Here you can read more about the TempData.
Yin
1) if you are using the ItemCustomer class as a property you should initialize it when you are using.
public ItemCustomer Item { get; set; }
2) if you are using ItemCustomer as object it will initialize when you constructor call as your example.
public ItemCustomer Item;
I have run the second point without get and set it's working fine.
I'm developping an application based on asp.NET MVC 5
This Application downloads a big deal of data from a local webservice and should display it.
Because it is much data, I call the webservice through a partial view, like this:
<div class="partialContents" id="partialContent" data-url="/Home/Action">
Loading...
</div>
This is loaded asynchronous with the following JS-Code:
$(document).ready(function (e) {
$(".partialContents").each(function (index, item) {
var url = $(item).data("url");
if (url && url.length > 0) {
$(item).load(url);
}
});
});
This works like intended.
But now I have to pass the values of a form to this partial view.
I know I can pass arguments as usual, like a request to /Home/Action/Params but I have to pass many arguments of which some might not be set or empty or null.
Because of this, I looked for a possibility to pass a object or a value of the ViewBag or something like this to the partial view.
Is there a possibility to pass a Model-Object to the Controller in my code?
The data has to go to the controller for further validation and so on. I know I can pass a model to the view itself, but I need those data in my controller.
My controller is accessed as following:
public ActionResult Index()
{
//Do things
return View(model);
}
public ActionResult Action()
{
//Validate the Form-Data
//Download Data from Webservice
return PartialView("MyPartialView", model);
}
Any help or tipps would be greatly appreciated.
Maybe I have to edit the Javascript-Code, but I don't usually code in JS, so I have the code from following source (followed his tutorial): blog.michaelckennedy.net
//Additional Info
My ViewModel looks like this:
public class PostForm
{
public string DatumVon { get; set; }
public string DatumBis { get; set; }
public string Werk { get; set; }
public string Pruefplatz { get; set; }
public string Auftrag { get; set; }
public string Gueltig { get; set; }
}
and my Java-Request like this:
$(item).load(url, {"DatumVon":dateVon, "DatumBis":dateBis, "Werk":werk, "Pruefplatz":pruefplatz, "Auftrag":auftrag, "Gueltig":gueltig});
If you are having a lot of parameters to pass to the action method, create a model which encompasses these parameters. For example, I have created MyParamModel class here below:
public class MyParamModel
{
//Your parameters go here
public int IntProperty { get; set; }
public string StringProperty { get; set; }
}
Then modify the action method to accept this class as parameter.
public ActionResult Action(MyParamModel model)
Modify the load call in the script to pass information to the url like this.
$(item).load(url, {"IntProperty":1, "StringProperty": "test"});
Once you have the model on the controller end, you can validate it accordingly.
Hope this helps. If it does, mark this as answer.
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.
}