The binding property is coming null - c#

I'm new at this and trying to figure out what happen and think this is the best place to ask. Well when I select the project and press the add button the property [Bind""]Model came null but why?
This is my View:
#model PortfolioDetailsVM
<form asp-controller="Portfolio" asp-action="AddProject" method="POST">
<div class="form-group">
<div class="input-group mb-3">
<select asp-for="PortfolioProjects.ProjectId" class="custom-select form-control">
<option disabled selected value="#null">Choose...</option>
#foreach (var item in Model.Projects)
{
<option value="#item.ProjectID">#item.Title</option>
}
</select>
The var PortfolioVM came null with any data.
And this is my Controller and my View Model:
namespace PEG.Models
{
public class PortfolioDetailsVM
{
public PortfolioDetailsVM()
{
Portfolios = new Portfolio();
PortfolioProjects = new PortfolioProject();
}
public Portfolio Portfolio;
public PortfolioProject PortfolioProjects;
public IEnumerable<Project> Projects;
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddProject([Bind("PortfolioProject")]PortfolioDetailsVM PortfolioVM) //<----Null
{
var addproject = PortfolioVM.PortfolioProjects;
if (ModelState.IsValid)
{
try
{
context.Update(addproject);
await context.SaveChangesAsync();
return RedirectToAction("Details", "Portfolio" + PortfolioVM.PortfolioProjects.PortfolioId);
}
catch (DbUpdateException)
{
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
}
return RedirectToAction("Index", "Portfolio");
}
This the other model and Details Method:
// GET: Portfolio/Details/5
public async Task<ActionResult> Details(int id)
{
PortfolioDetailsVM PortfolioVM = new PortfolioDetailsVM
{
Projects = await context.Project.Include(x => x.Task).ToListAsync(),
Portfolios = await context.Portfolio.SingleOrDefaultAsync(x => x.PortfolioID == id)
};
return View(PortfolioVM);
}
namespace PEG.Models
{
public partial class PortfolioProject
{
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
[Column(TypeName = "datetime2")]
public DateTime? CreatedDate { get; set; }
//RelationsId
[Key]
[Column(Order = 0)]
public int PortfolioId { get; set; }
[Key]
[Column(Order = 1)]
public string ProjectId { get; set; }
//Relations
[ForeignKey("PortfolioId")]
public virtual Portfolio Portfolio { get; set; }
[ForeignKey("ProjectId")]
public virtual Project Project { get; set; }
}
}

Bind to properties
First off, you have defined fields in your ViewModels. They can be read in your View, but for Model Binding to work you need to declare them as Properties, with a get and set accessor:
public class PortfolioDetailsVM
{
//...
public Portfolio Portfolio { get; set; }
public PortfolioProject PortfolioProject { get; set; }
public IEnumerable<Project> Projects { get; set; }
}
This should make your binding code work.
Better binding models
Second, you are using Model Binding in a slightly incorrect way. Try not to bind directly to your data models (e.g. the type of PortfolioProject). The model you're binding to shouldn't contain any reference to data model types.
Instead, I usually only declare what I really need in the model I'm binding to, so that I won't ever have to use that ol' Bind attribute in the first place. A simple example for your case:
public class DetailsAddProjectVM
{
public string SelectedProjectId { get; set; }
}
With a corresponding form:
#model PortfolioDetailsVM
<select asp-for="SelectedProjectId" class="custom-select form-control">
...
</select>
which posts to
public async Task<IActionResult> AddProject(DetailsAddProjectVM bindingModel)
{
//look ma, no [Bind]!
var projectid = bindingModel.SelectedProjectId;
}
Of course, for the corresponding form to render, you'd also have to declare a SelectedProjectId property in your original PortfolioDetailsVM.
As you can see, you don't have to bind to your original View Model at all.

Related

HTML.DropDownListFor DropDownList

I'm trying to retrieve data from my Database to an HTML instead of retrieving to the Html.DropDownListFor but I'm unable to retrieve to tag.
NewCustomerViewModel
public class NewCustomerViewModel
{
public int CustId { get; set; }
[Required]
public string CustFirstName { get; set; }
[Required]
public string CustLastName { get; set; }
[Required]
public int StId { get; set; }
public IEnumerable<State> States { get; set; }
}
CustomerController
public class CustomerController : Controller
{
private CustomerDbContext _context;
public CustomerController(CustomerDbContext context)
{
_context = context;
}
// GET: /<controller>/
public IActionResult Index()
{
return View(_context.Customers.ToList());
}
public IActionResult Create()
{
var stateNames = _context.States.ToList();
var viewModel = new NewCustomerViewModel
{
States = stateNames
};
return View(viewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(Customer customer)
{
if (ModelState.IsValid)
{
_context.Customers.Add(customer);
_context.SaveChanges();
return RedirectToAction("Index");
}
return View(customer);
}
}
Create View
The HTML DropDownListFor below works fine:
#Html.DropDownListFor(m => m.StId, new SelectList(Model.States, "StId", "StName"))
I'm unable to get the select tag to work though.
<select asp-for="StId" asp-items="#Model.States" class="form-control">
<option>Select State</option>
</select>
All of my HTML in my Create view using and rather than the HTML Helpers which is what I'm trying to avoid. I would just like to be able to retrieve the data to the tag instead.
For the select tag helper, asp-items expects SelectListItem collection\SelectList, and each item in that has a Value and Text property. The Value properties value will be used for the option's value and Text propertiesvalue will be used for the display text of the option in the UI.
Items in your States collection does not have a Value and Text property, but has StId and StName property. So we need to convert this type to SelectListItem type.
So your code should be
<select asp-for="StId" asp-items="#(new SelectList(Model.States,"StId","StName"))">
<option>Please select one</option>
</select>
Additional reference
Select Tag Helper in MVC 6

How Post the list of items from PartialView to controller?

This is quite simple situation actually, but I can't get how to make this work. So, there is list of checkboxes rendered in PartialView. Data passed from parent ViewModel to child ViewModel in PartialView. All these wrapped by form, unfortunately I can't get the data from PartialView.
Parent ViewModel:
public class UserProgramsViewModel
{
public int Id { get; set; }
[Required(ErrorMessage = "Введите название")]
[DisplayName("Название")]
public string ProgramName { get; set; }
[DisplayName("Пользователь")]
public string UserName { get; set; }
[DisplayName("Пользователь")]
public int UserId { get; set; }
[DisplayName("Дни Программы")]
public ICollection<ProgramDaysDTO> ProgramDays { get; set; }
public IEnumerable<DaysViewModel> Days { get; set;} //Passed to Partial
}
Child ViewModel:
public class DaysViewModel
{
public int Id { get; set; }
public string DayName { get; set; }
}
Parent View:
#Html.Partial("Days", Model.Days)
PartialView (here we are using attribute name 'Days' to bind it to Parent model)
#model IEnumerable<BBN.Admin.ViewModels.DaysViewModel>
<ul class="list-group col-lg-2">
#foreach (var item in Model)
{
<li class="list-group-item"><input type="checkbox" name="Days" value="#item.Id" /> #item.DayName</li>
}
</ul>
Controller:
[HttpPost]
[RBAC]
public async Task<ActionResult> Create(UserProgramsViewModel model)
{
var groups = await _us.GetAll();
ViewBag.Users = groups.Select(x => new SelectListItem
{
Text = x.Login,
Value = x.Id.ToString()
});
var dto = new UserProgramsDTO
{
ProgramName = model.ProgramName,
UserId = model.UserId,
Days = model.Days
};
var result = await _ps.Create(dto);
if (result.IsSuccess == (BLL.Utilities.Enums.IsSuccess)Enums.IsSuccess.Success) return RedirectToAction("Index");
else return View("Create");
}
You can use FormCollection. Assign item.DayName to input's name attribute:
PartialView(fragment):
<li class="list-group-item">
<input type="checkbox" name="#(item.DayName)Days" #if (item.Id > 0) { <text>checked</text> } /> #item.DayName
</li>
Then process FormCollection parameter and fill model's Days property with it's help:
Controller:
[HttpPost]
[RBAC]
public async Task<ActionResult> Create(UserProgramsViewModel model, FormCollection formCollection)
{
model.Days = new List<DaysViewModel>();
foreach(var key in formCollection.AllKeys.Where(x => x.Contains("Days")))
model.Days.Add(new DaysViewModel { Id = formCollection[key] == "on" ? 1 : 0, DayName = key.Replace("Days", "")} );
//other stuff...
}
Have you set values for "Days" in your parent view from controller?
like Suppose your parent view name is "Parent" then you should write like this,
public ActionResult Parent()
{
UserProgramsViewModel loUser = new UserProgramsViewModel();
//Assign Your values here
View(loUser);
}
So may be you will not get Null value here.

Form submission in partial views in MVC

I am developing a simple mvc application . The code is as follows:
Model .cs:
public class CustomModel
{
public IEnumerable<lang> lstlang { get; set; }
public IEnumerable<org> lstOrg { get; set; }
}
public class lang
{
public int langid { get; set; }
public string langName { get; set; }
}
public class org
{
public int orgId { get ;set;}
public string orgName { get; set; }
}
Controller.cs
public Action Index()
{
// Get data from database and fill the model
var model = new CustomModel();
return View(model);
}
public Action Partial()
{
// Get data from database and fill the model
var model = new CustomModel();
return PartialView(model);
}
[HttpPost]
public Action Partial(FormCollection frm, CustomModel model)
{
// Get data from database and fill the model
var model = new CustomModel();
return PartialView(model);
}
Index.cshtml
#model CustomModel
#Html.TextboxFor(x => x.lang.FirstOrDefault().id);
<input type="button" id="btn" />
#Html.RenderPartial("Partial", model)
Partial.cshtml
#model CustomModel
#Html.TextboxFor(x => x.lang.FirstOrDefault().id);
<input type="submit" id="submit" />
The thing is, when I click the submit button in the Partial.cshtml page, and examine the model in httppost method in public Action Partial(FormCollection frm, CustomModel model), the model contains null for both lists lstlang and lstOrg, but the formcollection[0] will give the selected textbox value.
What am I missing, or is this the right way of using partial views?
Don't use FirstOrDefault(). If you want to post something back to the front end with collections, you'll need to use indexing.
Public class CustomModel
{
public ICollection<lang> lstlang { get; set; }
public ICollection<org> lstOrg { get; set; }
}
#HTML.textboxfor(x=>x.lang[0].id);

Insert into two tables with no relationships

I am learning LINQ and was wondering how can I INSERT into two different tables with no relationship on one click using LINQ. Is it even possible?
I am adding into one table like this. How can I add into the second table as well?
Second Table,
GenreId
Name
Description
Code,
[HttpPost]
public ActionResult Create(Artist artist)
{
if (ModelState.IsValid)
{
_db.Artists.Add(artist);
_db.Genres.Add(new Genre { GenreId = 1, Name = "Some Genre", Description = "Trying to add second table" }); // how can i add the genre object here
_db.SaveChanges();
return RedirectToAction("Index", new { id = artist.ArtistId });
}
return View(artist);
}
Note that my View is strongly typed to Artist class.
My View,
#model EntityFrameWorkDBFirst.Models.Artist
#Html.TextBoxFor(model => model.Name)
#Html.TextBoxFor(model =>model.Genres) // how to get this working
My Model Class,
public partial class Artist
{
public Artist()
{
this.Albums = new HashSet<Album>();
this.Genres = new HashSet<Genre>();
}
public int ArtistId { get; set; }
public string Name { get; set; }
public virtual ICollection<Album> Albums { get; set; }
public virtual ICollection<Genre> Genres { get; set; }
}
Tuple related suggestion didn't work. Maybe I am missing something.
#model Tuple<EntityFrameWorkDBFirst.Models.Artist, EntityFrameWorkDBFirst.Models.Genre>
<legend>Create a Artist</legend>
#Html.TextBoxFor(model => model.Item1.Name)
<h2>Genre</h2>
#Html.TextBoxFor(model => model.Item2.Name)
#Html.TextBoxFor(model => model.Item2.Name)
<p>
<input type="submit" value="Create" />
</p>
Controller Code:
[HttpPost]
public ActionResult Create(Artist artist, Genre genre)
{
if (ModelState.IsValid)
{
_db.Artists.Add(artist);
_db.Genres.Add(genre);
_db.SaveChanges();
return RedirectToAction("Index", new { id = artist.ArtistId });
}
return View(artist);
}
You mixed domain model and view model.
It's big mistake, you should work only with viewmodel on view.
You should create view model:
public class CreateArtistViewModel
{
public string ArtistName { get; set; }
public int? Genres { get; set; } // if you provide chooser for user
public string GenresName { get; set; } // if user can enter new genre
public string GenresDecription { get; set; } // if user can enter new genre
public IList<Genre> Genres { get; set; }
}
in the post action you should check view model and create artist and genre if user create new gener.

Can't get all my ViewModel properties back after the postback -MVC Partial Views

Can't get all my ViewModel properties back after the postback (After user entered some values on HttpPost)
There are numerous questions here related to losing data or getting nulls after the postback
I tried some of them and played around on my case, I think the scenario is a bit different,
Using a PartialView or Editor Templates(except a list property), Always the returned result properties are null.
In partialView approach always all the properties are null, I think maybe I missed a piece.
In the "custom editor template approach for the type", I'll have just "EnteredNums" List returned. (Maybe because these are what the template have EditorFor for them, but what is the solution here if that's the case?)
Don't know weather if it's important here or not, the application also uses Unity. I don't think it be the problem here.
The HttpGet Passed model is the same as HttpPost : DataVm
The name of the action is also the same : ProcessEnteredData
=================== Controller and action
[HttpPost]
public ActionResult ProcessEnteredData(DataVm vm)
{
if (ModelState.IsValid)
{
foreach (NumType num in vm.EnteredNums)
{
int i1 = num.Score1;
int i2 = num.Score2;
string profTitle = vm.Profile.Title;
Repository.Context.EnteredNums.Add(num);
}
return RedirectToAction("ShowTable");
}
else
{
return View(vm);
}
}
==============
The Partial View of Custom Editor Template are similar :
#model xxxx.NumType
#Html.LabelFor(m => m.TheTitle)
#Html.TextBoxFor(m => m.Score1)
#Html.TextBoxFor(m => m.Score2)
#Html.HiddenFor(m => m.Profile)
// Profile or ProfileId - Just used to see could it bring the property back or not as a test
============
NumType Model
[Key]
public int NumTypeId { get; set; }
[ForeignKey("Profile")]
[Required]
public int ProfileId { get; set; }
public int Score1 { get; set; }
public int Score2 { get; set; }
public int BoxId { get; set; }
public Box Box { get; set; } // something not important here
public virtual Profile Profile { get; set; }
============
The ViewModel
public class DataVm
{
public Profile Profile { get; set; }
public string TheTitle { get; set; }
public List<NumType> EnteredNums { get; set; }
// In the Editor template approach it's the only item with data and others are null
public List<Box> Boxes { get; set; }
}
=========
View for PartialView approach :
#model xxxx.DataVm
#using (Html.BeginForm("ProcessEnteredData", "Profile", FormMethod.Post))
{
#Html.AntiForgeryToken()
Model.EnteredNums = new List<NumType>();
foreach(var box in Model.Boxes)
{
NumType num = new NumType();
num.Profile = Model.Profile;
num.Box = box;
int iCount = Model.EnteredNums.Count;
Model.EnteredNums.Add(num);
#Html.Partial("NumView", Model.EnteredNums[iCount]);
}
<input type="submit" value="Do Process" />
}
===================
View for Editor for approach :
// instead of #Html.Partial :
#Html.EditorFor(m => m.EnteredNums[iCount]);

Categories