ASP.NET MVC edit operation? - asp.net-mvc

#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)

Related

ListBoxFor in .NET Core not binding

I cannot get this ListBox to have a value. It worked in MVC4.5, from which I am translating; cannot get to work in MVC Core. Swear it was working at some point; now it is not.
Have tried:
https://stackoverflow.com/a/19144613/2496266
https://stackoverflow.com/a/40308906/2496266
etc.
View
<div class="tb-field desc-field">
#Html.ListBoxFor(model => model.workerRequests,
new SelectList(ViewBag.workerRequests, "Value", "Text"),
new { id = "workerRequests2_WO-"+ Model.ID, tabindex = "22", style="min-width: 16em;"}
)
</div>
Controller (GET)
wo.workerRequests = new List<ViewModel.WorkerRequest>();
ViewData["workerRequests"] = new SelectList(wo.workerRequests);
return PartialView("Create", wo);
Controller (POST)
public async Task<ActionResult> Create(WorkOrder wo, string userName)
{
ModelState.ThrowIfInvalid();
var modelUpdated = await _adaptor.TryUpdateModelAsync(this, wo);
// ... "wo.WorkerRequests" is empty here ^
}
Entity
public virtual ICollection<WorkerRequest> workerRequests { get; set; }
Additional info:
Apparently it is coming across in the Request, but as a StringValue:
...apparently all of them are string values. It's not getting bound to its desired type.
Basically:
It has getters and setters on the entity.
I'm populating the ViewData before serving the page.
I'm using a SelectList(arg) where arg is of type myModel.myProperty.
We are selecting the Worker in the WorkerRequest from the database (nested type). Is this due to the lazy-loading bugs? What am I doing wrong?
It looks like you are creating 2 SelectList objects for the view.
Once in controller:
ViewData["workerRequests"] = new SelectList(wo.workerRequests);
And again in the view:
#Html.ListBoxFor(model => model.workerRequests,
new SelectList(ViewBag.workerRequests, "Value", "Text"),
You only need to create one and the best place would be in the view. So change your controller code to populate the ViewData with the data and let the view create the SelectList to display:
ViewData["workerRequests"] = wo.workerRequests;
Now you should see the list box populated with the data.
For the curious, this is what we ended up with:
Controller
public ActionResult Create(Domain.WorkOrder wo, string userName, List<Domain.WorkerRequest> workerRequestList)
{
UpdateModel(wo);
Domain.WorkOrder neworder = woServ.Create(wo, userName);
// JSON object with new work order data
var result = map.Map<Domain.WorkOrder, ViewModel.WorkOrder>(neworder);
return Json(new
{
sNewRef = result.tabref,
sNewLabel = result.tablabel,
iNewID = result.ID
},
JsonRequestBehavior.AllowGet);
}
View
<div class="tb-label desc-label">
#Html.LabelFor(model => model.workerRequests)
<br />
<input type="button" value="#Machete.Web.Resources.WorkOrder.requestAdd" class="formButton" id="addRequestBtn-#(Model.ID)"/>
<input type="button" value="#Machete.Web.Resources.WorkOrder.requestRemove" class="formButton" id="removeRequestBtn-#(Model.ID)"/>
</div>
<div class="tb-field desc-field">
#Html.ListBox("workerRequests2", new SelectList(ViewBag.workerRequests, "Value", "Text"), new { id = "workerRequests2_WO-"+ Model.ID, tabindex = "22", style="min-width: 16em;"})
</div>

Mixing model binding with form posting in MVC 5?

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

How do I get form values to controller

I'm working on our new startpage that will show a filter and a list of news posts. Each news post, and each newslist can be tagged with several business areas.
The newslist will contain news according to:
the settings of the newslist that can be altered by admins.
then filtered by the filter form on the page.
and, if the filter is empty but the user is logged in, filtered by the users preferences.
When the view IndexSecond is loaded, the filter gets its choosable business areas according to its newslist. My problem is that I don’t know how to get the selected business areas from the EditorFor to end up in the model.filteredBAs that is passed to IndexSecondFiltered?
When I come to a breakpoint in IndexSecondFiltered, the model is allways null.
In the view IndexSecond
#model Slussen.BLL.Models.PostFilterListModel
...
#using (Html.BeginForm("IndexSecondFiltered", "Home", new { model = model }))
{
#Html.HiddenFor(model => model.filteredBAs)
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
<div class="col-sm-2">Business area</div>
<div class="col-md-10">
#Html.EditorFor(model => Model.newslistModel.BusinessAreas,
new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Go" class="btn btn-primary orange" />
</div>
</div>
}
In HomeController
public ActionResult IndexSecond()
{
//Known user?
int? uo = null;
if (User.Identity.IsAuthenticated)
uo = CurrentUser.UserOrganization.Id;
return View(
_queryDispatcher.Execute<PostFilterListModel>(
new GetFilteredNewsListById(1, uo, "", 1,
new System.Collections.Generic.List<int>(),
new System.Collections.Generic.List<int>())));
}
[HttpPost]
public ActionResult IndexSecondFiltered(PostFilterListModel model)
{
//Known user?
int? uo = null;
if (User.Identity.IsAuthenticated)
uo = CurrentUser.UserOrganization.Id;
return View(
_queryDispatcher.Execute<PostFilterListModel>(
new GetFilteredNewsListById(1, uo, "", 1,
new System.Collections.Generic.List<int>(), model.filteredBAs)));
}
I got help from a collegue.
I didn't need the [HttpPost] ActionResult IndexSecondFiltered at all.
When I replaced this
#using (Html.BeginForm("IndexSecondFiltered", "Home"), new { model = model })
with this
#using (Html.BeginForm("IndexSecond", "Home"))
the model was passed to the controller along with IsSelected-status

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);

