Mixing model binding with form posting in MVC 5? - asp.net-mvc

Is it possible to enable model binding along with a posted data from a form?
I have a collection property that I want to iterate in a foreach loop to save each selected item in the collection:
<div class="form-group">
#Html.LabelFor(m => m.Users, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#{
List<ApplicationUser> allUsers = ViewBag.AllUsers;
bool assigned;
}
<div>
#foreach (var user in allUsers)
{
//here I want to render all users, and only the users who are in the task, have a checked checkbox
assigned = Model.Users.Select(u => u.Id).Contains(user.Id);
<input type="checkbox" name="asndUsers" value="#user.Id" id="#user.Id" #Html.Raw(assigned ? "checked" : "") /> <label style="font-weight: normal;" for="#user.Id">#user.UserName</label><br />
}
</div>
</div>
</div>
//fields updated with model binding:
<div class="form-group">
#Html.LabelFor(m => m.Status, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(m => m.Status, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(m => m.Status)
</div>
</div>
this is the Edit action post method:
[HttpPost, ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "Id,Title,Description,DueDate,Status")] UserTask task, string[] asndUsers)
{
if (ModelState.IsValid)
{
task.Users = new List<ApplicationUser>();
foreach (var item in asndUsers)
{
var user = context.Users.Find(item);
task.Users.Add(user);
}
context.Entry(task).State = EntityState.Modified;
context.SaveChanges();
return RedirectToAction("Index");
}
return View(task);
}
it works when I debug, I see the new posted data has merged with the bound data.
but when the request redirected to the Index view, there is no change after editing an item, this is the Index action method:
public ActionResult Index(int? taskId)
{
var viewModel = new TasksUsers();
viewModel.Tasks = context.Tasks.Include(x => x.Users);
if (taskId != null)
{
viewModel.Users = viewModel.Tasks.Where(t => t.Id == taskId).Single().Users;
ViewBag.Row = taskId;
}
return View(viewModel);
}

Original answer
The proper way of updating the related entities is loading them first,
so I used eager loading to redefine the incoming task parameter as
follows:
task = context.Tasks.Include(t => t.Users).Single(s => s.Id == task.Id); Note that `Find` can't be used with `Include` so I used
Single.
That resolved the problem of updating the Users entity
this was wrong,
the proper way is to use explicit binding instead of implicit binding (TryUpdateModel())

The task that is posted back is no longer tracked by the DbContext. Try to Attach the task to the DbSet in the Edit action:
context.Tasks.Attach(task);
if (task.Users == null) {
task.Users = new List<ApplicationUser>();
}
foreach (var item in asndUsers) {
var user = context.Users.Find(item);
task.Users.Add(user);
}
// may be important because Attach() sets the State to 'Unchanged'?
context.Entry(task).State = EntityState.Modified;
context.SaveChanges();
As a side note, you can pass parameters when you call RedirectToAction. (Only do this if you want to pass the id of the edited task to the Index action):
return RedirectToAction("Index", new { taskId = existingTask.Id });
// ^^^^^^
// must match parameter name of Index action

Related

MVC create using liked table values in dropdown, entity framework

I have a patient model, which has a foreign key to a gender table. I would like to create a 'Create' view which allows user to select the value of male or female from a dropdown.
firstly I was not sure how to get the Gender model into the dropdown. I have managed to get it to work by putting into the viewbag, like so
// GET: Patient
public ActionResult Create()
{
using (var context = new WaysToWellnessDB())
{
// prepopulat roles for the view dropdown
var gender = context.Genders.Select(rr => new SelectListItem { Value = rr.GenderId.ToString(), Text = rr.GenderDesc }).ToList();
ViewBag.Gender = gender;
return View();
}
}
In my view I have the following, which gives me the dropdown I desire.
<div class="form-group">
#Html.LabelFor(model => model.Gender, "Gender", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("Gender", (IEnumerable<SelectListItem>)ViewBag.Gender, "Select ...")
#Html.ValidationMessageFor(model => model.Gender.GenderId, "", new { #class = "text-danger" })
</div>
</div>
However when I do my post to add to the database, the gender column is null.
// POST: /Roles/Create
[HttpPost]
public ActionResult Create(Patient patient)
{
try
{
using (var context = new WaysToWellnessDB())
{
context.Patients.Add(patient);
context.SaveChanges();
return RedirectToAction("Index");
}
}
catch
{
return View();
}
}
Please can someone advise how I get the gender Id to be stored in the database?
You should use #Html.DropDownListFor
#Html.DropDownListFor(model => model.GenderId, (IEnumerable<SelectListItem>)ViewBag.Gender, "Select ...")

