View is something like this (I dynamically add and remove documents, Guids written here are DocumentIdentifier member of ViewModel):
<div>
<input type="hidden" name="DocumentViewModels.Index" value="8796c4e8-2262-46c0-bacc-8ffb6678b62a" />
<input type="text" name="DocumentViewModels[8796c4e8-2262-46c0-bacc-8ffb6678b62a].Document.Name" />
<input type="text" name="DocumentViewModels[8796c4e8-2262-46c0-bacc-8ffb6678b62a].Document.Type" />
<div>
<div>
<input type="hidden" name="DocumentViewModels.Index" value="3f2810c6-e338-4a6a-aa64-54b162303aab" />
<input type="text" name="DocumentViewModels[3f2810c6-e338-4a6a-aa64-54b162303aab].Document.Name" />
<input type="text" name="DocumentViewModels[3f2810c6-e338-4a6a-aa64-54b162303aab].Document.Type" />
<div>
I have viewmodel like this:
public class DocumentViewModel
{
public Document Document {get;set;}
public List<Attachments> {get;set;}
public Guid DocumentIdentifier {get;set;}
}
Controller
public ActionResult PostDocuments(List<DocumentViewModel> documentViewModels)
{
// I successfully save newly added documents in database
// Then I delete documents from database that are not in documentViewModels any more (because I dynamically add and delete them)
// Then I find documents that are on form and in database too and I want to update them
List<Document> matchedDocumentsFromDatabase = SynchronizeDocuments(documentViewModels.Select(x => x.Document));
// Here i'm stuck. I want to write something like this:
UpdateModel(matchedDocumentsFromDatabase, "DocumentViewModels");
context.SaveChanges();
// return something;
}
Can you help me?
you can use prefix as following.
public ActionResult PostDocuments(List<DocumentViewModel> documentViewModels)
{
// I successfully save newly added documents in database
// Then I delete documents from database that are not in documentViewModels any more (because I dynamically add and delete them)
// Then I find documents that are on form and in database too and I want to update them
var matchedDocumentsFromDatabase = SynchronizeDocuments(documentViewModels.Select(x => x.Document));
// Here i'm stuck. I want to write something like this:
string prefix = "";
foreach(var item in matchedDocumentsFromDatabase)
{
prefix = string.Format("DocumentViewModels[{0}]",item.DocumentIdentifier .ToString());
UpdateModel(item, prefix);
}
context.SaveChanges();
// return something;
}
hope this could help.
Related
Cannot work out how to simply pass a string from Controller 1 to Controller 2 and use in View 2. Can someone provide a simple example for me?
You said to pass a string from one controller to another, however, the
code you provided in comment is passing an object of Complaints.
If you want to pass an object by TempData, you need to convert object to json first ,then put this json in the TempData, when you want to get the object, you need to get the TempData and deserialize the json to object.
public class Complaints
{
public int? UniqueId { get; set; }
}
Change your code as follow:
public async Task<IActionResult> CreateWorkTicket(int? id)
{
Complaints data = new Complaints() { UniqueId = id };
TempData["mydata"] = JsonConvert.SerializeObject(data);// convert object to json
return RedirectToAction("Create", "WorkTickets");
}
/WorkTickets/Create
public IActionResult Create()
{
Complaints data = JsonConvert.DeserializeObject<Complaints>(TempData["mydata"].ToString());//convert json to object
return View(data);
}
Create.cshtml:
#model Complaints
#{
ViewData["Title"] = "Create";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h1>Create</h1>
<div class="form-group">
<label asp-for="UniqueId" class="control-label">ID</label>
<input asp-for="UniqueId" onclick="ZoomMap()" class="form-control" />
<span asp-validation-for="UniqueId" class="text-danger"></span>
</div>
Test result:
Update
To pass a string , you can use TempData directly, or pass from controller to view, you can refer to the following code:
public async Task<IActionResult> CreateWorkTicket(int? id)
{
TempData["MyString"] = "hello";
return RedirectToAction("Create", "WorkTickets");
}
public IActionResult Create()
{
var data = TempData["MyString"].ToString();
return View((object)data);
}
View:
#model string
#{
ViewData["Title"] = "Create";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h1>Create</h1>
<div class="form-group">
<input class="form-control" value="#Model" />#*first way*#
<input class="form-control" value="#TempData["MyString"].ToString()" />#*second way*#
</div>
Here is the test result:
you just need to make the view a strongly type view and bind primitive values to input field like
#model Complaints
<div class="form-group"> <label asp-for="Id" class="control-label">ID</label> <input asp-for="Id" onclick="ZoomMap()" class="form-control" value=#Model.Value1 /> <span asp-validation-for="Id" class="text-danger"></span> </div>
Using "disabled" attribute on inputs on form does not post them, which is expected and wanted. However, if you prepare a form of 3 objects in a list, disable the first and third, and submit, the 2nd object appears in post header, but does not bind to the list correctly, because it has an index [1] instead of [0].
I understand how model binding works and why it does not bind the posted object that I want, but I don't know how else to describe the problem to get specific results that would lead me to my solution. Anything I search for leads to basic post and binding examples.
List inside the model I'm using:
public IList<_Result> Results { get; set; }
Class _Result has one of the properties:
public string Value { get; set; }
I fill up the list and use it in view like so:
#for (int i = 0; i < Model.Results.Count; i++)
{
...
<td>
<input asp-for="Results[i].Value" disabled />
</td>
...
}
I have checkboxes on form, which remove (with javascript) the "disabled" attribute from the inputs and thus allow them to be posted.
When I fill up the said list with 3 _Result objects, they are shown on form and all have the "disabled" attribute. If I remove the "disabled" attribute from the first two objects and click on submit button, I receive the Results list with first 2 _Result objects, which is as expected.
However, if I remove the "disabled" attribute only from the second _Result object (the first _Result object still has "disabled" attribute), the Results list comes back empty in my Controller method.
In my Form Data Header, I see this: "Results[1].Value: Value that I want posted", which means that post occurs, but list does not bind the object due to the index.
Any idea on how I can achieve that proper binding? Also, the reason I'm using "disabled" attribute is because I'm showing many results on a single page and want to only post those that are selected.
For getting selected items, you could try checkbox with View Model instead of using jquery to control the disable property.
Change ViewModel
public class ModelBindVM
{
public IList<_ResultVM> Results { get; set; }
}
public class _ResultVM
{
public bool IsSelected { get; set; }
public string Value { get; set; }
}
Controller
[HttpGet]
public IActionResult ModelBindTest()
{
ModelBindVM model = new ModelBindVM
{
Results = new List<_ResultVM>() {
new _ResultVM{ Value = "T1" },
new _ResultVM{ Value = "T2" },
new _ResultVM{ Value = "T3" }
}
};
return View(model);
}
[HttpPost]
public IActionResult ModelBindTest(ModelBindVM modelBind)
{
return View();
}
View
<div class="row">
<div class="col-md-4">
<form asp-action="ModelBindTest">
#for (int i = 0; i < Model.Results.Count; i++)
{
<input type="checkbox" asp-for="Results[i].IsSelected" />
<label asp-for="#Model.Results[i].IsSelected">#Model.Results[i].Value</label>
<input type="hidden" asp-for="#Model.Results[i].Value" />
}
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
I have a dilemma that I hope someone can help me with. During the Edit Get stage of loading and updating a page before it is loaded, I store the checkboxes that are checked like this:
foreach (var course in courses.Select((x, i) => new { Data = x, Index = i }))
{
#: <td>
<br /><br /><br />
<input type="checkbox" id="checkbox" name="selectedCourses" value="#course.Data.CourseID"
#(Html.Raw(course.Data.Assigned ? "checked=\"checked\"" : "")) />
</td>
bool theNewString = course.Data.Assigned;
String a = theNewString.ToString();
assignedCourses.Add(a);
}
At the top of the view I defined the list to store the data like this so that I can be sent in the BeginForm:
#{ List<String> assignedCourses = new List<String>(); }
Then I try to send the list to the Edit POST like this:
#using (Html.BeginForm(new { assigned = assignedCourses }))
In my controller signature looks like this:
[HttpPost]
public ActionResult Edit(int id, List<String> assigned)
The list is loaded at the end of the get stage but the data in the list does not go through to Edit POST. My question is how do I get the list that I created at the end of the edit get stage to persist so that I can use it in my post?
Thanks for any ideas.
I'll try make this as simple as possible. So your Course model is something like this...
public class Course
{
public int Id { get; set; }
public string Name { get; set; }
public bool Assigned { get; set; }
}
...and you want to display some saved values on screen to edit. So starting at the controller, I've dummied some data and set one of the Assigned booleans to true so it is checked when the page loads.
public Controller Courses
{
public ActionResult Edit(int id)
{
var courses = new Course[] {
new Course() { Id = 1, Name = "maths", Assigned = true },
new Course() { Id = 2, Name = "english", Assigned = false },
new Course() { Id = 3, Name = "science", Assigned = false }
};
return View(courses);
}
Now, your page should expect a collection of these courses, so at the top of your page define the type that the View is expecting...
cshtml
#model IEnumerable<ExampleProject.Domain.Course>
...which you can then enumerate through in your View and create your checkboxes.
#using (Html.BeginForm("Edit", "Courses", FormMethod.Post))
{
#foreach(var item in Model)
{
<label for="#item.Id">#item.Name</label>
<input type="checkbox" name="courses" id="#item.Id" value="#item.Id" checked="#item.Assigned" />
}
<button type="submit">Save changes</button>
}
This will render the following html:
<label for="1">maths</label>
<input type="checkbox" name="courses" id="1" value="1" checked="checked">
<label for="2">english</label>
<input type="checkbox" name="courses" id="2" value="2">
<label for="3">science</label>
<input type="checkbox" name="courses" id="3" value="3">
Now you can check what you like and submit your form. In true http fashion your controller action will receive the values were checked on the View:
[HttpPost]
public ActionResult Courses(int id, int[] courses)
{
// courses will contain the values of the checkboxes that were checked
return RedirectToAction("Index"); // etc
}
Hopefully this helps. You can be a bit smarter and use the Html helpers for some more complicated binding (eg. Html.CheckBox, Html.CheckBoxFor) but this should get you going and it's clear to see what's going on.
So I have the following code:
#model Project.Models.ViewModels.SomeViewModel
#using (Html.BeginForm("SomeAction", "SomeController", new { id = Model.Id}))
{
for(int i = 0; i < Model.SomeCollection.Count(); i++)
{
#Html.HiddenFor(x => Model.SomeCollection.ElementAt(i).Id)
<div class="grid_6">
#Html.TextAreaFor(x => Model.SomeCollection.ElementAt(i).Text, new { #style = "height:150px", #class = "grid_6 input" })
</div>
}
<div class="grid_6 alpha omega">
<input type="submit" value="Next" class="grid_6 alpha omega button drop_4 gravity_5" />
</div>
}
On the Controller Side I have the following:
[HttpPost]
public ActionResult SomeAction(int id, SomeViewModel model)
{
return PartialView("_SomeOtherView", new SomeOtherViewModel(id));
}
My View Model is set up like this:
public class SomeViewModel
{
public SomeViewModel()
{
}
public IEnumerable<ItemViewModel> SomeCollection { get; set; }
}
public class ItemViewModel{
public ItemViewModel(){}
public int Id {get;set;}
public string Text{get;set;}
}
The SomeCollection is always empty when SomeAction if performed. What do I have to do in order to show the updated values by users. Text Property and Id field.
Use an EditorTemplate
Create an EditorTemplate folder under your Views/YourcontrollerName and create a view with name ItemViewModel.cshtml
And Have this code in that file
#model Project.Models.ViewModels.ItemViewModel
<p>
#Html.EditorFor(x => x.Text)
#Html.HiddenFor(x=>x.Id)
</p>
Now from your Main view, call it like this
#model Project.Models.ViewModels.SomeViewModel
#using (Html.BeginForm("SomeAction", "Home", new { id = Model.Id}))
{
#Html.EditorFor(s=>s.SomeCollection)
<div class="grid_6 alpha omega">
<input type="submit" value="Next" class="grid_6 alpha omega button drop_4 gravity_5" />
</div>
}
Now in your HTTPPOST method will be filled with values.
I am not sure what you want to do with the values( returning the partial view ?) So not making any comments about that.
I am not sure you have posted all the code.
Your action method does not do anything, since it returns a partial view (for some reason from a post call, not an ajax request) using a new model object.
Your effectively passing a model back to the action and then discarding it, and returning a new model object. This is the reason your collection is always empty, its never set anywhere.
Well, for one thing, why do you have both the model AND id, a property of model, sent back to the controller? Doesn't that seem a bit redundant? Also, you're using a javascript for loop in the view. It'd be much easier to just use #foreach.
Anyway, your problem is that when you tell an action to accept a model, it looks in the post for values with keys matching the names of each of the properties of the model. So, lets say we have following model:
public class Employee
{
public string Name;
public int ID;
public string Position;
}
and if I'm passing it back like this:
#using(Html.BeginForm("SomeAction", "SomeController"))
{
<input type="text" name = "name" [...] /> //in your case HtmlHelper is doing this for you, but same thing
<input type="number" name = "id" [...] />
<input type="submit" name = "position" [...] />
}
To pass this model back to a controller, I'd have to do this:
Accepting a Model
//MVC matches attribute names to form values
public ActionResult SomethingPosted(Employee emp)
{
//
}
Accepting a collection of values
//MVC matches parameter names to form values
public ActionResult SomethingPosted(string name, int id, string postion)
{
//
}
or this:
Accepting a FormCollection
//same thing as first one, but without a strongly-typed model
public ActionResult SomethingPosted(FormCollection empValues)
{
//
}
So, here's a better version of your code.
Your new view
#model Project.Models.ViewModels.SomeViewModel
#{
using (Html.BeginForm("SomeAction", "SomeController", new { id = Model.Id}))
{
foreach(var item in Model)
{
#Html.HiddenFor(item.Id)
<div class="grid_6">
#Html.TextAreaFor(item.Text, new { #style = "height:150px", #class = "grid_6 input" })
</div>
}
<div class="grid_6 alpha omega">
<input type="submit" value="Next" class="grid_6 alpha omega button drop_4 gravity_5" />
</div>
}
}
Your new action
[HttpPost]
public ActionResult SomeAction(int Id, string Text)
{
//do stuff with id and text
return PartialView("_SomeOtherView", new SomeOtherViewModel(id));
}
or
[HttpPost]
public ActionResult SomeAction(IEnumerable<ItemViewModel> SomeCollection) //can't use someviewmodel, because it doesn't (directly) *have* members called "Id" and "Text"
{
//do stuff with id and text
return PartialView("_SomeOtherView", new SomeOtherViewModel(id));
}
I am a begginer with asp.net mvc 3. I want to create a page where an user can enter a search item and retrieve the data from database. I have created the database. I have created the controller which is:
public ActionResult SearchIndex(string id)
{
string searchString=id;
var search = from m in db.Searches
select m;
if (!String.IsNullOrEmpty(searchString))
{
search = search.Where(s => s.Name.Contains(searchString));
}
return View(search);
}
I want to pass the value from view page to the above controller. What should be the code in view page to enter an item to search from above mentioned controller?
So create your view with a form inside... something like this:
#using (Html.BeginForm())
{
<label>Search:</label>
<input type="text" name="searchString" />
<input type="submit" name="submit" />
}
Then in your controller you can use the FormCollection object to grab your searchString like this:
[HttpPost]
public ActionResult SearchIndex(FormCollection formCollection)
{
string searchString = formCollection["searchString"];
...
}