I am attempting to create a new database record in ASP.NET MVC using a code first database. I created the controller for the model with scaffolding and views and every time I attempt to do a POST on create the model is never valid, the two models that are members of the one I am attempting to create are always null.
Below is the code for the Create POST in my controller.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "DeviceNumber,ManufacturerNumber,CarrierNumber,Name")] Device device)
{
if (ModelState.IsValid)
{
db.Devices.Add(device);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.CarrierNumber = new SelectList(db.Carriers, "CarrierNumber", "CarrierID", device.CarrierNumber);
ViewBag.ManufacturerNumber = new SelectList(db.Manufacturers, "ManufacturerNumber", "ManufacturerID", device.ManufacturerNumber);
return View(device);
}
The following is the model I am trying to create. The errors in the ModelState always tell me that Manufacturer and Carrier are null, but they are required.
public class Device
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int DeviceNumber { get; set; }
[ForeignKey("ManufacturerNumber"), Required]
public virtual Manufacturer Manufacturer { get; set; }
public int ManufacturerNumber { get; set; }
[ForeignKey("CarrierNumber")]
[Required]
public virtual Carrier Carrier { get; set; }
public int CarrierNumber { get; set; }
[Required]
public string Name { get; set; }
}
I've tried assigning the Carrier and Manufacturer in the Create method but it still resulted in a failed validation.
You should not have the [Required] attributes on the navigation properties(Manufacturer and Carrier).
Instead keep it on the ManufacturerNumber and CarrierNumber properties because you are getting values for those columns from the UI.
public class Device
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int DeviceNumber { get; set; }
[ForeignKey("ManufacturerNumber")]
public virtual Manufacturer Manufacturer { get; set; }
[Required]
public int ManufacturerNumber { get; set; }
[ForeignKey("CarrierNumber")]
public virtual Carrier Carrier { get; set; }
[Required]
public int CarrierNumber { get; set; }
[Required]
public string Name { get; set; }
}
Also there is no need to include DeviceNumber inside Bind as the value for this will be auto generated. This should be good enough.
[HttpPost]
public ActionResult Create([Bind(Include = "ManufacturerNumber,CarrierNumber,Name")]
Device device)
{
// your code
}
This should work fine assuming your view has a form which sends data for the required fields.
#model Device
#using (Html.BeginForm())
{
#Html.ValidationSummary(false)
#Html.LabelFor(f=>f.Name)
#Html.TextBoxFor(f=>f.Name)
#Html.LabelFor(f => f.CarrierNumber)
#Html.DropDownList("CarrierNumber")
#Html.LabelFor(f => f.ManufacturerNumber)
#Html.DropDownList("ManufacturerNumber")
<input type="submit"/>
}
Remember, the best way to prevent over posting is to use a view model. This also helps to keep your layers loosely coupled.
Related
I'm doing an ASP.NET MVC project and I'm having trouble to make the create view work: it doesn't save data into two tables. I'm creating a client with name, email, address, zipcode + city.
These are my two tables:
Clients table:
public int ClientID { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Address { get; set; }
public Nullable<int> MailID { get; set; }
public virtual PostOffices PostOffices { get; set; }
And then I have PostOffices table:
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public PostOffices()
{
this.Clients = new HashSet<Clients>();
}
public int MailID { get; set; }
public string Zipcode { get; set; }
public string City { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Clients> Clients { get; set; }
I have written a controller looking like this:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ClientID,Name,Email,Address,MailID")] Clients clients)
{
if (ModelState.IsValid)
{
db.Asiakkaat.Add(asiakkaat);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(asiakkaat);
}
In the view it shows everything and lets me enter data into all of the input boxes, but it only saves data to the Clients table like name, email, address, but it doesn't save anything into the PostOffices table (meaning zipcode and city).
Is there something wrong with my controller? Or should I somehow add into a client class a zipcode + city, and not the MailID? I can't get it to work.
I appreciate the help!
Just remove [Bind] and never use this attribute in the future
public ActionResult Create( Clients clients)
If I have for example model Ticket.cs which contain following field
public class Ticket
{
public int Id { get; set; }
[Display(Name = "Opis")]
public string Description { get; set; }
[Display(Name = "Datum i vrijeme")]
public DateTime DateAndTime { get; set; } = DateTime.Now;
[Display(Name = "Vrsta tiketa")]
public int TicketTypeID { get; set; }
[ForeignKey("TicketTypeID")]
public virtual TicketType TicketType { get; set; }
public int? ClientId { get; set; }
[ForeignKey("ClientId")]
public Client Client { get; set; }
}
And in Index.cshtml I display this data.
Another Model Discussion.cs
public class Discussion
{
[Key]
public int Id { get; set; }
[Display(Name = "Odgovor")]
public string Answer { get; set; }
public string UserId { get; set; }
[ForeignKey("UserId")]
public virtual ApplicationUser ApplicationUser { get; set; }
public bool IsActive { get; set; } = true;
}
Right now, I want data from Ticket.cs transfer to Discussion Index.cshtml to be display like
DateTime,Description
What Do I need to do in my controller to get this kind of data exactly same as it is in Ticket model
Please let me know if you need more information or source code. I didn't put much code in order to post be more clear.
Is the best way to pass to my Discussion Model
public Ticket ticket {get;set;}
Or create ViewModel
UPDATE
Once I include in my View
#model VmSTicketing.Models.ViewModels.DiscussionTicketVM
And when I use foreach look
#foreach (var item in Model)
{
<tr>
<td>#item</td>
<td>08/14/2017</td>
<td>nn</td>
<td>nn</td>
<td>Website problem</td>
<td><span class="text-primary">Open</span></td>
</tr>
}
I get error message
foreach statement cannot operate on variables of type 'DiscussionTicketVM' because 'DiscussionTicketVM' does not contain a public instance or extension definition for 'GetEnumerator'
In such cases, you should use the ViewModel. The ViewModel is a combination of several models that are displayed in one view.
Suppose you have a view in which both customer information and product information must be displayed. To do this, you need to create a class and create an instance of each model that appears in the view.
public class DiscussionTicketViewModel
{
public Discussion discussion { get; set; }
public Ticket ticket { get; set; }
}
In controller
public ActionResult Index()
{
var model = new DiscussionTicketViewModel();
model.ticket = //.............
model.discussion = //.........
return View(model);
}
In View
<tr>
<td>#Model.ticket.Description</td>
<td>08/14/2017</td>
<td>nn</td>
<td>nn</td>
<td>Website problem</td>
<td><span class="text-primary">Open</span></td>
</tr>
I'm new to programming and development and I'm learning, and this is one of my learning projects. I've been trying to get around this in various ways, but when I try to add a new Vehicle Model to a specific Vehicle Make, the Id column doesn't automatically increment, but tries to overwrite the first Id.
I tried working around Data annotations, which I think are correct, I tried manually adding values to the database via queries, and it works perfectly. Tried deleting the db and migrations, changing the annotations again and nothing works. The only thing I can be doing wrong is the code itself, probably somewhere in the controller or service layer.
VehicleMake Class:
public class VehicleMake
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
[Display(Name = "Make Name")]
public string Name { get; set; }
[Required]
[Display(Name = "Abbreviation")]
public string Abrv { get; set; }
[Display(Name = "Models")]
public virtual IEnumerable<VehicleModel> VehicleModels { get; set; }
}
VehicleModel Class:
public class VehicleModel
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public int MakeId { get; set; }
public virtual VehicleMake Make { get; set; }
[Required]
[Display(Name = "Model Name")]
public string Name { get; set; }
[Required]
[Display(Name="Abbreviation")]
public string Abrv { get; set; }
}
Controller for Vehicle Model:
[HttpPost]
public IActionResult Create(int Id, VehicleModel newModel)
{
if (ModelState.IsValid)
{
newModel.MakeId = Id;
_model.Add(newModel);
return RedirectToAction("Index");
}
return View(newModel);
}
Service for adding new model:
public void Add(VehicleModel newModel)
{
_context.Add(newModel);
_context.SaveChanges();
}
Here is the value it is trying to add to the db and of course gives an error
https://imgur.com/pL9EruF
What am I doing wrong?
Why are you passing an id to a create action in the first place? You should just have:
[HttpPost]
public async Task<IActionResult> Create(VehicleModel newModel)
{
if (!ModelState.IsValid)
return View(newModel);
_context.Add(newModel);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
Note:
I'm showing the minimal code here. If you want to factor out the call to Add and SaveChangesAsync to a service, that's fine.
Use the async methods when working with EF Core. ASP.NET Core and EF Core are both async all the way. The sync methods only exist for serving rare edge-case scenarios where you can't use async for some reason, and all they do is block on the async methods.
I have the Users Model follows:
Model Users.cs
[Key]
[Column("cod")]
public int Cod { get; set; }
[Column("nome")]
[StringLength(120)]
public string Nome { get; set; }
[Column("sobrenome")]
[StringLength(80)]
public string Sobrenome { get; set; }
[Column("email")]
[StringLength(60)]
public string Email { get; set; }
[Column("password")]
[StringLength(20)]
public string Password { get; set; }
And I also have the ViewModel only with the data shown in View
UsersViewModel.cs
public int Cod { get; set; }
public string Nome { get; set; }
public string Sobrenome { get; set; }
My generic repository looks like this:
public void Update(TEntity obj)
{
Db.Entry(obj).State = EntityState.Modified;
Db.SaveChanges();
}
In my controller it looks like this:
[HttpPost]
public IActionResult SalvaPerfil([FromBody] UsersViewModel usersViewModel)
{
if (ModelState.IsValid)
{
var user = Mapper.Map<UsersViewModel, Users>(usersViewModel);
_PetApp.Update(user);
return Content("fim...");
}
return Content("ERRO");
}
The problem is that the Email and Password fields are being written as NULL in the database, I do not want to change the Email and Password values as they are changed in another View.
I saw this other post, but it is not with Generic Repository, I could not implement it ..
How to update only one field using Entity Framework?
In this case do I need to create a custom method for this change? or is there any way to only change the fields that are in the ViewModel?
Thanks!
I have looked around and found some close answers, but I haven't seen one yet like this:
Using Entity Framework I have the following:
A Role model:
public class Role
{
[Key]
public short RoleId { get; set; }
public string RoleName { get; set; }
public string RoleDescription { get; set; }
}
A User model:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Username { get; set; }
//more fields etc...
public virtual ICollection<UserRole> UserRoles { get; set; }
}
and a UserRole model:
public class UserRole
{
[Key]
public int UserRoleId { get; set; }
public int UserId { get; set; }
public short RoleId { get; set; }
public virtual Role Role { get; set; }
}
What I am trying to do is determine how to compose a viewmodel such that I can display a list of all available roles when creating a new user and a list of available+selected roles when editing a user. I can achieve the first part already using a foreach, but I feel like its dirty.
In all of the examples I have seen, the entire viewmodel is wrapped in an IEnumerable on main view and is rendered using #Html.EditorForModel() with an editor template. This seems to allow for automagic mapping of the view data back into the underlying model. I would like to achieve this using the same technique, but I can't seem to wrap my head around handling the collection of Role/UserRole within a singular User model.
StackOverflow question I am referencing: Generate Dynamically Checkboxes, And Select Some of them as Checked
I would suggest 2 view models for editing
public class RoleVM
{
public short RoleId { get; set; }
public string RoleName { get; set; }
public bool IsSelected { get; set; }
}
public class UserVM
{
public int Id { get; set; }
public string Name { get; set; }
public List<RoleVM> Roles { get; set; }
}
GET method
public ActionResult Edit(int ID)
{
UserVM model = new UserVM();
// map all avaliable roles to model.Roles
// map user to model, including setting the IsSelected property for the users current roles
return View(model);
}
View
#model YourAssembly.UserVM
...
#Html.TextBoxFor(m => m.Name)
...
#EditorFor(m => m.Roles)
EditorTemplate (RoleVM.cshtml)
#model YourAssemby.RoleVM
#Html.HiddenFor(m => m.RoleId) // for binding
#Html.CheckBoxFor(m => m.IsSelected) // for binding
#Html.DisplayFor(m => Name)
POST method
[HttpPost]
public ActionResult Edit(UserVM model)
{
// model.Roles now contains the ID of all roles and a value indicating if its been selected