ASP MVC - Get data from partial view on Create - c#

Im using ASP.Net MVC 5.
I have two simple classes; Student and Course, like this;
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Course
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
I want to create a new Course with optional many Students.
The student(s) form/view will be rendered as a partail view (insde the Course-view).
Right now I have a Create-View that is strongly type to Course.
This view only have 1 textbox - name of the Course.
I render the partial view that is strongly typed to Student.
To simplify i just want to add 1 student to the List.
I would like pass the student data to the course object and then "move on" to the controller.
Can anyone help me with this approach of passing data from a partitial view, or give me a hint of how its done in MVC? ;)

Ok found out what I was doing wrong. First off I donwloaded the Html helper, BeginCollectionItem. Real nice if you want to dynamically add and remove fields/textboxes that will be added to your model.
First off send an empty object to to your view to work with (from your controller).
I just passed in a new Course object. Ctor of Course creates a new List with 1 student object.
Then i used RenderPartial to display a partailview + the student item.
#foreach(var student in Model.Students)
{
RenderPartial("_Student", student);
}
This view looks like this:
#model Project.Data.Entities.Student
<div class="AddStudent form-group">
#using (Html.BeginCollectionItem("students"))
{
#Html.Label("Name:", new { #class = "control-label col-md-2" })
<div class="col-md-8">
#Html.TextBoxFor(x => x.Name)
<button type="button" class="deletButton btn btn-default">Remove</button>
#Html.ValidationMessageFor(model => model.Name)
</div>
}
</div>
I render a button that is hooked up to delete (jquery) the student field.
When i want to add more students to my Course i just use an ajax call to add more partial "_Student" views.
<div>
#Ajax.ActionLink("Add more...", "NewStudentRow", "Course", new AjaxOptions()
{
InsertionMode = InsertionMode.InsertAfter,
UpdateTargetId = "students"
}, new { #class = "btn btn-default" })
</div>
The NewStudentRow method in my controller looks like this:
public PartialViewResult NewStudentRow ()
{
return PartialView("_Student", new Student());
}
Pretty simple if you just use the http://www.nuget.org/packages/BeginCollectionItem/

You can solve this by having more than one partial view..
Pseudo-code:
CourseView:
<TextBox>#Model.Name</TextBox>
#foreach(var student in Model.Students)
{
RenderPartial("ShowStudent");
}
RenderPartial("AddStudent");
The AddStudentView conains all fields you need to provide to save a student to database. In the action you take the input parameters, save the new student and redirect ( something like return RedirectToAction("Course", new { id = student.CourseId }) ) to the Course view. The course view will then be loaded including the new student.
You could also do all of this with ajax to prevent postback, but as you haven't specified any desire to prevent postback I think this would be a good solution.

Related

Entity set to null after selected item from SelectList

When creating Employee entity you are supposed to select MeetingCenterfrom DropDownList. All MeetingCenters show just fine in DropDownList with their Names, but when some of them is selected and Employee is created Meeting Center is null. Im using NoSQL DocumentDB database.
Controller:
[ActionName("Create")]
public async Task<IActionResult> CreateAsync()
{
ViewBag.MeetingCentersList = await _meetingCenterReposiotry.GetItemsAsync();
return View();
}
Create View:
#model Employee
#using (Html.BeginForm("Create", "Home", FormMethod.Post, new { #class = "form-horizontal" }))
{
...
<div class="form-group">
#Html.LabelFor(MeetingCenter => Model.MeetingCenter, new { #class = "control-label" })
#Html.DropDownListFor(MeetingCenter => Model.MeetingCenter, new SelectList(ViewBag.MeetingCentersList, "MeetingCenterId", "Name"), new { #class = "form-control" })
</div>
...
}
Piece of Employee Model
public class Employee
{
...
[JsonProperty(PropertyName = "id")]
public string EmployeeId { get; set; }
[JsonProperty(PropertyName = "meetingCenter")]
public MeetingCenter MeetingCenter { get; set; }
...
}
Piece of MeetingCenter Model
public class MeetingCenter
{
...
[JsonProperty(PropertyName = "id")]
public string MeetingCenterId { get; set; }
...
}
With your current code, the DropDownListFor helper will render a SELECT element with options, which has the MeetingCenterId as the value attribute and the Name as the Text. The SELECT element's name attribute value will be MeetingCenter. So when the form is submitted the form data will look like this
MeetingCenter: 2
Assuming user selected the option with value "2".
But the MeetingCenter property of your view model(Employee) is not a numeric type, it is a complex type(MeetingCenter). So model binder cannot map this value to MeetingCenter property of your view model.
You can render the SELECT element with the name MeetingCenter.MeetingCenterId and then model binder will be able to map the posted form data as the input element name matches with the naming-structure of your view model.
So you should render something like this inside your form.
<select name="MeetingCenter.MeetingCenterId">
</select>
You can generate the above markup by using the SELECT tag helper and specifying MeetingCenter.MeetingCenterId as the asp-for property.
<select asp-for="MeetingCenter.MeetingCenterId"
asp-items="#(new SelectList(ViewBag.MeetingCentersList,
"MeetingCenterId", "Title"))">
</select>
Now when the form is submitted, it will populate the MeetingCenter property of your view model and it's MeetingCenterId property.
If you want the full MeetingCenter property to be populated (properties other than MeetingCenterId, get the full object by querying the data provided by _meetingCenterReposiotry.GetItemsAsync() using the MeetingCenterId available to you in the HttpPost action. Something like this
var id = viewModel.MeetingCenter.MeetingCenterId;
var items = await _meetingCenterReposiotry.GetItemsAsync();
var item = items.FirstOrDefault(a=>a.MeetingCenterId==id);
// User item now
// May be do somethig like : viewModel.MeetingCenter = item;
I also suggest you to use the correct types. If MeetingCenterId is numeric value, use int as type instead of string

Add item into List from View and pass to Controller in MVC5

I have a form like this:
JS Code:
$(document).ready(function() {
$("#add-more").click(function() {
selectedColor = $("#select-color option:selected").val();
if (selectedColor == '')
return;
var color = ' <
div class = "form-group" >
<
label class = "col-md-2 control-label" > Color: < /label> <
div class = "col-md-5" > < label class = "control-label" > ' + selectedColor + ' < /label></div >
<
/div>
';
var sizeAndQuantity = ' <
div class = "form-group" >
<
label class = "col-md-2 control-label" > Size and Quantity: < /label> <
div class = "col-md-2" > < label class = "control-label" > S < /label><input type="text" class="form-control"></div >
<
div class = "col-md-2" > < label class = "control-label" > M < /label><input type="text" class="form-control"></div >
<
div class = "col-md-2" > < label class = "control-label" > L < /label><input type="text" class="form-control"></div >
<
div class = "col-md-2" > < label class = "control-label" > XL < /label><input type="text" class="form-control"></div >
<
/div>
';
html = color + sizeAndQuantity
$("#appendTarget").append(html)
});
});
Old Code:
Model:
namespace ProjectSem3.Areas.Admin.Models
{
public class ProductViewModel
{
public ProductGeneral ProductGeneral { get; set; }
public List<SizeColorQuantityViewModel> SizeColorQuantities { get; set; }
}
public class ProductGeneral
{
public string Product { get; set; }
public string Description { get; set; }
public string ShortDescription { get; set; }
public List<ProductCategory> Categories { get; set; }
public string SKU { get; set; }
public float Price { get; set; }
public float PromotionPrice { get; set; }
public bool Status { get; set; }
}
public class SizeColorQuantityViewModel
{
public string ColorId { get; set; }
public List<SizeAndQuantity> SizeAndQuantities { get; set; }
}
public class SizeAndQuantity
{
public string SizeId { get; set; }
public int Quantity { get; set; }
}
}
Controller:
public class ProductController : Controller
{
// GET: Admin/Product
public ActionResult Create()
{
var colors = new List<string>() { "Red", "Blue" };
var sizes = new List<string>() { "S", "M", "L", "XL" };
var categories = new ProductDao().LoadProductCategory();
var productGeneral = new ProductGeneral()
{
Categories = categories
};
var model = new ProductViewModel
{
ProductGeneral = productGeneral,
SizeColorQuantities = new List<SizeColorQuantityViewModel>()
};
foreach (var color in colors)
{
var child = new SizeColorQuantityViewModel
{
ColorId = color,
SizeAndQuantities = new List<SizeAndQuantity>()
};
model.SizeColorQuantities.Add(child);
foreach (var size in sizes)
{
child.SizeAndQuantities.Add(new SizeAndQuantity()
{
SizeId = size
});
}
}
return View(model);
}
// POST: Admin/Product
[HttpPost]
public ActionResult Create(ProductViewModel model)
{
return View();
}
}
View:
#for (var i = 0; i < Model.SizeColorQuantities.Count; i++)
{
<div class="form-group">
<label class="col-md-2 control-label">Color:</label>
<div class="col-md-2">
#Html.TextBoxFor(m => m.SizeColorQuantities[i].ColorId, new { #class = "form-control", #readonly = "readonly" })
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Size and Quantity:</label>
#for (var j = 0; j < Model.SizeColorQuantities[i].SizeAndQuantities.Count; j++)
{
<div class="col-md-2">
#Html.TextBoxFor(m => m.SizeColorQuantities[i].SizeAndQuantities[j].SizeId, new
{
#class = "form-control",
#style = "margin-bottom: 15px",
#readonly = "readonly"
})
#Html.TextBoxFor(m => m.SizeColorQuantities[i].SizeAndQuantities[j].Quantity, new { #class = "form-control" })
</div>
}
</div>
}
I choose a color and click Add, it'll add more item in to list. I'm newbie in ASP.NET MVC. I just know value from Razor how to pass value form
I also asked about same thing in here and received kind explain. But, it's static value which pass from controller and then is used to bind into razor.
But now, it isn't static.
Could you tell me how to bind razor item in to list to post it to controller? I would be very grateful if you give me some suggest.
Thanks for your kind helping. (bow)
You can refer this post. It works perfect for me.
http://ivanz.com/2011/06/16/editing-variable-length-reorderable-collections-in-asp-net-mvc-part-1/
I will quote it below:
The aspects I will consider are:
Dynamically adding, removing and reordering items to/from the
collection
Validation implications
Code Reusability and Refactoring implications I will assume that you are already familiar with ASP.NET MVC and basic JavaScript concepts.
Source Code
All source code is available on GitHub
The Sample
What I am going to build is a little sample where we have a user who has a list of favourite movies. It will look roughly like on the image below and will allow for adding new favourite movies, removing favourite movies and also reordering them up and down using the drag handler.
In Part 1 I look at implementing collection editing by sticking to facilities provided to us by ASP.NET MVC such as views, partial views, editor templates, model binding, model validation, etc.
Domain Model
The domain model is basically:
public class User
{
public int? Id { get; set; }
[Required]
public string Name { get; set; }
public IList<Movie> FavouriteMovies { get; set; }
}
and
public class Movie
{
[Required]
public string Title { get; set; }
public int Rating { get; set; }
}
Let’s get cracking!
An Edit View
Let’s start by creating a first-pass edit view for our Person to look like the one on the image above:
#model CollectionEditing.Models.User
#{ ViewBag.Title = "Edit My Account"; }
<h2>Edit</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>My Details</legend>
#Html.HiddenFor(model => model.Id)
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
</fieldset>
<fieldset>
<legend>My Favourite Movies</legend>
#if (Model.FavouriteMovies == null || Model.FavouriteMovies.Count == 0) {
<p>None.</p>
} else {
<ul id="movieEditor" style="list-style-type: none">
#for (int i=0; i < Model.FavouriteMovies.Count; i++) {
<li style="padding-bottom:15px">
<img src="#Url.Content("~/Content/images/draggable-icon.png")" style="cursor: move" alt=""/>
#Html.LabelFor(model => model.FavouriteMovies[i].Title)
#Html.EditorFor(model => model.FavouriteMovies[i].Title)
#Html.ValidationMessageFor(model => model.FavouriteMovies[i].Title)
#Html.LabelFor(model => model.FavouriteMovies[i].Rating)
#Html.EditorFor(model => model.FavouriteMovies[i].Rating)
#Html.ValidationMessageFor(model => model.FavouriteMovies[i].Rating)
Delete
</li>
}
</ul>
Add another
}
<script type="text/javascript">
$(function () {
$("#movieEditor").sortable();
});
</script>
</fieldset>
<p>
<input type="submit" value="Save" />
Cancel
</p>
}
he view is creating a list of editing controls for each of the movies in Person.FavouriteMovies. I am using a jQuery selector and dom function to remove a movie when the user clicks “Delete” and also a jQuery UI Sortable to make the items from the HTML list drag and droppable up and down.
With this done we immediately face the first problem: We haven’t implemented the “Add another”. Before we do that let’s consider how ASP.NET MVC model binding of collections works.
ASP.NET MVC Collection Model Binding Patterns
There are two patterns for model binding collections in ASP.NET MVC. The first one you have just seen:
#for (int i=0; i < Model.FavouriteMovies.Count; i++) {
#Html.LabelFor(model => model.FavouriteMovies[i].Title)
#Html.EditorFor(model => model.FavouriteMovies[i].Title)
#Html.ValidationMessageFor(model => model.FavouriteMovies[i].Title)
…
}
which generates similar HTML:
<label for="FavouriteMovies_0__Title">Title</label>
<input id="FavouriteMovies_0__Title" name="FavouriteMovies[0].Title" type="text" value="" />
<span class="field-validation-error">The Title field is required.</span>
This is really great for displaying collections and editing static length collections, but problematic when we want to edit variable length collections, because:
1. Indices have to be sequential (0, 1, 2, 3, …). If they aren’t ASP.NET MVC stops at the first gap. E.g. if you have item 0, 1, 3, 4 after the model binding has finished you will end up with a collection of two items only – 1 and 2 instead of four items.
2. If you were to reorder the list in the HTML ASP.NET MVC will apply the indices order not the fields order when doing model binding.
This basically means that add/remove/reorder scenarios are no go with this. It’s not impossible but it will be big big mess tracking add/remove/reorder actions and re-indexing all field attributes.
Now, someone might say – “Hey, why don’t you just implement a non-sequential collection model binder?” .
Yes, you can write the code for a non-sequential collection model binder. You will however face two major issues with that however. The first being that the IValueProvider doesn’t expose a way to iterate through all values in the BindingContext which you can workaround* by hardcoding the model binder to access the current HttpRequest Form values collection (meaning that if someone decides to submit the form via Json or query parameters your model binder won’t work) or I’ve seen one more insane workaround which checks the *BindingContext one by one from CollectionName[0] to CollectionName[Int32.MaxValue] (that’s 2 billion iterations!).
Second major issue is that once you create a sequential collection from the non-sequential indices and items and you have a validation error and you re-render the form view your ModelState will no longer match the data. An item that used to be at index X is now at index X-1 after another item before it was deleted, however the ModelState validation message and state still point to X, because this is what you submitted.
So, even a custom model binder won’t help.
Thankfully there is a second pattern, which mostly helps for what we want to achieve (even though I don’t think it was designed to solve exactly this):
<input type="hidden" name="FavouriteMovies.Index" value="indexA"/>
<input name="FavouriteMovies[indexA].Title" type="text" value="" />
<input name="FavouriteMovies[indexA].Rating" type="text" value="" />
<input type="hidden" name="FavouriteMovies.Index" value="indexB"/>
<input name="FavouriteMovies[indexB].Title" type="text" value="" />
<input name="FavouriteMovies[indexB].Rating" type="text" value="" />
Notice how we have introduced an “.Index” hidden field for each collection item. By doing that we tell ASP.NET MVC’s model binding “Hey, don’t look for a standard numeric collection index, but instead look for the custom Index value we have specified and just get me the list of items in a collection when you are done”. How does this help?
We can specify any index value we want
The index doesn’t have to be sequential and items will be put in the collection in the order they are in the HTML when submitted.
Bam! That’s solves most, but not all of our problems.
The Solution
Firstly, ASP.NET MVC doesn’t have HTML helpers to generate the “[something].Index” pattern which is major problem since it means we can’t use validation and custom editors. We can fix that by utilizing some ASP.NET templating fu. What we are going to do is move the Movie editor to a its own partial view (MovieEntryEditor.cshtml):
#model CollectionEditing.Models.Movie
<li style="padding-bottom:15px">
#using (Html.BeginCollectionItem("FavouriteMovies")) {
<img src="#Url.Content("~/Content/images/draggable-icon.png")" style="cursor: move" alt=""/>
#Html.LabelFor(model => model.Title)
#Html.EditorFor(model => model.Title)
#Html.ValidationMessageFor(model => model.Title)
#Html.LabelFor(model => model.Rating)
#Html.EditorFor(model => model.Rating)
#Html.ValidationMessageFor(model => model.Rating)
Delete
}
</li>
And update our Edit view to use it:
<ul id="movieEditor" style="list-style-type: none">
#foreach (Movie movie in Model.FavouriteMovies) {
Html.RenderPartial("MovieEntryEditor", movie);
}
</ul>
<p><a id="addAnother" href="#">Add another</a>
Notice two things – firstly the Movie partial edit view uses standard Html helpers and secondly there is a call to something custom called Html.BeginCollectionItem. *You might even ask yourself: Wait a second. This won’t work, because the partial view will produce names such as “Title” instead of “FavouriteMovies[xxx].Title”, so let me show you the source code of *Html.BeginCollectionItem:
public static IDisposable BeginCollectionItem<TModel>(this HtmlHelper<TModel> html, string collectionName)
{
string itemIndex = Guid.NewGuid().ToString();
string collectionItemName = String.Format("{0}[{1}]", collectionName, itemIndex);
TagBuilder indexField = new TagBuilder("input");
indexField.MergeAttributes(new Dictionary<string, string>() {
{ "name", String.Format("{0}.Index", collectionName) },
{ "value", itemIndex },
{ "type", "hidden" },
{ "autocomplete", "off" }
});
html.ViewContext.Writer.WriteLine(indexField.ToString(TagRenderMode.SelfClosing));
return new CollectionItemNamePrefixScope(html.ViewData.TemplateInfo, collectionItemName);
}
private class CollectionItemNamePrefixScope : IDisposable
{
private readonly TemplateInfo _templateInfo;
private readonly string _previousPrefix;
public CollectionItemNamePrefixScope(TemplateInfo templateInfo, string collectionItemName)
{
this._templateInfo = templateInfo;
_previousPrefix = templateInfo.HtmlFieldPrefix;
templateInfo.HtmlFieldPrefix = collectionItemName;
}
public void Dispose()
{
_templateInfo.HtmlFieldPrefix = _previousPrefix;
}
}
This helper does two things:
Appends a hidden Index field to the output with a random GUID value
(remember that using the .Index pattern an index can be any string)
Scopes the execution of the helper via an IDisposable and sets the
template rendering context (html helperes and display/editor
templates) to be “FavouriteMovies[GUID].”, so we end up with HTML
like this:
Title
This solves the problem of using Html field templates and basically reusing ASP.NET facilities instead of having to write html by hand, but it leads me to the second quirk that we need to address.
Let me show you the second and final problem. Disable client side validation and delete the title of e.g. “Movie 2” and click submit. Validation will fail, because Title of a movie is a required field, but while we are shown the edit form again** there are no validation messages**:
Why is that? It’s the same problem I mentioned earlier in this post. Each time we render the view we assign different names to the fields, which do not match the ones submitted and leads to a *ModelState *inconsistency. We have to figure out how to persist the name and more specifically the Index across requests. We have two options:
Add a hidden CollectionIndex field and CollectionIndex property on the Movie object to persist the FavouriteMovies.Index. This however is intrusive and suboptimal.
Instead of polluting the Movie object with an extra property be smart and in our helper Html.BeginCollectionItem reapply/reuse the submitted FavouriteMovies.Index form values.
Let’s replace in Html.BeginCollectionItem this line:
string itemIndex = Guid.New().ToString();
with:
string itemIndex = GetCollectionItemIndex(collectionIndexFieldName);
And here’ is the code for GetCollectionItemIndex:
private static string GetCollectionItemIndex(string collectionIndexFieldName)
{
Queue<string> previousIndices = (Queue<string>) HttpContext.Current.Items[collectionIndexFieldName];
if (previousIndices == null) {
HttpContext.Current.Items[collectionIndexFieldName] = previousIndices = new Queue<string>();
string previousIndicesValues = HttpContext.Current.Request[collectionIndexFieldName];
if (!String.IsNullOrWhiteSpace(previousIndicesValues)) {
foreach (string index in previousIndicesValues.Split(','))
previousIndices.Enqueue(index);
}
}
return previousIndices.Count > 0 ? previousIndices.Dequeue() : Guid.NewGuid().ToString();
}
We get all submitted values for e.g. “FavouriteMovie.Index” put them in a queue, which we store for the duration of the request. Each time we render a collection item we dequeue its old index value and if none is available we generate a new one. That way we preserve the Index across requests and can have a consistent ModelState and see validation errors and messages:
All that is left is to implement the “Add another” button functionality and we can do that easily by appending a new row to the movie editor, which we can fetch using Ajax and use our existing MovieEntryEditor.cshtml partial view like that:
public ActionResult MovieEntryRow()
{
return PartialView("MovieEntryEditor");
}
And then add the follwing “Add Another” click handler:
$("#addAnother").click(function () {
$.get('/User/MovieEntryRow', function (template) {
$("#movieEditor").append(template);
});
});
Done;
Conclusion
While not immediately obvious editing variable length reorderable collections with standard ASP.NET MVC is possible and what I like about this approach is that:
We can keep using traditional ASP.NET html helpers, editor and display templates (Html.EditorFor, etc.) with in our collection editing
We can make use of the ASP.NET MVC model validation client and server side
What I don’t like that much however is:
That we have to use an AJAX request to append a new row to the editor.
That we need to use the name of the collection in the movie editor partial view, but otherwise when doing the standalone AJAX get request the name context won’t be properly set for the partial template fields.
I would love to hear your thoughts. The sample source code is available on my GitHub
Other way: http://blog.stevensanderson.com/2008/12/22/editing-a-variable-length-list-of-items-in-aspnet-mvc/

