rename mvc model object in Razor View - asp.net-mvc

I'm using MVC 4 and usually Visual Studio will create all the views for you. I have one form that just has one field and I want to just embed the create form into the Index View.
So the Index View has something like #model IEnumerable<Models.LinkModel>
So I access it by iterating through the Model collection.
But if I try to embed the form for the create action I need #model Models.LinkModel
and it is accessed by Model.Name as well. Is there a way to do this or use a different variable name?
Ok here is some extra info.
SO I have a model.
public class LinkModel
{
public string LinkUrl {get;set;}
}
I have a controller that has the Create and Index ActionResults.
Now in the Index view I have
#model IEnumerable<Models.LinkModel>
#{
ViewBag.Title = "Links";
}
I can do all my fancy logic to list all the links.
#foreach(link in Model)
{
<p>link.LinkUrl<p>
}
The Create View has this
#model Models.LinkModel // Note that it is just one item not IEnumerable
#{
ViewBag.Title = "Add Link";
}
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset class="editor-fieldset">
<legend>LinkModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.LinkUrl)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.LinkUrl)
</div>
<p>
<input type="submit" value="Add Link" />
</p>
</fieldset>
}
Now it seems pretty stupid to have a create form for just one field. I want to put this form on the Index page. Problem is that I access the object using the variable Model. I wanted to know if there is a way to have two seperate instances or be able to access the Model objects with different names.

Have a composite model with a list of items and 1 single item for the create
public class IndexModel {
public LinkModel CreateModel {get; set;}
public IEnumerable<LinkModel> Items {get; set;}
}
#model IndexModel
#using(Html.BeginForm("create")) {
#Html.EditorFor(m => m.CreateModel.Name);
}
#foreach(var item in Model.Items) {
#item.Name
}

Related

List Binding with model data

So I have a form that I am trying to submit and I can get either the list or the model to bind, but not both at the same time. I suspect it has to do with the model binder.
HTML
#using (Html.BeginForm("Index", "Home", FormMethod.Post)){
#Html.AntiForgeryToken()
<div class="TransferHeader">
<div>
#Html.LabelFor(model => model.tranRequestedBy)
#Html.EditorFor(model => model.tranRequestedBy, new { #Name = "h.tranRequestedBy" })
</div>
<div>
#Html.LabelFor(model => model.tranNotes)
#Html.EditorFor(model => model.tranNotes, new { #Name = "h.tranNotes" })
</div>
<input name="h.TransfersDetail.Index" id="detIndex" type="hidden" value="c3a3f7dd-41bb-4b95-b2a6-ab5125868adb">
<input name="h.TransfersDetail[c3a3f7dd-41bb-4b95-b2a6-ab5125868adb].detToolCode" id="detToolCode" type="hidden" value="1234">
</div>
}
Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(TransfersHeader h)
{
return View();
}
Model Class:
public virtual ICollection<TransfersDetail> TransfersDetail { get; set; }
public string tranRequestedBy { get; set; }
public string tranNotes { get; set; }
The two bottom inputs were generated from an AJAX call to an add method, what happens is if they are not present the two HTML helper editors will come in the model, but if they do exist only the transfer detail list will appear.
Is there anything I could do to make sure all of the data comes into the model?
Its not clear how you are generating those inputs, but the name attributes are incorrect. You model does not contain a collection property named h, but it does contain one named TransfersDetail, so your inputs need to be
<input name="TransfersDetail.Index" type="hidden" value="c3a3f7dd-41bb-4b95-b2a6-ab5125868adb">
<input name="TransfersDetail[c3a3f7dd-41bb-4b95-b2a6-ab5125868adb].detToolCode" type="hidden" value="1234">
Its also not clear why your adding an id attribute (if you referencing collection items in jQuery, you would be better off using class names and relative selectors), but the id your using does not have an indexer suggesting that your going to be generating duplicate id attributes which is invalid html (and jQuery selectors would not work in any case)

Razor checkboxfor not working as expected

There are razor checkbox controls in application which needs to be repeated for each of the collection. But for the collection number second onwards below code passes nothing as value for checkbox:
<div class="checkbox">
<label class="checkbox-inline">
#Html.CheckBoxFor(m => m.Collection[i].Item)Some Label
</label>
</div>
The viewmodel is:
public class Items{
public List<Collection> Collection{get; set;}
}
public class Collection{
public bool Item { get; set; }
}
Assuming your HttpPost action's parameter is an object of
[HttpPost]
public ActionResult Create(Items model)
{
//to do : Save and Redirect
}
You need to make sure that the checkboxes in your form will have the name matching to your ViewModel property hierarchy. So , for model binding to work, you need to have your checkboxes with names like this
<input name="Collection[1].Item" type="checkbox" >
So in your view, Make sure you manipulate the name like that
#model Items
#using (Html.BeginForm())
{
for (int index = 0; index < Model.Collection.Count; index++)
{
var collection = Model.Collection[index];
#Html.CheckBox("Collection["+index+"].Item",collection.Item)
}
<input type="submit"/>
}
Another (better) option is to use Editor Templates. With this approach, you do not need to manipulate the form field name. Here is a complete post which explains the step by step
Instead of Item write selected
<div class="checkbox">
<label class="checkbox-inline">
#Html.CheckBoxFor(m => m.Collection[i].selected)Some Label
</label>
</div>
Or try this
#Html.CheckBoxFor(m => m[i].Checked,new {Style ="vertical-align})

