Model binding Asp net core 2.2 - model-binding

I have the following complex model:
public class User
{
public int Id { get; set; }
public string UserName { get; set; }
public int UserId { get; set; }
}
I need to bind the whole model in my action method using [FromBody], while Id property should come [FromQuery]. My action method looks like this:
public IActionResult Delete([FromBody]User userRequest)
{
// Some code
}
The thing is that I can't change the model, because it is 3-rd party and also, I can't have Id as the second parameter in action method, because I have validation logic for userRequest where I need the Id. Any ideas?

Use a DTO/view model and map over to User. For example:
public class UserDTO
{
public string UserName { get; set; }
public int UserId { get; set; }
}
Then:
public IActionResult Delete(int id, [FromBody]UserDTO userRequest)
{
var user = new User
{
Id = id,
UserName = userRequest.UserName,
UserId = userRequest.UserId
}
// do something
}

Related

Adding Child to an existing parent in Webapi odata controller

I using Odata Webapi with EF6 and my models are as below
public class Company
{
Public Company()
{
Products = new List<Product>();
}
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
public List<Product> Products { get; set; }
}
public class Product
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
I need to expose an end point to add product/s to an existing Company.
the end point has to be something like below.
Post: ~odata/Company(1)/Products with body as List of products json.
body:[{"Name":"Product1"},{"Name":"Product2"}]
I would suggest using an bound ODataAction for this purpose.
You would need to pass in a DTO containing a list of Products.
An example for DTO would be:
public class CreateProductsDTO
{
List<Product> Products { get; set; }
}
Then you would need to register an bound action to Company controller:
var action = builder.EntityType<Company>().Action("AddProducts");
action.Parameter<CreateProductsDTO>("Value");
In the controller you need to define an action like:
[HttpPost]
public async Task<IHttpActionResult> AddProducts([FromODataUri] Guid key, ODataActionParameters parameters)
{
//read parameter from ODataActionParameters
var createProducts = parameters["Value"] as CreateProductsDTO;
//Process information
}
The request for this would look like this:
Post: ~odata/Company(1)/AddProducts
body:
{ "Value" : {
"Products" :
[{"Name":"Product1"},{"Name":"Product2"}]
}
}
Hope this helps.
Regards,
Mihai

Can user hack values in action parameter?