Saving "Select Multiple" values in a database

I have from where teachers can select multiple options (each option represents a skill they posses).
The form looks like this
#model Ability.Models.ViewModel
<h2>Add Skill</h2>
<form action="" method="post">
<select multiple name="AddSkillForm">
#foreach (var skill in Model.Skills)
{
<option value="#skill.ID">#skill.SkillName</option>
}
</select>
</form>
As you can see, they can select more then one option.
My database looks like this
Now I was wondering how I can succesfully save the data in my form. I was thinking of using a url that looks like
teacher/addskills/1
Where "1" represents the Teacher_ID and ofcourse the skill_ID's are given though the form.
My question is, how can I pass the multiple values too my controller and save them correctly in my DB?
Looking forward to any help!
As Darin suggested.
The correct way is to use a view model in conjunction with the
Html.ListBoxFor strongly typed helper
I would use a multi select ListBox. The model binder will take care of the rest.
Model view
public class MyModelView
{
public IEnumerable<SelectListItem> Skills{ get; set; }
public string[] SelectedSkills { get; set; } // You could use List<string> instead.
public int TeacherId {get;set;}
}
View
#Html.ListBoxFor(s => s.SelectedSkills ,
new MultiSelectList(Model.Skills, "Value", "Text", Model.SelectedSkills ),
new { #class = "form-control", style = "height:250px; width:100%" })
#Html.HiddenFor(s => s.TeacherId)
Controller
[HttpPost]
public ActionResult Save(MyModelView model)
{
foreach (string skill in model.SelectedSkills){//Selected Skills. Save to database}
}
EDIT
Get skills from database. Based on your database structure you should be able to get all skills per teacher from the bridge table "TeacherSkills". You will need to fill you SelectedSkills property where Skills should have all the available skills.
List<SelectListItem> selectedSkills= service.GetSkillsByTeacherId(teacherId);
if (selectedSkills != null && selectedSkills.Count > 0)
{
model.SelectedSkills = selectedSkills.Select(x => x.Value).ToArray();
}

MVC 4 ViewModel not being sent back to Controller

I can't seem to figure out how to send back the entire ViewModel to the controller to the 'Validate and Save' function.
Here is my controller:
[HttpPost]
public ActionResult Send(BitcoinTransactionViewModel transaction)
{
}
Here is the form in the view:
<li class="check">
<h3>Transaction Id</h3>
<p>#Html.DisplayFor(m => m.Transaction.TransactionId)</p>
</li>
<li class="money">
<h3>Deposited Amount</h3>
<p>#Model.Transaction.Amount.ToString() BTC</p>
</li>
<li class="time">
<h3>Time</h3>
<p>#Model.Transaction.Time.ToString()</p>
</li>
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post, new { transaction = Model }))
{
#Html.HiddenFor(m => m.Token);
#Html.HiddenFor(m => m.Transaction.TransactionId);
#Html.TextBoxFor(m => m.WalletAddress, new { placeholder = "Wallet Address", maxlength = "34" })
<input type="submit" value="Send" />
#Html.ValidationMessage("walletAddress", new { #class = "validation" })
}
When i click on submit, the conroller contains the correct value of the walletAddress field but transaction.Transaction.Time, transaction.Transaction.Location, transaction.Transaction.TransactionId are empty.
Is there a way i could pass the entire Model back to the controller?
Edit:
When i dont even receive the walletAddress in the controller. Everything gets nulled!
When i remove this line alone: #Html.HiddenFor(m => m.Transaction.TransactionId);
it works and i get the Token property on the controller, but when i add it back, all the properties of the transaction object on the controller are NULL.
Here is the BitcoinTransactionViewModel:
public class BitcoinTransactionViewModel
{
public string Token { get; set; }
public string WalletAddress { get; set; }
public BitcoinTransaction Transaction { get; set; }
}
public class BitcoinTransaction
{
public int Id { get; set; }
public BitcoinTransactionStatusTypes Status { get; set; }
public int TransactionId { get; set; }
public decimal Amount { get; set; }
public DateTime Time { get; set; }
public string Location { get; set; }
}
Any ideas?
EDIT: I figured it out, its in the marked answer below...
OK, I've been working on something else and bumpend into the same issue all over again.
Only this time I figured out how to make it work!
Here's the answer for anyone who might be interested:
Apparently, there is a naming convention. Pay attention:
This doesn't work:
// Controller
[HttpPost]
public ActionResult Send(BitcoinTransactionViewModel transaction)
{
}
// View
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post, new { transaction = Model }))
{
#Html.HiddenFor(m => m.Token);
#Html.HiddenFor(m => m.Transaction.TransactionId);
.
.
This works:
// Controller
[HttpPost]
public ActionResult Send(BitcoinTransactionViewModel **RedeemTransaction**)
{
}
// View
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post, new { **RedeemTransaction** = Model }))
{
#Html.HiddenFor(m => m.Token);
#Html.HiddenFor(m => m.Transaction.TransactionId);
.
.
In other words - a naming convention error! There was a naming ambiguity between the Model.Transaction property and my transaction form field + controller parameter. Unvelievable.
If you're experiencing the same problems make sure that your controller parameter name is unique - try renaming it to MyTestParameter or something like this...
In addition, if you want to send form values to the controller, you'll need to include them as hidden fields, and you're good to go.
The signature of the Send method that the form is posting to has a parameter named transaction, which seems to be confusing the model binder. Change the name of the parameter to be something not matching the name of a property on your model:
[HttpPost]
public ActionResult Send(BitcoinTransactionViewModel model)
{
}
Also, remove the htmlAttributes parameter from your BeginForm call, since that's not doing anything useful. It becomes:
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post))
Any data coming back from the client could have been tampered with, so you should only post back the unique ID of the transaction and then retrieve any additional information about it from your data source to perform further processing. You'll also want to verify here that the user posting the data has access to the specified transaction ID since that could've been tampered with as well.
This isn't MVC specific. The HTML form will only post values contained within form elements inside the form. Your example is neither inside the form or in a form element (such as hidden inputs). You have to do this since MVC doesn't rely on View State. Put hidden fields inside the form:
#Html.HiddenFor(x => x.Transaction.Time)
// etc...
Ask yourself though.. if the user isn't updating these values.. does your action method require them?
Model binding hydrates your view model in your controller action via posted form values. I don't see any form controls for your aforementioned variables, so nothing would get posted back. Can you see if you have any joy with this?
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post, new { transaction = Model }))
{
#Html.TextBoxFor(m => m.WalletAddress, new { placeholder = "Wallet Address", maxlength = "34" })
#Html.Hidden("Time", Model.Transaction.Time)
#Html.Hidden("Location", Model.Transaction.Location)
#Html.Hidden("TransactionId", Model.Transaction.TransactionId)
<input type="submit" value="Send" />
#Html.ValidationMessage("walletAddress", new { #class = "validation" })
}
Try to loop with the folowing statement not with FOREACH
<table>
#for (var i = 0; i < Model.itemlist.Count; i++)
{
<tr>
<td>
#Html.HiddenFor(x => x.itemlist[i].Id)
#Html.HiddenFor(x => x.itemlist[i].Name)
#Html.DisplayFor(x => x.itemlist[i].Name)
</td>
</tr>
}
</table>
Try Form Collections and get the value as. I think this may work.
public ActionResult Send(FormCollection frm)
{
var time = frm['Transaction.Time'];
}
Put all fields inside the form
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post))
and make sure that the model
BitcoinTransactionViewModel
included in view or not?
Can you just combine those 2 models you have? Here's how I do it with one model per view...
1. I use Display Templates from view to view so I can pass the whole model as well as leave data encrypted..
2. Setup your main view like this...
#model IEnumerable<LecExamRes.Models.SelectionModel.GroupModel>
<div id="container">
<div class="selectLabel">Select a Location:</div><br />
#foreach (var item in Model)
{
#Html.DisplayFor(model=>item)
}
</div>
3. Create a DisplayTemplates folder in shared. Create a view, naming it like your model your want to pass because a DisplayFor looks for the display template named after the model your using, I call mine GroupModel. Think of a display template as an object instance of your enumeration. Groupmodel Looks like this, I'm simply assigning a group to a button.
#model LecExamRes.Models.SelectionModel.GroupModel
#using LecExamRes.Helpers
#using (Html.BeginForm("Index", "Home", null, FormMethod.Post))
{
<div class="mlink">
#Html.AntiForgeryToken()
#Html.EncryptedHiddenFor(model => model.GroupKey)
#Html.EncryptedHiddenFor(model => model.GroupName)
<p>
<input type="submit" name="gbtn" class="groovybutton" value=" #Model.GroupKey ">
</p>
</div>
}
4. Here's the Controller.
*GET & POST *
public ActionResult Index()
{
// Create a new Patron object upon user's first visit to the page.
_patron = new Patron((WindowsIdentity)User.Identity);
Session["patron"] = _patron;
var lstGroups = new List<SelectionModel.GroupModel>();
var rMgr = new DataStoreManager.ResourceManager();
// GetResourceGroups will return an empty list if no resource groups where found.
var resGroups = rMgr.GetResourceGroups();
// Add the available resource groups to list.
foreach (var resource in resGroups)
{
var group = new SelectionModel.GroupModel();
rMgr.GetResourcesByGroup(resource.Key);
group.GroupName = resource.Value;
group.GroupKey = resource.Key;
lstGroups.Add(group);
}
return View(lstGroups);
}
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult Index(SelectionModel.GroupModel item)
{
if (!ModelState.IsValid)
return View();
if (item.GroupKey != null && item.GroupName != null)
{
var rModel = new SelectionModel.ReserveModel
{
LocationKey = item.GroupKey,
Location = item.GroupName
};
Session["rModel"] = rModel;
}
//So now my date model will have Group info in session ready to use
return RedirectToAction("Date", "Home");
}
5. Now if I've got alot of Views with different models, I typically use a model related to the view and then a session obj that grabs data from each model so in the end I've got data to submit.
The action name to which the data will be posted should be same as the name of the action from which the data is being posted. The only difference should be that the second action where the data is bein posted should have [HttpPost] and the Posting method should serve only Get requests.