CSHTML, Changing a value with EditorFor, do not want old value displayed?

I'm sorry if this question is unclear or I'm not giving enough information, I'm new to CSHTML, and cannot tell if I'm missing something incredibly obvious.
I currently have an EditorFor() in my view that changes the password of a specific userCard field in a database. When this password is being changed, I would like the textbox to be empty, but every time it displays the old value. Deleting the value prior to edit is not an option, for my page has too many options for the user to go to a different page and leave the account passwordless. Any help?
The offending EditorFor is below:
<div class="form-group">
<div class="col-md-10">
<h5>Enter a New Passcode</h5>
</div>
<div class="col-md-10">
#Html.EditorFor(model => model.userCard.password, new { htmlAttributes = new { #class = "form-control"} })
#Html.ValidationMessageFor(model => model.userCard.password, "", new { #class = "text-danger" })
</div>
</div>
The controller methods for this viewpage are below:
public ActionResult EditPasscode(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
UserCardOBJ userCardOBJ = new UserCardOBJ();
userCardOBJ.userCard = db.UserCards.Find(id);
if (userCardOBJ.userCard == null)
{
return HttpNotFound();
}
return View(userCardOBJ);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult EditPasscode([Bind(Include = "ID,password")] UserCard userCard)
{
if (ModelState.IsValid)
{
//db.Entry(userCard).State = EntityState.Modified;
db.UserCards.Attach(userCard);
db.Entry(userCard).Property(model => model.password).IsModified = true;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(userCard);
}
You could add a value property to the htmlAttribute anonymous type passed in.
#Html.EditorFor(model => model.userCard.password, new { #class = "form-control", #value = "" } })

BeginForm not showing initial Dropdown List Value

I am using BeginForm and the initial value of one of the Dropdown's is not being set properly. The value is held in a SelectLitemItem and is correctly shown in the preceding Table (in the Index view). The available values provided for list are also correct. It always defaults to "Create" which has a value of "1" even when it should be "Update" which has a value of "2".
The Model.RuleType.Value is shown correctly if I just display it on the form.
I cannot see what I'm doing wrong. I can see no duplicate ID/ Name and have tried including a Hidden field.
There is another dropdown that happens to use a common value for both Value and Text and that works.
Any ideas?
Thanks.
#model AMS.Web.Areas.Admin.Models.RuleActionModel
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true) #Html.HiddenFor(model => model.ID)
<div class="form-group">
#Html.LabelFor(model => model.RuleType, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.Partial("Partials/_RuleTypeDropdown") #Html.ValidationMessageFor(model => model.RuleType)
</div>
</div>
RuleTypeDropdown
#using Kendo.Mvc.UI;
#(Html.Kendo().DropDownList()
.Name("RuleType")
.DataTextField("Text")
.DataValueField("Value")
.DataSource(source =>
{
source.Read(read =>
{
read.Action("RuleTypeList", "List", new { Area = "Admin" });
});
})
)
Model
public class RuleActionModel
{
public RuleActionModel() { }
public RuleActionModel(RuleAction ruleAction)
{
...
RuleType = new SelectListItem()
{
Value = ruleAction.RuleType.ToString(),
Text = ((RuleType)(ruleAction.RuleType)).EnumToString()
};
}
[Display(Name = "Type")]
public SelectListItem RuleType { get; set; }
Controller
public ActionResult Edit(Guid? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
RuleAction ruleAction = UnitOfWork.RuleActionRepository.FirstOrDefault(x => x.ID == id);
if (ruleAction == null)
{
return HttpNotFound();
}
var model = new RuleActionModel(ruleAction);
return View(model);
}

There is no ViewData item of type 'IEnumerable<SelectListItem>' that has the key 'CategoryID

I have found a lot of similar threads on this problem. But whatever I do, it never works for me.
The ONLY thing I want to achieve right now, is to fill a dropdown list with database values in a partial view, that's within a partial view. That is all, and this is driving me absolutely nuts. A user should be able to chose a category name in the dropdown list.
Here's my Controller Class:
public class AttributeController : Controller
{
private readonly IRepository<ClassLibrary.Entities.Attribute> _aRepository;
private readonly IRepository<Category> _cRepository;
public AttributeController() : this(new Repository<ClassLibrary.Entities.Attribute>(), new Repository<Category>())
{
}
public AttributeController(IRepository<ClassLibrary.Entities.Attribute> repo, IRepository<Category> repository)
{
_aRepository = repo;
_cRepository = repository;
}
//
// GET: /Attribute/
public ActionResult Index()
{
var attributes = _aRepository.GetAll();
var attributeViewModels = new List<AttributeViewModel>();
foreach (ClassLibrary.Entities.Attribute attribute in attributes)
{
var viewModel = new AttributeViewModel();
viewModel.Id = attribute.Id;
viewModel.AttributeName = attribute.Name;
attributeViewModels.Add(viewModel);
}
return View(attributeViewModels);
}
//
// GET: /Attribute/Details/5
public ActionResult Details(int id)
{
return View();
}
//
// GET: /Attribute/Create
public ActionResult Create()
{
ViewData["CategoryID"] = new SelectList(_cRepository.GetAll().ToList(), "Category_id", "Category");
IEnumerable<SelectListItem> items = _cRepository.GetAll().Select(c => new SelectListItem
{
Value = c.Id.ToString(),
Text = c.Name
});
ViewData["CategoryID"] = items;
return View();
}
//
// POST: /Attribute/Create
[HttpPost]
public ActionResult Create(FormCollection collection)
{
try
{
// TODO: Add insert logic here
return RedirectToAction("Index");
}
catch
{
return View();
}
}
And here's the index View:
#model IEnumerable<GUI.Models.AttributeViewModel>
#{
ViewBag.Title = "Attributes";
}
<div>
#Html.Partial("~/Views/Attribute/Create.cshtml", new GUI.Models.AttributeViewModel())
</div>
<h2>All existing Attributes</h2>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.AttributeName)
</th>
<th></th>
</tr>
#if(Model != null)
{
foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.AttributeName)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id=item.Id }) |
#Html.ActionLink("Details", "Details", new { id=item.Id }) |
#Html.ActionLink("Delete", "Delete", new { id=item.Id })
</td>
</tr>
}
}
</table>
Here's the partial View Within the index view:
#model GUI.Models.AttributeViewModel
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Create an attribute for this Category</h4>
<hr />
#Html.ValidationSummary(true)
<div class="form-group">
#Html.Partial("~/Views/Attribute/CategoryPartial.cshtml", new GUI.Models.CategoryViewModel())
<div class="form-group">
#Html.LabelFor(model => model.AttributeName, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.AttributeName)
#Html.ValidationMessageFor(model => model.AttributeName)
</div>
</div>
</div>
</div>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
And finally CategoryPartialView within the partial view that will contain the DropDown List where the user should be able to select a category Name.
#model GUI.Models.CategoryViewModel
#using GUI.Controllers
#using System.Collections.Generic
#{
//var categories = (IEnumerable<GUI.Models.CategoryViewModel>)ViewData["categories"];
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<hr />
#Html.ValidationSummary(true)
<div class="form-group">
#Html.LabelFor(model => model.Name, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("CategoryID", (IEnumerable<SelectListItem>)ViewData["CategoryID"])
</div>
</div>
}
The exception ALWAYS occurs when I'm debugging the application and it points to the #Html.DropDownList part. Claiming that "There is no ViewData item of type 'IEnumerable' that has the key 'CategoryID".
#Html.DropDownList("CategoryID", (IEnumerable<SelectListItem>)ViewData["CategoryID"]
I'm sorry for posting so many code blocks. But I would really like to solve this.I've beaten my head against the wall for hours with frustration, and any help would be greatly appreciated.
When you use #Html.Partial("~/Views/Attribute/CategoryPartial.cshtml", new GUI.Models.CategoryViewModel()) you are directly instantiating the partial view. The controller does not get called and is not responsible for generating the HTML markup string. Additionally, you are passing an empty view model (new GUI.Models.CategoryViewModel()). Since the controller does not get called, it does not get the data for the drop down list and, obviously, does not save it in your ViewData.
Use the #Html.Action helper instead:
<div class="form-group">
#Html.Action("Create", "Attribute")
<div class="form-group">
Change the Create action like this:
//
// GET: /Attribute/Create
public ActionResult Create()
{
ViewData["CategoryID"] = new SelectList(_cRepository.GetAll().ToList(), "Category_id", "Category");
IEnumerable<SelectListItem> items = _cRepository.GetAll().Select(c => new SelectListItem
{
Value = c.Id.ToString(),
Text = c.Name
});
ViewData["CategoryID"] = items;
return PartialView("CategoryPartial", new GUI.Models.CategoryViewModel());
}
Note: I don't know your entire structure, but I might be best to use this approach and to return a Lis<SelectListItem> model.
when calling:
#Html.Partial("~/Views/Attribute/Create.cshtml", new GUI.Models.AttributeViewModel())
you call the view directly with out passing throw the controller so the model you have there is an empty model!
try pass throw the controller to create you'r model and send it back as a view:
#Html.RenderPartial("YOUR CONTROLLER",model);

