I have a view model and models similar to this
public class SupplierViewModel
{
public Supplier Supplier { get; set; }
//Select Lists and other non model properties
}
And two models
public class Supplier
{
public string Name { get; set; }
public Contact PrimaryContact { get; set; }
public List<Contact> SecondaryContacts { get; set; }
}
public class Contact
{
public string Name { get; set; }
}
But in my view the fields get prefixed with the class name so when I send it to the Web API controller it is in the following form
{
Supplier.Name: "test",
Supplier.PrimaryContact.Name: "test",
Supplier.SecondaryContacts: [
{ Name: "test" }
]
}
When I send it to my controller
[System.Web.Http.Route("Suppliers/{idSupplier?}")]
public HttpResponseMessage SuppliersAddOrEdit(Supplier Supplier, int idSupplier = 0)
It obviously doesn't deserialize because of the prefixes, currently I'm reformatting it before I send the request like this
{
Name: "test",
PrimaryContact: {Name: "test"},
SecondaryContacts: [
{
Name: "test"
}
]
}
then it binds OK, but I'm pretty sure that when I was sending data to an ActionController it knew even without specifying Bind[(Prefix)] that for example
PrimaryContact.Name: "test"
Would go into the class PrimaryContact. How do I achieve the same result in a Web API controller?
Edit: Based on Jon Susiak's answer I'd like to clarify further
If instead I use a Controller instead of a ApiController my model as it is would bind just fine sending the data in JSON with prefixes, is there a way to achieve the same thing in an ApiController?
In your view you are initially sending a SupplierViewModel however on the POST of the form you are expecting a Supplier object.
You can do one of two things:
a) Change the POST model to SupplierViewModel
b) Change the initial model to Supplier and put the additional properties and Lists in the ViewBag
Related
I've an ASP.Net MVC 4 application that I'm porting to ASP.Net Core 3.0 MVC.
I'm trying to port this method
[HttpPost]
public ActionResult DoSave(
[Bind(Prefix = "new")]IEnumerable<C_Data> newItems,
[Bind(Prefix = "updated")]IEnumerable<C_Data> updatedItems,
[Bind(Prefix = "deleted")]IEnumerable<C_Data> deletedItems))
{
}
In the post AJAX (in JavaScript from the web browser) I'm sending the values as JSON like this
{
"new[0].Id":3,
"new[0].SID":"00000000-0000-0000-0000-000000000000",
"new[0].Name":"asd"
}
Here's the C_Data class
public class C_Data
{
[Key]
public int Id { get; set; }
public Guid SID { get; set; }
[Required]
[MaxLength(40)]
public string Name { get; set; }
}
But the three parameters are empty when this action is executed.
Here's the error I get in the ModelState
"The JSON value could not be converted to C_Data"
Anyone please can tell me how to port this method?
Thank you.
PD: This action is in an MVC controller not an API controller.
Here's a link that should help.
It looks like you should be able to use C_Data object, put it in an array, and stringify it in the AJAX call, receive an IEnumerable.
For Asp.Net Core, there are two ways to bind the model, ModelBinding and JsonInputFormatter. For sending request with json, it will use JsonInputFormatter and Bind will not work.
In general, I would suggest you try option below:
Controller Action
[HttpPost]
public ActionResult DoSave([FromBody]ItemModel itemModel)
{
return Ok("Worked");
}
Model
public class ItemModel
{
public IEnumerable<C_Data> NewItems { get; set; }
public IEnumerable<C_Data> UpdatedItems { get; set; }
public IEnumerable<C_Data> DeletedItems { get; set; }
}
Request Json
{
"newItems":[{
"Id":3,
"SID":"00000000-0000-0000-0000-000000000000",
"Name":"asd"
}]
}
As a follow on from my previous question (MVC abstract ViewModel, retain Validation Attributes (dynamically)) I thought I'd ask the question in an alternate version.
So, let's consider the same situation, where I have a core ViewModel:
public class SampleViewModel {
[Required]
public string Property1 { get; set; }
[Required]
[EmailAddress]
public string Property2 { get; set; }
public IList<AnotherModel> Items { get; set; }
}
And another model:
public AnotherModel {
public string Value { get; set; }
}
And then within a controller, I perform the following:
var model = new SampleViewModel();
var fields = new List<AnotherModel>() {
new AnotherModel() { Value = model.Property1 },
new AnotherModel() { Value = model.Property2 },
};
So, my question is, how can I get the AnotherModel models to respond to the properties that are passed to their respective Value property.
In the sample above, the first AnotherModel will be Required, and the second will be Required, and an EmailAddress.
How is this possible?
Thank you
Update
For the purpose of this, lets say that each of those AnotherModel objects is represented by a form field. When the form is posted back, I use a custom model binder to obtain the Value from the AnotherModel and place it back into the source property (so Property1). My model is reconstructed correctly, and ModelState.IsValid is working. So, I have server-side validation of my SampleViewModel on post-back. Can this be somehow passed to the client to validate for me, based on the model's validation attributes?
Thanks
I do not want do bind the Id property on my CustomerViewModel so I added a [BindNever] attribute but it is not working. What could be the solution?
I have the following:
CustomerController.cs
// PUT api/customers/5
[HttpPut("{id}")]
public async Task<IActionResult> Put([FromUri] int id, [FromBody]CustomerViewModel customer)
{
//Implementation
}
CustomerViewModel
public class CustomerViewModel
{
[BindNever]
public int Id { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string Email { get; set; }
}
If I input the following json . The id property still gets binded
{
"id": 100,
"lastName": "Bruce",
"firstName": "Wayne",
"email": "bruce#gothamcity.com"
}
This Blog post is an interesting read and concludes that the [FromBody] annotation "overrides" the BindBehaviourAttribute (BindNever is a simple specialization). The model is populated by all data available from the body (your JSON data in this case).
I do not consider this as intuitive, and the issue has a nice statement about this:
[BindRequired] customizes the MVC model binding system . That's its
purpose and it's working as designed.
[FromBody] switches the affected property or parameter into the
different world of input formatting. Each input formatter (e.g.
Json.NET and a small MVC-specific wrapper) can be considered a
separate system with its own customization. The model binding system
has no knowledge the details of JSON (or any other) deserialization.
Lesson learned: BindNever does not work in this scenario.
What are alternatives ?
Solution 1: Writing some custom model binding code. I have not done it myself, but What is the correct way to create custom model binders in MVC6? may help.
Solution 2: Rather pragmatic one
Perhaps this simple (but not very nice) workaround helps you out:
[HttpPut("{id}")]
public async Task<IActionResult> Put([FromUri] int id, [FromBody]CustomerViewModel customer)
{
customer.Id = 0;
//Implementation
}
also you could do this
public class CustomerViewModel
{
public int Id { get; private set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string Email { get; set; }
}
I add a note.
Now it's officially explained by Microsoft.
https://learn.microsoft.com/ja-jp/aspnet/core/mvc/models/model-binding?view=aspnetcore-6.0#attributes-for-complex-type-targets
https://learn.microsoft.com/ja-jp/aspnet/core/mvc/models/model-binding?view=aspnetcore-6.0#input-formatters
https://learn.microsoft.com/ja-jp/aspnet/core/mvc/models/model-binding?view=aspnetcore-6.0#frombody-attribute
In summary,
If we use the “FromBody attribute (including defaults such as HttpPost attribute)”, it depends on the input formatter and the BindNever attribute etc. will not work.
Instead, we can do so by specifying the attribute that corresponds to the input formatter.
For example, for the default json
It can be ignored using "System.Text.Json.Serialization.JsonIgnoreAttribute".
Try NotMapped attribute.
Body must be at least 30 characters; you entered 24.
Im New to ASP.NET MVC.
im just learning MVC and i am stuck in a situation where i want to update data in database using Ajax and EF.
I am using code first approach.
I have two projects in my solution. First is The Web MVC project named as Gem, The other i have entities in it with project name Gem.Domain
I have this entity named Category with file name Category.cs
namespace Gem.Domain
{
public class Category
{
public virtual int CategoryID { get; set; }
public virtual int ParentCategory { get; set; }
public virtual string Name { get; set; }
[MaxLength]
public virtual string Description {get;set; }
public virtual ICollection<Product> Products { get; set; }
}
}
with datasource file
namespace Gem.Domain
{
public interface IStoreDataSource
{
IQueryable<Product> Products { get; }
IQueryable<Category> Categories { get; }
}
}
Now in other Project Named Web
I have Area Registered with Name Admin which contains some controllers, but to be specific CategoriesController.cs reside in it.
And i have this method in this CategoriesController
public string UpdateCategoryName_DT(Category category)
{
return "Just A Test";
}
Finally coming to view.
I want to use ajax on a popup that appears on datatables.
Ajax request works fine.. and request do generates to correct method.
Here is my ajax code.
//Category Name Update Using Ajax.
$('#datatable').on('click', '.editable-submit', function () {
var rowID = $(this).parents('tr').find('td:eq(0)').text();
var updatedCategoryName = $(this).parents('div').siblings('div.editable-input').find('input').val();
var postData = {
ID: rowID,
CategoryName: updatedCategoryName
};
//For Ajax Request.
$.ajax({
type:"POST",
data:JSON.stringify(postData),
url:"#Url.Action("UpdateCategoryName_DT", "Categories", new { area = "Admin" })",
success: function (output) {
console.log(output);
}
});
});
Below is generated Ajax Request screen cap, using firebug for showing post info.
Main Question:
I want to know how to get the posted values in the controller through this ajax request and update the category name in database on base of posted values.
e-g i am getting ID of row and New Category name in ajax post, and i want to update the record in category table in database using entity framework.
How to do it, and what is the right method as i am using ajax.
I have tried youtube and tutorials but i am not understanding it.
I have DBContext with name of StoreDb which resides in Gem Project and Infrastructure Folder.
namespace Gem.Infrastructure
{
public class StoreDb : DbContext, IStoreDataSource
{
public StoreDb() : base("GemStoreConnection")
{
}
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
IQueryable<Product> IStoreDataSource.Products
{
get
{
return Products;
}
}
IQueryable<Category> IStoreDataSource.Categories
{
get
{
return Categories;
}
}
}
}
plus i am using structuremap.mvc5 dependency resolution.
namespace Gem.DependencyResolution {
using Domain;
using Infrastructure;
using StructureMap.Configuration.DSL;
using StructureMap.Graph;
public class DefaultRegistry : Registry {
#region Constructors and Destructors
public DefaultRegistry() {
Scan(
scan => {
scan.TheCallingAssembly();
scan.WithDefaultConventions();
scan.With(new ControllerConvention());
});
For<IStoreDataSource>().Use<StoreDb>();
}
#endregion
}
}
i am new but i have did this setup using tutorial for what i have understood so far. but there is nothing related to ajax so i need little help with ajax.
-=-=-=-=-=-=-=-=-=-=
Update:
Used a debugger, i think i am getting null, values are posting fine but i am getting null in controller ?
=-=-=-=-=-=-=-=-=--=-=-=-=-
Update 2:
i removed the JSON.stringify() and changed the Posted Data to this
var postData = {
CategoryID: rowID,
Name: updatedCategoryName
};
As now it matches to schema, so its working now..
but on other hand it also exposes my DB Schema. What if i want to post Ajax with Different ValuesNames, other than the database column names, what to do in that case?
Based on your comments, I think you are not much familiar with HTTP concept.
The simpliest way to read the object from ajax request (or any other POST request) is to update your Category model property names to match the once in json request (you can keep the first letter upper in c#, the rest has to be the same). Right now, your model has CategoryID and CategoryName, but in the json request, you are sending ID and Name parameters. Then, you need to add [FromBody] attribute to you action:
public string UpdateCategoryName_DT([FromBody]Category category)
{
return "Just A Test";
}
The attribute tells the framework, that it should parse the json from body of the request and creates an instance of Category object. Then you will not get null in as seen in your updated question.
UPDATE
You can have different names in json request and in database. You just need to use JsonProperty attribute on the property.
public class Category
{
[JsonProperty(PropertyName = "yourDesiredNameInJsonRequest")]
public virtual int CategoryID { get; set; }
public virtual int ParentCategory { get; set; }
[JsonProperty(PropertyName = "yourAnotherDesiredNameInJsonRequest")]
public virtual string Name { get; set; }
[MaxLength]
public virtual string Description {get;set; }
public virtual ICollection<Product> Products { get; set; }
}
To clarify the code above - the attribute will tell Json.Net to deserialize property "yourDesiredNameInJsonRequest" in your json and save it to Category.CategoryID field. It is just mapping.
I have two models as below
public class Category
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ID { get; set; },
[Required]
public string category { get; set; }
[Required]
public string Desc { get; set; }
}
public class Product
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ID { get; set; },
public int CatID { get; set; },
[ForeignKey("CatID")]
public virtual Category Category { get; set; },
[Required]
public string Desc { get; set; },
public string DisplayName
{
get
{
return string.format("{0} - {1}",this.Category.category,this.Desc);
}
}
}
This is my Edit Action
public ActionResult Edit(int id)
{
ViewBag.PossibleCategories = categoryRepository.All;
return View(productRepository.Find(id));
}
[HttpPost]
public ActionResult Edit(Product product)
{
if (ModelState.IsValid) //<== This becomes false saying category.desc is required
{
productRepository.InsertOrUpdate(product);
productRepository.Save();
return RedirectToAction("Index");
}
else
{
ViewBag.PossibleCategories = categoryRepository.All;
return View();
}
}
I have a scaffolded a Edit view of product and it shows ID and DisplayName as Readonly. All the other fields a editable.
The edit view also has the product -> category -> category has a read-only text field
#Html.TextBoxFor(model => model.Category.category, new Dictionary<string, object>() { { "readonly", "true" } })
The Post back sends this and tries to create a new category. This is not required. The category link will be carried forward using the product.CatID.
How can i display these types of fields??
When the Edit view Post back the Model state appears as invalid because the product's category's desc is null (product -> category -> desc).
if i comment out the DisplayName property in Product this issue doesn't occur.
From my understanding, this is because the DiaplayName property refers to Category property and the view view doesn't have category.desc field so when the model is created back on the POST action, the desc is not populated. Adding the category.desc field to the view is one way of solving this problem.
Is there any other method to solve this?
Note: This is not the only model i'm having this issue. There are many complex models which have the same problem and to me having these fields also included in the view would make for (1) a very cluttered view (2) the amount of data making the round trip will be high.
Simple Solution
Check for null. Really you should be making this a habit anyway.
public string DisplayName
{
get
{
if(this.Category != null)
{
return string.format("{0} - {1}",this.Category.category,this.Desc);
}
else
{
return String.Empty;
}
}
}
Complex Solution
Instead of directly using your database model in your Views another solution is to create ViewModels. These are models meant specifically for your View. As a simplified example, let's take your Product model and create a ViewModel.
Create a folder for your ViewModels
Create ViewModel files that match your Controller
Create a ViewModel that you will use in your View
Say you have a Store Controller. This would be the file structure you would create.
Models
ViewModels
StoreViewModels.cs
Inside the StoreViewModels you would create a ViewModel called ProductViewModel which you would fill in with information from Product.
public class ProductViewModel
{
public int ID { get; set; }
public string Description { get; set; }
public string DisplayName { get; set; }
public ProductViewModel() { }
public ProductViewModel(Product product)
{
this.ID = product.ID;
this.Description = product.Description;
this.DisplayName = product.DisplayName;
}
}
In your View you reference ProductViewModel instead of Product. On the receiving end you then translate the ViewModel fields back to your Model. Let me know if you have any questions.