Example:
I have table Orders and table OrderPositions.
public partial class Orders
{
public Orders()
{
this.OrderPositions = new HashSet<OrderPositions>();
}
public int OrderId { get; set; }
public string Title { get; set; }
public virtual ICollection<OrderPositions> OrderPositions { get; set; }
}
public partial class OrderPositions
{
public int OrderPositionId { get; set; }
public int OrderId { get; set; }
public string Name { get; set; }
public virtual Orders Orders { get; set; }
}
On the view user can modify single record from OrderPositions table.
In controller:
[HttpPost]
public ActionResult Edit(OrderPositions orderPosition)
{
// save orderPosition
}
So parameter orderPosition.Orders should be = null because on the form in view user can modify only order position. But can user hack it? I mean that in parameter orderPosition.Orders won't be null and I update record not only in table OrderPositions but also in table Orders? Or ASP.NET MVC prevent from that situation?
It really depends on what you do here
[HttpPost]
public ActionResult Edit(OrderPositions orderPosition)
{
// save orderPosition
}
If you're saving the whole entity then yes there is nothing stopping a user passing over addition entity properties. There are a few ways to prevent this though, here are a couple...
1.Create a new entity at the point of saving
[HttpPost]
public ActionResult Edit(OrderPositions orderPosition)
{
if(ModelState.IsValid)
{
var order = new OrderPositions
{
OrderPositionId = orderPosition.OrderPositionId,
OrderId = orderPosition.OrderId,
Name = orderPosition.Name
};
//Then save this new entity
}
}
2.Create a Model specific to the entity's action
public class EditOrderPosition
{
[Required]
public int PositionId { get; set; }
[Required]
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
[HttpPost]
public ActionResult Edit(EditOrderPosition model)
{
if(ModelState.IsValid)
{
var order = new OrderPositions
{
OrderPositionId = model.PositionId,
OrderId = model.Id,
Name = model.Name
};
//Then save this new entity
}
}
I generally go with the 2nd method as it stops direct user involvement with my entities. As a rule of thumb I never use entity objects as parameters in controller actions.
Hope this helps
Yes they can. This is one reason I do not expose my entities as a parameter to action methods, instead I use DTOs that only have the properties that I expect.
This is an example of the Mass Assignment Vulnerability.
Yes, there is nothing preventing a rogue app calling your endpoint with arbitrary data. Always validate everything serverside.

MVC Controller to Controller data

Is there a way in MVC to pass information from one controller to another? I have a character model that looks like this:
public class Character
{
[Key]
public string CharacterID { get; set; }
public string UserID { get; set; }
public string Name { get; set; }
public int Str { get; set; }
public int Con { get; set; }
public int Dex { get; set; }
public int Int { get; set; }
public int Wis { get; set; }
public int Cha { get; set; }
public int BaseAttack { get; set; }
}
And a separate weapon model like this:
public class Weapons
{
[Key]
public string WeaponID { get; set; }
public string UserID { get; set; }
public string CharacterID { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public string Range { get; set; }
public int Damage { get; set; }
public int Crit { get; set; }
public int CritMultiplier { get; set; }
public string Hands { get; set; }
public string Distance { get; set; }
}
To create a weapon, you first need to create a character which assigned an ID, and I want to be able to pass that ID into the create method of my weapon controller. Is there a way to do this? Thanks
You can use TempData for this purpose. TempData stores data only between two requests. When you set the TempData the next request that is initiated can retrieve value from the TempData and it will be erased for any consequent requests.
[HttPost]
public ActionResult CreateCharacter()
{
// creates charaeters here and sets the tempdata
TempData['CharacterId'] = 50;
return RedirectToAction('CreateWeapon');
}
[HttpGet]
public ActionResult CreateWeapon()
{
var weaponModel = new WeaponModel() { CharacterId = (int)TempData['CharacterId'] };
return View(weaponModel);
}
and in your view simply have a hidden for the CharacterId, so it will be persisted if you your post fails validation or if you need to re-display the view.
#Html.HiddenFor(e => e.CharacterId);
Again this is just one approach, only if you you don't want to pass the CharacterId in the url.
You can also achive this just by passing it in the url:
[HttPost]
public ActionResult CreateCharacter()
{
// creates charaeters here and sets the tempdata
return RedirectToAction('CreateWeapon', new { characterId = 50 });
}
[HttpGet]
public ActionResult CreateWeapon(int characterId)
{
var weaponModel = new WeaponModel() { CharacterId = characterId };
return View(weaponModel);
}
I would be inclined to pass the character id to the create weapon action via routing, either as a route token that forms part of the path or via the query string. Be sure to check that the weapon can logically be associated with the character to whom the id corresponds.
You could also pass the id using TempData or Session, but considering both by default will take up memory on the web server, the simple option is to use the routing. In addition, unless you call TempData.Keep("key") after accessing TempData, the value will be removed from TempData after the first access, potentially causing issues if the user refreshes the browser window.
You could use RedirectToAction(), though as titled this will cause browser redirection.
return RedirectToAction("CreateWeapon", "Weapon", new { id = yourid });
or
#Html.ActionLink("CreateWeapon", "Create", new { id = yourid })
Edit: Your plain object property names and your action method variables need to match, to do this.

MVC parameter not binding to controller action (KENDO UI)

Hope someone can help - this has been bugging me for around 2 hours - its probably something simple :)
Kendo UI Grid sends a request to my controller
http://localhost:1418/user/update?UserID=1&UserName=Admin&RoleName=Admin&Email=c.j.hannon%40gmail.com&Active=true&Company%5BCompanyID%5D=1&Company%5BCompanyName%5D=asd
However, the controller class 'Company' isnt bound by the binder? Can any one help my view model and controller action signature are below:
[HttpGet]
public JsonResult Update(UserViewModel model)
{
svcUser.UpdateUser(new UpdateUserRequest() {
UserID=model.UserID,
RoleID = model.RoleName,
Email = model.Email,
Active = model.Active.GetValueOrDefault(false),
UserName = model.UserName
});
return Json("", JsonRequestBehavior.AllowGet);
}
public class UserViewModel
{
public int UserID { get; set; }
public string UserName { get; set; }
public string RoleName { get; set; }
public string Email { get; set; }
public bool? Active { get; set; }
public CompanyViewModel Company { get; set; }
}
Cheers
Craig
A few things. Your immediate problem is that Company is mapped to a complex object not a primitive type. Kendo Grid just does not do this (as of this writing). Just guessing, but you probably want to setup a foreign key binding on the Grid and just pass back the Id of the company from a listbox. This is not as bad as you think and it will immediatly fix your problem and look nice too.
Maybe personal taste but seems to be a convention. Use the suffix ViewModel for the model that is bound to your View and just the suffix Model for your business objects. So a Kendo Grid is always populated with a Model.
Ex.:
public class UserModel
{
public int UserID { get; set; }
public string UserName { get; set; }
public string RoleName { get; set; }
public string Email { get; set; }
public bool? Active { get; set; }
public int CompanyID { get; set; }
}
public class CompanyModel
{
public int ID { get; set; }
public string Name { get; set; }
}
public class UserViewModel
{
public UserModel UserModel { get; set; }
public IList<CompanyModel> Companies { get; set; }
}
public ActionResult UserEdit(string id)
{
var model = new UserViewModel();
model.UserModel = load...
model.Companies = load list...
return View(model);
}
#model UserViewModel
...
column.ForeignKey(fk => fk.CompanyId, Model.Companies, "ID", "Name")
(Razor Notation)
BUT! This is just an example, you are better off Ajax loading the Grid with the IList becuase I assume you have many Users in the Grid at once, though you could server bind off the ViewModel with a List too. But the list of Companies is probably the same every time, so map it to the View just liek this rather than Ajax load it every time you do a row edit. (not always true)

Creating related attributes when creating new instance of class MVC 4 - Entity Framework

I am just getting into MVC 4 and Entity Framework 5 and want to know if what I am doing is correct?
I have a UserObject and a JobObject, the jobObject has a reference to a User Object.
public class Job
{
public int id { get; set; }
public virtual MyUser User { get; set; }
public JobType JobType { get; set; }
}
When I want to create an instance of the Job I am passing in the query string a parameter UserID, but the Job only deals with an instance of MyUser.
Is the following the correct way to associate the user to the job?
[HttpPost]
public ActionResult Create(Job job, int userid)
{
if (ModelState.IsValid)
{
MyUser staffmember = db.MyUsers.Find(userid);
if (staffmember == null)
{
return View("StaffMemberNotFound");
}
job.User = staffmember;
db.Jobs.Add(job);
db.SaveChanges();
}
}
Or is there a better way to associate the user to the job?
Your way will work but I prefer to simply work with ids if possible.
What I would suggest is that you add a MyUserId property to your Job class (remember to update the database if you are using codefirst):
public class Job
{
public int id { get; set; }
[ForeignKey("User")]
public int MyUserId { get; set: }
public virtual MyUser User { get; set; }
public JobType JobType { get; set; }
}
Then simply populate the MyUserId. You can also change your check to simply check if the id exists as apposed to finding an object and letting EF map that to a class before returning it to you
[HttpPost]
public ActionResult Create(Job job, int userid)
{
if (ModelState.IsValid)
{
if (!db.MyUsers.Any(u => u.Id == userid)
{
return View("StaffMemberNotFound");
}
job.MyUserId = userid;
db.Jobs.Add(job);
db.SaveChanges();
}
}
EF will do the rest of the mapping for you when you next retrieve the record from the database.
Your approach works fine, the only small optmization you could make is not taking the "retrieval hit" of MyUser staffmember = db.MyUsers.Find(userid); since you already have the userid.
I am using ASP.NET MVC 4 and Entity Framework 5.0, and here is my code (different model objects, but same intent as what you are doing).
Note: I let EF generate my model classes by right-clicking on the Models folder and choosing Add->ADO.NET Entity Data Model in VS.NET 2012.
Store.Models.Product
namespace Store.Models
{
using System;
using System.Collections.Generic;
public partial class Product
{
public long Id { get; set; }
public string ProductName { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
public System.DateTime DateAdded { get; set; }
public Nullable<long> CategoryId { get; set; }
public virtual Category Category { get; set; }
}
}
Store.Models.Category
namespace Store.Models
{
using System;
using System.Collections.Generic;
public partial class Category
{
public Category()
{
this.Products = new HashSet<Product>();
}
public long Id { get; set; }
public string CategoryName { get; set; }
public System.DateTime DateAdded { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
}
On my Create.cshtml page, I have the User select the CategoryId from the drop-down list. This Category Id is bound to Product.CategoryId. All I do in my method is this:
ProductController
public class ProductController : Controller
{
...
[HttpPost]
public ActionResult Create(Product product)
{
product.DateAdded = DateTime.Now;
if (dbContext != null)
{
dbContext.Products.Add(product);
dbContext.SaveChanges();
}
return RedirectToAction("Index");
}
...
}

Resources