Unable to Post Dropdown Values Added at Runtime in ASP.NET MVC - asp.net-mvc

For some reason I'm having trouble getting option values I add to an ASP.NET MVC dropdown at runtime to post.
In this case, I'm adding options to the dropdown at runtime using jquery.
This is what I have so far:
The Razor HTML:
#Html.DropDownListFor(sc => sc.SelectedComponents, Enumerable.Empty<SelectListItem>(), new { #class = "form-control", #id = "SelectedComponents", #name= "SelectedComponents", size = "5", multiple = "multiple" })
The relevant portion of the model:
public IEnumerable<string> SelectedComponents { get; set; }
Also, I can't get the posted values added at runtime to appear in the Forms Collection when I look in the controller action.
Can anyone suggest the best way to handle this particular situation?
Thanks much,
Pete
More information:
I'm trying to implement a simple solution using "BeginCollectionItem" to post added items at runtime when I submit the Razor Form.
In the main view I have this to display the partial view:
#foreach (var components in Model.selectedComponents)
{
#Html.Partial("SelectedComponents", Model)
}
The Partial View looks like this:
#model blah.blah.RequestViewModel
#using(Html.BeginCollectionItem("selectedComponents"))
{
<div class="form-group">
<label for="Component" class="control-label col-xs-12 col-sm-3">Selected Components:</label>
<div class="col-xs-12 col-sm-6">
#Html.DropDownListFor(model => model.selectedComponents, Enumerable.Empty<SelectListItem>(), new { #class = "form-control", #id = "SelectedComponents", #name = "SelectedComponents", size = "5", multiple = "multiple" })
</div>
<div class="col-xs-0 col-sm-3">
</div>
</div>
}
The relevant portion of the viewmodel looks like this:
public class RequestViewModel
{
public IEnumerable<string> selectedComponents { get; set; }
}
I simply want to post a list of strings that are added at runtime using jquery. This part is working and is not shown.
The controller that I'm posting to looks like this:
public ActionResult Create(HttpPostedFileBase[] files, RequestViewModel data)
{
}

Related

Asp.Net Razor View Passing Expression To Partial

I find myself writing this a whole lot in my views:
<div class="form-group">
#Html.LabelFor(x => x.City)
#Html.EditorFor(x => x.City)
#Html.ValidationMessageFor(x => x.City)
</div>
I'd really like to put this in a Partial _Field.cshtml, something like this:
#model //what model type???
<div class="form-group">
#Html.LabelFor(Model)
#Html.EditorFor(Model)
#Html.ValidationMessageFor(Model)
</div>
That could then be called by:
#Html.Partial("_Field", x => x.City)
What would the #model type in my partial be if I wanted to accomplish something like this?
UPDATE This works, but I'd rather use a partial for ease of changing the template:
public static MvcHtmlString Field<TModel, TItem>(this HtmlHelper<TModel> html, Expression<Func<TModel, TItem>> expr)
{
var h = "<div class='form-group'>";
h += $"{html.LabelFor(expr)}{html.EditorFor(expr)}{html.ValidationMessageFor(expr)}";
h += "</div>";
return MvcHtmlString.Create(h);
}
That's not possible. However, what you want is very similar to editor templates. Essentially, you just create a view in Views/Shared/EditorTemplates named after one of the following conventions:
A system or custom type (String.cshtml, Int32.cshtml, MyAwesomeClass.cshtml, etc.)
One of the members of the DataType enum (EmailAddress.cshtml, Html.cshtml, PhoneNumber.cshtml, etc.). You would then apply the appropriate DataType attributes to your properties:
[DataType(DataType.EmailAdress)]
public string Email { get; set; }
Any thing you want, in conjunction with the UIHint attribute:
[UIHint("Foo")]
public string Foo { get; set; }
Which would then correspond to a Foo.cshtml editor template
In your views, then, you simply use Html.EditorFor:
#Html.EditorFor(x => x.City)
Then, for example, you could have Views/Shared/EditorTemplates/String.cshtml as:
<div class="form-group">
#Html.Label("", new { #class = "control-label" })
#Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, new { #class = "form-control" })
#Html.ValidationMessage("")
</div>
(The empty quotes are placeholders. Razor will automatically fill in the appropriate property name, thankfully.)
Calling EditorFor, then, will print all of this, rather than just the default text input. You can take this much further, as well. I have some articles on my blog that goes into greater detail, if you're interested.
UPDATE
It's worth mentioning a few features of EditorFor:
You can pass a template directly to the call, meaning you can customize what template is used on the fly and per instance:
#Html.EditorFor(x => x.City, "MyCustomEditorTemplate")
You can pass additionalViewData. The members of this anonymous object are added to the ViewData dynamic dictionary. Potentially, you could use this to branch within your editor template to cover additional scenarios. For example:
#Html.EditorFor(x => x.City, new { formGroup = false })
Then in your editor template:
#{ var formGroup = ViewData["formGroup"] as bool? ?? true; }
#if (formGroup)
{
<!-- Bootstrap form group -->
}
else
{
<!-- Just the input -->
}