No post values when using html helpers

I have a problem in mvc3. Im not sure this is specifically mvc3 but, Im currently using that with razor engine. Anyway, the problem that I am facing is, I have a form, I use the TextBoxFor, CheckBoxFor, etc. to render it. The rendering is working flawless, except, when I try to post the data, I basically post an empty form with null values.
Here is my model:
public class SendReplyPmForm : PM
{
public new string Text { get; set; }
public bool IsOriginalDelete { get; set; }
public int ReplyNr { get; set; }
public string ReceiverName { get; set; }
}
I have an extra viewmodel layer between the view and the model and it contains an extra paramater regarding this model
public class IndexViewModel
{
public SendReplyPmForm SendReplyPmForm { get; set; }
...
here is my view
#using (Html.BeginForm("SendReply", "Pm", FormMethod.Post, new { id = "formSendMsg" }))
{
#Html.TextBoxFor(model => model.SendReplyPmForm.ReceiverName, new { id = "ReceiverName" })
<span id="spanChkText">Delete Original Message: #Html.CheckBoxFor(model => model.SendReplyPmForm.IsOriginalDelete, new { id = "chkIsOriginalDelete", value = 1 })</span>
#{Html.RenderPartial("~/Areas/Forums/Views/Shared/Toolbar.cshtml");}
<span class="spanLabel">Message</span>
#Html.TextAreaFor(model => model.SendReplyPmForm.Text, new { id = "Text", rows = "10", cols = "65" })
#{Html.RenderPartial("temp.cshtml");}
#Html.HiddenFor(model => model.SendReplyPmForm.ReplyNr, new { id = "inputReplyNr", value = 0 })
<input type="submit" value="Send" />
}
and here i have the controller
[HttpPost]
public ActionResult SendReply(SendReplyPmForm SendReplyForm) {
var ViewModel = new IndexViewModel();
.
.
.
return View("Index", ViewModel);
}
The strange thing is, if I use pure HTML instead of the Html helpers, then the post is going smoothly without any problem.
I read this article (ASP.NET MVC’s Html Helpers Render the Wrong Value!) before I post this, but im not quite sure, that I have the same issue. (f.e.: I dont have action in my controller, that has the same name), but the fact that is also working with pure Html that makes me think.
Do you guys have any idea, how can I use this form with the Html helpers?
I believe your problem is the same as here, so you need to add a prefix:
[HttpPost]
public ActionResult SendReply([Bind(Prefix="SendReplyPmForm")]SendReplyPmForm SendReplyForm)
{
...
}

Categories