How to "extend" model validation?

I have a two pages:
Create page that uses Data Annotations validation.
Edit page.
Both pages use different models. I have to use a specific model for the Edit page in order for the values of the selected row to display. How do I:
Get the Edit Controller to use the same validation, OR
Get the Edit page to display the current row's values if I use the same model as the Create page?
e.g.:
My Create page:
#model Test.Models.NewPerson
#using (Html.BeginForm())
{
#Html.ValidationSummary(true, "Failed. Please fix the errors.")
<div class="editor-label">
#Html.LabelFor(m => m.FirstName)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.FirstName)
#Html.ValidationMessageFor(m => m.FirstName)
</div>
<div class="editor-label">
#Html.LabelFor(m => m.LastName)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.LastName)
#Html.ValidationMessageFor(m => m.LastName)
</div>
<input type="submit" value="Submit" />
}
My Model:
public class NewPerson
{
[Required(ErrorMessage = "*")]
[Display(Name = "First name")]
public string FirstName { get; set; }
[Required(ErrorMessage = "*")]
[Display(Name = "Last name")]
public string LastName { get; set; }
}
Then my Edit page:
#model Test.Person
using (Html.BeginForm())
{
#Html.ValidationSummary(true, "Please fix the errors below.")
<div class="editor-label">
#Html.LabelFor(m => m.FirstName)
</div>
<div class="editor-field">
#Html.EditorFor(m => m.FirstName)
#Html.ValidationMessageFor(m => m.FirstName)
</div>
<div class="editor-label">
#Html.LabelFor(m => m.LastName)
</div>
<div class="editor-field">
#Html.EditorFor(m => m.LastName)
#Html.ValidationMessageFor(m => m.LastName)
</div>
<input type="submit" value="Update" />
}
EDIT
In my controller, Edit action, I have:
var context = new MyContext();
var person = context.Person.Single(m => m.ID == id);
if (Request.IsAjaxRequest())
{
return PartialView("Edit", person);
}
return View(person);
When I put a breakpoint in that function, I am seeing results for var person. However, it returns nothing in the View. Why not?
EDIT
Here is my code for the actions:
[HttpPost]
public ActionResult Create(NewPerson model)
{
if (ModelState.IsValid)
{
string UID = Membership.GetUser().ProviderUserKey.ToString();
System.Guid myUID = System.Guid.Parse(UID);
using (var context = new MyContext())
{
Person newPerson = new Person();
newPerson.UserId = myUID;
newPerson.FirstName = model.FirstName;
newPerson.LastName = model.LastName;
context.Person.AddObject(newPerson);
context.SaveChanges();
}
}
And Edit action:
[HttpGet]
public ActionResult Edit(int id)
{
var context = new MyContext();
//recently edited: accidentally had "camper" instead of "person"
var person = context.Person.Single(m => m.ID == id);
if (Request.IsAjaxRequest())
{
return PartialView("Edit", person);
}
return View(person);
}
And my View:
#foreach (var person in Model)
{
#Html.DisplayFor(modelItem => person.LastName), #Html.DisplayFor(modelItem => person.FirstName)
#Html.ActionLink("Edit", "Edit", new { id = person.ID }, new { #class = "openDialog",
data_dialog_id = "emailDialog", data_dialog_title = "Edit Person" })
}
Both views can use the same model. That is a common practice for Create/Edit functions, and will solve problem 1, having same validation rules.
To display existing data in the input fields on the edit view, you'll need to retrieve that data in the controller and passing it to the view (ie, populate the model).
Presumably, on Edit, you will have an ID of some sort. Use that to get the proper record from the data source, and then set the FirstName and LastName values in the model. Then when you render the view (after passing in the model, of course) you'll see the existing values in the textboxes.
It's a little confusing here as to what you have and haven't done, and what does and doesn't work. However there are a few things that you should fix.
First, you should not be passing the Person object that comes from your database directly to the view. Instead, you should have your own specific Person ViewModel. This ViewModel should have your data annotations for your view on it. When you get the Person from the database, you project them into your ViewModel like this:
var camper = context.Person
.Select(m => new ViewModel.Person
{ Firstname = m.Firstname, Lastname = m.Lastname}).Single(m => m.ID == id);
This prevents your Data model's Person from being bloated by View requirements (for instance, your data model might allow nulls, but you want to set your View to be Required.)
Second, you're not using a using statement in the edit view. Something like this:
using (var camper = ..) {
...
}
Using a ViewModel helps here as well, since this allows the context to be destroyed without conflicting with the entities which are change tracked.
Third, you should probably use DisplayTemplates a little better. Rather than having the foreach statement in your view, do this:
#Html.DisplayForModel()
Then create a folder called DisplayTemplates in Views\Shared (or in the folder your view is in) and create a new .cshtml file called Person.cshtml, in that have this code:
#model Person
#Html.DisplayFor(m => m.LastName), #Html.DisplayFor(m => m.FirstName)
#Html.ActionLink("Edit", "Edit", new { id = person.ID }, new { #class = "openDialog",
data_dialog_id = "emailDialog", data_dialog_title = "Edit Person" })
I also notice some discrepancies in your namespaces. In one place you have Test.Person, in another you have Test.Models.NewPerson. Is it possible that you also have a Test.Models.Person and you're getting confused about which is which, so you end up populating the wrong one?

Resources