How to avoid view code duplication in MVC?

I've made a very simple mockup of my problem.
If i have a complex person viewModel:
public class PersonViewModel
{
public string Name { get; set; }
public List<DogViewModel> Dogs { get; set; }
}
public class DogViewModel
{
public string Name { get; set; }
}
And i have 2 views : to edit person and create a person :
#model Custom.Models.PersonEditViewModel
#{
ViewBag.Title = "Edit";
}
<h2>Edit Person</h2>
<div class="PersonDataArea">
//this contain identical html in Create and Edit view
</div>
<div class="SelectDogAre">
//this contain identical html in Create and Edit view
</div>
<input type="button" value="save" />
#model Custom.Models.PersonCreateViewModel
#{
ViewBag.Title = "Create";
}
<h2>Create Person</h2>
<div class="PersonDataArea">
//this contain identical html in Create and Edit view
</div>
<div class="SelectDogAre">
//this contain identical html in Create and Edit view
</div>
<input type="button" value="save"/>
And of course 2 ViewModels for each view :
public class PersonEditViewModel
{
public PersonViewModel Person { get; set; }
}
public class PersonCreateViewModel
{
public PersonViewModel Person { get; set; }
}
Question :
as you can see i have 2 identical areas in Create and Edit views, and i want to avoid to suplicate code. I cant use Partial views because model in partial view will not be bound on post. How to avoid duplication of the view code??
You definitely need to use a Partial View.
First of all, you don't need two separate View Models. Just the PersonViewModel is enough.
Then you'll create a partial view _PersonEdit.cshtml (by the way you are missing the form in your code):
#model Custom.Models.PersonViewModel
#using(Html.BeginForm())
{
<div class="PersonDataArea">
//this contain identical html in Create and Edit view
</div>
<div class="SelectDogAre">
//this contain identical html in Create and Edit view
</div>
<input type="button" value="save"/>
}
And, two Views for Create and Edit:
Create:
#{
ViewBag.Title = "Create";
}
<h2>Create Person</h2>
#Html.Partial("_PersonEdit", Model)
Edit:
#{
ViewBag.Title = "Edit";
}
<h2>Edit Person</h2>
#Html.Partial("_PersonEdit", Model)
Alternatively, you can create only one view for both actions, and pass the ViewBag.Title from the controller to the view.

How can i maintain objects between postbacks?