ASP.NET MVC edit operation?

#Html.ActionLink("Edit", "EditArticle", new { ArticleID = article.ArticleID })
I retrieved Article by ArticleID and return to edit page like this:
public ActionResult EditArticle(Guid ArticleID)
{
AddArticleModel AddArticleModel = new AddArticleModel();
AddArticleModel.Categories = entity.TBL_CATEGORIES.Select(a => a);
AddArticleModel.Article = dbo.SelectArticleById(ArticleID);
return View(AddArticleModel);
}
There is no problem until here.
And in my editing page I'm changing some attributes of article (not all attributes).For example I'm changing title, content, and updateddate. Like this:
#model DunyaYazilim.Models.AddArticleModel
#{
ViewBag.Title = "EditArticle";
Layout = "~/Views/Shared/_LayoutAuthor.cshtml";
}
#using (Html.BeginForm((string)ViewBag.FormAction, "Author"))
{
#Html.ValidationSummary(true, "Makale gönderilirken bir hata oluştu. Lütfen daha sonra tekrar deneyin.")
<div>
<div class="label_header">#Html.Label("Kategori Seçiniz:")</div>
<div>#Html.DropDownList("CategoryID", new SelectList(Model.Categories, "CategoryID", "Name"),Model.Article.TBL_CATEGORIES.Name)</div>
<div class="label_header">#Html.Label("Makale Başlık:")</div>
<div>#Html.TextBoxFor(m => m.Article.Title, new { #class = "my_textbox" })</div>
<div class="label_header">#Html.Label("Makale Açıklama:")</div>
<div>#Html.TextAreaFor(m => m.Article.Description, new { #class = "my_textarea" })</div>
<div class="label_header">#Html.Label("Makale İçerik:")</div>
<div>#Html.TextAreaFor(m => m.Article.ArticleContent, new { #class = "my_textarea" })</div>
<div><input type="submit" value="Gönder" class="my_button" /></div>
</div>
}
And then I post it to:
[HttpPost]
public ActionResult EditArticle(AddArticleModel AddArticleModel, String CategoryID)
{
//TODO: update database...
return View(AddArticleModel);
}
But unchanged attributes are return null(ArticleID, UserID, etc).So I cant Update the database, Because I dont have ArticleID after posting. What is the reason for this?
Thanks.
MVC doesn't maintain anything for you between requests. When you post to your action, it will post only the values that you have set up in your form. As you don't have your article id or user id in the form (or anywhere else, e.g. route or query string), MVC won't know about them during model binding for your EditArticle action.
If you want the extra details to be sent through with your post, you can put hidden fields in the form, e.g.
#Html.HiddenFor(m => m.Article.Id)

Resources