Master-Detail created in one View

I have a Model with Child model.
[Table("Personnel")]
public class Personnel
{
[Key]
public int Id { get; set; }
[MaxLength(10)]
public string Code { get; set; }
[MaxLength(20)]
public string Name { get; set; }
public virtual List<PersonnelDegree> Degrees
{
get;
set;
}
}
public class PersonnelDegree
{
[Key]
public int Id { get; set; }
[ForeignKey("Personnel")]
public int PersonnelId { get; set; }
public virtual Personnel Personnel { get; set; }
[UIHint("Enum")]
public Degree Degree { get; set; }
public string Major { get; set; }
public string SubField { get; set; }
public string Location { get; set; }
}
I want to created a view for this.(Add)
I added pesonnel field to view, but how to add items for PersonnelDegree?
#using (Html.BeginForm("Add", "Personnel", FormMethod.Post, new {enctype = "multipart/form-data", #class = "form-horizontal tasi-form", id = "default"}))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, null, new {#class = "alert alert-danger "})
<div class="form-group">
#Html.LabelFor(m => m.Code, new {#class = "control-label col-lg-1"})
<div class="col-lg-3">
#Html.TextBoxFor(m => m.Code, null, new {#class = "form-control", maxlength = 10})
#Html.ValidationMessageFor(m => m.Code)
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Name, new {#class = "control-label col-lg-1"})
<div class="col-lg-3">
#Html.TextBoxFor(m => m.Name, new {#class = "form-control", maxlength = 20})
#Html.ValidationMessageFor(m => m.Name)
</div>
#Html.LabelFor(m => m.Family, new {#class = "control-label col-lg-1"})
<div class="col-lg-3">
#Html.TextBoxFor(m => m.Family, null, new {#class = "form-control", maxlength = 30})
#Html.ValidationMessageFor(m => m.Family)
</div>
</div>
Can i add multy PersonnelDegrees in this View?
Edit
I add a div in view for Degrees
<div id="Degrees">
<div id="NewDegree" style="display:none">
<div class="form-group">
<input class="form-control" id="Degrees[#].Major" name="Degrees[#].Major" value="" type="text">
// another items
</div>
</div>
</div>
and in javascript :
$(document).ready(function() {
$(function() {
$("#addItem").click(function () {
var index = $('#Degrees tbody tr').length; // assumes rows wont be deleted
var clone = $('#NewDegree').html();
// Update the index of the clone
clone.replace(/\[#\]/g, '[' + index + ']');
clone.replace(/"%"/g, '"' + index + '"');
$('#Degrees').append(clone);
});
);
});
it add a div ,But after a few seconds hide div and refresh page.
Yes, you can. There are several options how to do it:
Use Js grid-like component
Use some js grid component, i prefer jqgrid you can add data localy on your View with it and then serialize it on form POST to controller.
Advantage: You don't need to write js CRUD operations with your grid your self the only thing that you should get is how to serialize local data right way to controller.
Disadvantage: You should learn how component works and could be that some component not easy emplimentable in MVC project (I mean you could lost your model validation, data annotation etc. on client side)
Add markup with js
Write your own js to resolve this issue. Here is good basic example how to do it. The idea that you generate html with js(get js from controller) and add it to your View.
Advantage: You could do what ever you want is you know js.
Disadvantage: You lost your model validation, data annotation etc. on client side.
Use PartialView with js
Get markup from Controller with Ajax (PartialView for your PersonnelDegree) and attach it to your View with js.
Advantage: You can use all ViewModel advandages (DataAnnotations) plus you can add some tricki logic in your CRUD controller methods if you need. Also it's the most easy maintainable solution if your Project is big and have losg life cicle.
Disadvantage: You should learn how to init client side validation when markup comes from ajax call. Also usually this approach force you to write a lot of code.
I prefer last option if i have a time for this.
you can add items for PersonnelDegree using partial views.
for adding multy PersonnelDegrees in this View you need to create objects in the controller
Personnel pers = new Personnel();
PersonnelDegrees pr_obj = new PersonnelDegrees ();
ind_obj.PersonnelDegrees .Add(pr_obj );
PersonnelDegrees pr_obj1 = new PersonnelDegrees ();
ind_obj.PersonnelDegrees .Add(pr_obj1 );

MVC 4 MetaClass Markup not showing up in Partial View

I have a View that loads a few partial views at load time depending on a database. It's working great so far with one exception: because this is a database first situation, I'm using a meta class to add the appropriate markup to the model fields. It worked perfectly fine when I had all the stuff on a single page but since I broke it out into partials, it refuses to recognize the meta class anymore.
There's a lot of unrelated code everywhere so I've broken out the important stuff (some names have been changed to protect the innocent)...
In the view to load said partials:
foreach (var p in MyProject.Controllers.UtilityController.GetPViews())
{
#Html.Partial("../Partial/" + p.ViewName, new ViewDataDictionary { { "column", "column" + p.ColumnSize } })
}
The main part of the meta class:
namespace MyProject.Models
{
public class MyProjectData_Meta
{
[MetadataType(typeof(MyProjectData_Meta))]
public partial class MyProjectData
{
public class MyProjectData_Meta
{
//Gets and sets and stuff. For example:
[Required(ErrorMessage = "Please enter a first name.")]
[Display(Name = "First Name")]
[UIHint("FloatLabel")]
public string MemberFirstName { get; set; }
}
}
}
}
And finally the code that pulls the list from said database...
public static List<ModuleList> GetPViews()
{
MyProjectEntities pv = new MyProjectEntities();
List<ModuleList> lvl = pv.ModuleLists.OrderBy(s => s.PageOrder).ToList();
return lvl;
}
So is there some glaring detail I'm missing or more information you need from me in order to tell me where I'm being stupid?
EDIT: Doh! Forgot to put in my actual view code:
#model MyProject.Models.MyProjectData
<div class="#ViewBag.column">
<fieldset id="fsBasic">
<legend>Basic Information</legend>
<div class="editor-label">
#Html.LabelFor(model => model.MemberFirstName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.MemberFirstName)
#Html.ValidationMessageFor(model => model.MemberFirstName)
</div>
</div>
To answer the other question: I know it's not using the meta class because the LabelFor is showing "MemberFirstName" instead of "First Name" which is what it does actually do when I'm not using a partial class.

One variable in model becomes null after http post

I'm having a problem with my model losing one of its values when submitted in a http post form.
The controller methods:
[HttpGet]
public ActionResult AddTeam(int id)
{
return PartialView("_AddTeam", ViewModelGenerator.TeamFormModel(id));
}
[HttpPost]
public ActionResult AddTeam(TeamFormModel model)
{
TournamentTeamService.CreateTeam(model);
return RedirectToAction("Index", "Tournament", null);
}
The form model:
public class TeamFormModel
{
public TeamViewModel Team { get; set; }
public TournamentViewModel Tournament { get; set; }
public List<PlayerViewModel> Players { get; set; }
}
This function is called from the controller, it creates a new instance of the form model and gets the Tournament from the database and puts it into a view model
public static TeamFormModel TeamFormModel(int id)
{
var _db = new DbContext();
var model = new TeamFormModel();
var tempModel = new TournamentViewModel();
var temp = (from t in _db.tournament
where t.Id == id
select t).SingleOrDefault();
tempModel.Id = temp.Id;
tempModel.Name = temp.Name;
tempModel.SignupStartDate = temp.SignupStartDate;
tempModel.SignupEndDate = temp.SignupEndDate;
tempModel.StartDate = temp.StartDate;
tempModel.EndDate = temp.EndDate;
tempModel.Description = temp.description;
tempModel.TournamentType = temp.TournamentType;
model.Players = new List<PlayerViewModel>();
model.Tournament = tempModel;
return model;
}
Here's the view, the code with the inputs for the player list is missing, it's because it is appendedTo the player-forms div via jquery and it works perfectly so i didn't consider it relevant in this case.
#model Context.ViewModels.TeamFormModel
<script src="~/Scripts/jquery-1.8.2.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
#using (Html.BeginForm("AddTeam", "Tournament", FormMethod.Post, new { enctype = "multipart/form-data", #class = "form-horizontal" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<h2>Registering a team to #Model.Tournament.Name </h2>
<div class="control-group">
#Html.LabelFor(model => model.Team.Name)
<div class="control">
#Html.EditorFor(model => model.Team.Name)
</div>
<a class="add-player-form" href="javascript:void(0)">Add a player</a>
<div id="player-forms">
</div>
</div>
<div class="control-group">
<div class="control" style="clear: both;">
<input type="submit" value="Register" />
</div>
</div>
}
Before i submit this form, the TeamFormModel is perfectly set up, all values are inserted and i only need to save everything to DB. However when it goes back to the controller, the tournament variable in TeamFormModel is null. So when i send it to a function that changes it to a Db model and submits it to db fails everytime.
I know there are ways around the problem like only keeping the id of the tournament instead of the whole model and then get it from db after the form is submitted but it really bugs me that it behaves this way.
I wasn't able to find anything on this specific issue here on stackoverflow, there were a few similar questions but nothing that presented it self the same way.
you have to set something like this
#Html.HiddenFor(x => x.TournmentID);
something has to retain the value of the model that was passed into the view while the view is rendered, this will pass the value back to the controller on post

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.

Resources