I'm not so experienced using MVC. I'm dealing with this situation. Everything works well until call the HttpPost method where has all its members null. I don't know why is not persisting all the data on it.
And everything works well, because I can see the data in my Html page, only when the user submit the information is when happens this.
[HttpGet]
public ActionResult DoTest()
{
Worksheet w = new Worksheet(..);
return View(w);
}
[HttpPost]
public ActionResult DoTest(Worksheet worksheet)
{
return PartialView("_Problems", worksheet);
}
This is class which I'm using.
public class Worksheet
{
public Worksheet() { }
public Worksheet(string title, List<Problem> problems)
{
this.Title = title;
this.Problems = problems;
}
public Worksheet(IEnumerable<Problem> problems, WorksheetMetadata metadata, ProblemRepositoryHistory history)
{
this.Metadata = metadata;
this.Problems = problems.ToList();
this.History = history;
}
public string Title { get; set; }
public List<Problem> Problems { get; set; } // Problem is an abstract class
public WorksheetMetadata Metadata { get; set; }
public ProblemRepositoryHistory History { get; set; }
}
And my razor view.... the razor view shows successfully my view. I realized something rare, please note in my 5 and 6 lines that I have HiddenFor method, well if I used that, when calls HTTPPOST persists the data, I don't know why.
#model Contoso.ExercisesLibrary.Core.Worksheet
<div id="problemList">
<h2>#Html.DisplayFor(model => model.Metadata.ExerciseName)</h2>
#Html.HiddenFor(model => model.Metadata.ExerciseName)
#Html.HiddenFor(model => model.Metadata.ObjectiveFullName)
#for (int i = 0; i < Model.Problems.Count; i++)
{
<div>
#Html.Partial(Contoso.ExercisesLibrary.ExerciseMap.GetProblemView(Model.Problems[i]), Model.Problems[i])
</div>
}
</div>
UPDATE
I'm using a static class to get the view name, but as I'm testing I'm just using this Partial view
#model Contoso.ExercisesLibrary.AbsoluteArithmetic.Problem1
<div>
<span style="padding:3px; font-size:18px;">#Model.Number1</span>
<span style="padding:5px; font-size:18px;">+</span>
<span style="padding:5px; font-size:18px;">#Model.Number2</span>
<span style="padding:5px; font-size:18px;">=</span>
<span style="font-size:18px">
#Html.EditorFor(model => model.Result, new { style = "width:60px; font-size:18px;" })
#Html.ValidationMessageFor(model => model.Result)
</span>
</div>
#section Scripts {
}
And here the user do the post
#model Contoso.ExercisesLibrary.Core.Worksheet
<form method="post">
#Html.Partial("_Problems", Model)
<input type="submit" value="Continue" />
</form>
The Model Binder will 'bind' or link input fields on your view to the model. It will not bind display fields (like label), that is why you need the HiddenFor it will add an <input type="hidden" which will then be bound to the Model when you Post.
You can use 'TempData'. It is used to pass data from current request to subsequent request means incase of redirection.
This link also helps you.
TempData
SO Tempdata
Make sure your form tag looks like the following, for instance the controller name, action method, the form method and an id for the form. I am referring to the #using statement. In my case the controller name is RunLogEntry, the action method is Create and the id is form.
Normal Post from View to Controller
#using (Html.BeginForm("Create", "RunLogEntry", FormMethod.Post, new { id = "form", enctype = "multipart/form-data" }))
{
<div id="main">
#Html.Partial("_RunLogEntryPartialView", Model)
</div>
}
If you want to post via Jquery, could do the following:
$.post("/RunLogEntry/LogFileConfirmation",
$("#form").serialize(),
function (data) {
//this is the success event
//do anything here you like
}, "html");
You must specify a form with correct attribute in your view to perform post action
<form action="Test/DoTest" method="post">
...
</form>
or
#using(Html.BeginForm("DoTest", "Test", FormMethod.Post)) {
...
}
The second is recommended.
Put your entire HTML code under:
#using(Html.BeginForm())
tag.

Passing Data across multiples views using mvc 3 and EF

maybe this question is seen repeatedly around here but i was not able to find a answers.
my project is about reservations for hotels. I have a class Reservation witch has a Icollection of ChoosenRooms and a class that represents de User making the reservation, and other stuff like dates and other stuff.
The process is this:
In my first view I get the chosen rooms, dates, etc, then i pass that to my second view where i´m going to get the user info, and then i have a third view where i want to show all the gathered information so the user can finally click a button to save the data.
My problem is that i need to pass the reservation object class across all these views. In my testing i see that primitive types pass just fine BUT The iColletion of ChoosenRooms is lost when i post back from the view to the next controller action.
can someone post some example how to, Posting back from a view to a controller, complex types like ChoosenRooms inside another class Reservations, are not lost in the process?
Or maybe explain why this info is lost?
the code:
public class Reserva
{
public virtual int ID { get; set; }
[NotMapped]
public string[] q { get; set; }
[Display(Name = "Cliente")]
public virtual Utilizador utilizador { get; set; }
[Display(Name = "Quarto")]
public virtual ICollection<Quartos> ChoosenRooms{ get; set; }
[Display(Name = "Serviços Adicionais")]
public virtual ICollection<ReservasItens> itens { get; set; }
The view
#model SolarDeOura.Models.Reserva
#{
ViewBag.Title = "AddReservaUser";
var _reserva = TempData["reserva"] as Reserva;
}
<h2>AddReservaUser</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Reserva</legend>
<div class="editor-label">
#Html.LabelFor(model => model.dtEntrada)
</div>
<div class="editor-field">
#Html.DisplayFor(model => model.dtEntrada)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.dtSaida)
</div>
<div class="editor-field">
#Html.DisplayFor(model => model.dtSaida)
</div>
<div class="editor-label">
#Model.q.Count() Choosen Rooms
</div>
#foreach (var q in Model.ChoosenRooms)
{
<ul>
<li>
#Html.DisplayFor(modelitem => q.descricao)
</li>
</ul>
}
posting back from here is the Problem. In this view " #foreach (var q in Model.ChoosenRooms)" has data but posting back the data is lost.
The concept of model binder at this point was not very clear to me and some knowledge about this topic is all it takes to solve the question.
In resume:
The view gets a model which is a complex type: class [reserva] has a collection of [ChoosenRooms] which is also a complex type.
The line #Html.DisplayFor(modelitem => q.descricao) renders the necessary html elements to display the data, but not the necessary html to be posted back to the controller (input element or hidden field ) so the model binder fails.
Also the controller (post) action argument didn't had the property name that matches the field, in this case it needed to be a String[] type since its a collection of values.
I would also recommend reading about Display Templates and Editor Templates.

Resources