Loop of checkboxes to add objects to model list - asp.net-mvc-5

I have a model (called plan) which one of its properties is a list (ICollection) of Exercises (which is another model):
public int Id { get; set; }
public virtual ICollection<Exercise> Exercises { get; set; }
So i tried to create a view that creates a plan and another view to add Exercises to the plan from exercises in the database.
So i did a loop that ranges all the exercises from the DB and for each one i added a check box with and id same as the id of the exercise and i thought i could do something with it but i tried so many things and ways and i couldn't even sent the input of the check boxes to the controller.
I must say that i'm kinda new with programming with mvc and i tried to look all over the internet and i didn't really know people who knows to program so this is really my last chance to solve it. Sorry if it was a long post or too easy for you to even comment but i really need this.
This is very specific for my project and what i stacked on was to sent the input to the controller but i'm open to different solutions cause i'm desperate.
public async Task<ActionResult> AddExercises(string id,int[] selectedExercises)
{
List<Exercise> list = new List<Exercise>();
foreach (int i in selectedExercises)
{
list.Add(db.Exercises.Find(i));
}
db.Plans.Find(id).Exercises = list;
await db.SaveChangesAsync();
return RedirectToAction("index");
}
I am sure its wrong and the view i tried is:
#using (Html.BeginForm("index", "Plans"))
{
#Html.AntiForgeryToken()
foreach (var i in Model.Exercises)
{
<table class="table">
<tr>
<td>#Html.DisplayFor(modelItem => i.Level)</td>
<td>#Html.DisplayFor(modelItem => i.Description)</td>
<td>#Html.DisplayFor(modelItem => i.MoreDescription)</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = i.Id }) |
#Html.ActionLink("Details", "Details", new { id = i.Id }) |
<input type="checkbox" class="selectedObjects" id="i.id" /> |
</td>
</tr>
</table>
}
#Html.ActionLink("Finish", "AddExercises")
}
I use viewModel PlanExercise in this view

Your checkboxes do not have either a name attribute or a value attribute so there is nothing to submit. You html needs to be
<input type="checkbox" name="selectedExercises" value="#i.id" />
and then in order to submit the form, you need a submit button (remove #Html.ActionLink("Finish", "AddExercises"))
<input type="submit" value="Save" />
and the form needs to be (assuming the controller is FinishController)
#using (Html.BeginForm("AddExercises", "Finish"))
and the method needs to be marked with the [HttpPost] attribute.

Related

Posting an array of string

I am trying to post a string array to the post action in an Razor Pages project. For this, I thought about using a hidden <select> tag. The user would enter text into a text box, press a button and I would then add a new option to the <select> then post the whole thing with a submit button. However, after everything is posted, the array property of my model is empty.
Does anyone know if there is a better way of doing this or what I am doing wrong?
Razor:
<form method="post">
<input id="string-value" />
<input type="button" id="add-item" value="Add item" />
<select asp-items="#Model.Model.ArrayOfStrings" id="hidden-select"></select>
<table id="table-items">
</table>
<input type="submit" value="Submit" />
</form>
public class ArrayModel
{
public List<SelectListItem> ArrayOfStrings { get; set; } = new List<SelectListItem>();
}
public class IndexModel : PageModel
{
[BindProperty]
public ArrayModel Model { get; set; }
public void OnGet()
{
Model = new ArrayModel();
}
public void OnPost()
{
System.Diagnostics.Debugger.Break();
}
}
JS:
$('#add-item').on('click', function () {
debugger;
var value = $('#string-value').val();
$('#hidden-select').append(new Option(value, value));
$('#table-item tr:last').after('<tr><td>' + value + '</td></tr>')
});
Repository can be found here.
The options of the select will not be posted so this will not work.
The easiest way to do this is append the results to a hidden input with a separator char, then do a string split on the server side.
Another, maybee more elegant way, would be to add hidden inputs with the same name. Each input with it's own value. You should then be able to get this as a List or Array on the server.
Razor:
<input value="#String.Join(",", Model.Model.ArrayOfStrings)" id="tags"></select>
JS
$('#tags').val($('#tags').val() + ',' + value);
Controller
public void OnPost(string tags)
{
var tagsArray = tags.split(',');
}

requiredif on an element in a list of custom inputs

I have a modelview that contains a list of ICustomInput values
public class DemoViewModel {
[Required]
public string FirstName {get; set;}
[Required]
public string LastName {get; set;}
[RequiredIf("DayPhoneRequired", true)]
public string DayPhone {get; set;}
public bool DayPhoneRequired {get; set;} = false;
public List<ICustomInput> CustomInputFields { get; set; } = new List<ICustomInput>();
}
an example of an ICustomInput
public class CustomTextInput : ICustomInput
{
public CustomField Field { get; }
public string DisplayName { get; set; }
[RequiredIf("DataValueRequired", true, ErrorMessage = "This is a required field")]
public virtual string DataValue { get; set; }
public bool DataValueRequired { get; set; } = false;
public virtual string ClassName => "CustomTextInput";
public string AssemblyName => "Application.Models";
}
The purpose of this is so that i can pull information from the DB about the custom input fields that the logged in client has requested on the form. One client may want a couple text fields, another client may want a drop down. These custom fields may or may not require input as well. (The CustomField object is an older object returned by the dataLayer and used heavily, I don't want to rebuild it, but assume it's just full of strings)
I have an editor template for the concrete implementations of ICustomInputs as well as custom binders that allow me to get the data on post. But the issue I'm having is that the RequiredIf attribute is setting the unobtrusive data values for client side validation the same for all ICustomInputs. It makes sense since they all have the same name for their dependent property, but it doesn't solve the issue I have.
My view displays the list of ICustomInput by simply:
#Html.EditorFor(model => model.CustomInputFields)
Then each concrete type that implements ICustomInput has it's own editorTemplate similar to:
<div class="columnPositioner">
<div class="inputContainer">
#Html.TextBoxFor(model => model.DataValue, new
{
#class = "inputFields input-lg form-control",
placeholder = Model.Field.Display
})
<span class="inputLabel">
#Html.LabelFor(model => model.Field.Display, Model.Field.Display)
</span>
#Html.ValidationMessageFor(model => model.DataValue, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.DataValueRequired)
</div>
</div>
The resulting HTML looks like:
<select name="CustomInputFields[0].DataValue" class="inputFields input-lg form-control" id="CustomInputFields_0__DataValue" data-val="true" data-val-requiredif-operator="EqualTo" data-val-requiredif-dependentvalue="True" data-val-requiredif-dependentproperty="DataValueRequired" data-val-requiredif="This is a required field"><option value="">TEST01</option>
<option value="01">01</option>
<option value="02">02</option>
<option value="03">03</option>
</select>
<input name="CustomInputFields[0].DataValueRequired" class="hasContent" id="CustomInputFields_0__DataValueRequired" type="hidden" value="True" data-val-required="The DataValueRequired field is required." data-val="true">
<input name="CustomInputFields[1].DataValue" class="inputFields input-lg form-control" id="CustomInputFields_1__DataValue" type="text" placeholder="TEST02" value="" data-val="true" data-val-requiredif-operator="EqualTo" data-val-requiredif-dependentvalue="True" data-val-requiredif-dependentproperty="DataValueRequired" data-val-requiredif="This is a required field">
<input name="CustomInputFields[1].DataValueRequired" id="CustomInputFields_1__DataValueRequired" type="hidden" value="False" data-val-required="The DataValueRequired field is required." data-val="true">
The hidden field is named properly, but how can I get the attribute to set the data-val-requiredif-dependentproperty to the actual id/name on the hidden field?
I do not currently have a custom editor template for the List. I did have one, but couldn't get it to bind the data back correctly. Dropping the editor template on the List and building unique editor templates for the concrete implementations of ICustomInput gave me all the UI layout control I needed and bound the data correctly, but now I can't get the client side validation to work properly. If it's just a editor template, what might that look like?
Update
This is A fix, but I don't like it. I have a javascript that's already doing an .each through inputs to apply styles so I added this to the .each:
function requiredIfHack($input) {
var depPropVal = $input.data("val-requiredif-dependentproperty");
//return if the value exists
if ($("#" + depPropVal).length) return;
//it doesn't. it's missing the parent object name
var parentName = $input.attr("name").split(".")[0].replace("[", "_").replace("]", "_");
$input.data("val-requiredif-dependentproperty", parentName + "_" + depPropVal);
}
It solves the problem, but I don't think it should be a problem that is the js responsibility to solve. And since it's a pretty sneaky fix, it could trip up others trying to work on this code in the future. I still want to find a better way to do it.

RazorEngine - How to use a complex model view?

Anyone have experience working with a complex model and RazorEngine?
Working on generating HTML using RazorEngine version 3.7.3, but running into issues with the complex model view we have. It seems like we should be able to use the templates to get RazorEngine to discover the SubSample below, but have not discovered the proper way to tell RazorEngine about the associated cshtml file.
In the example below we are looking to use a shared template for the SubSample class using the SubSample.cshtml file. As can be seen from the results, the class namespace (ReportSample.SubSample) is displayed rather than an HTML row of data.
We have tried implementing an ITemplateManager, but Resolve() is never called with a key asking for the SubSample. Also tried AddTemplate() on the service, but still no joy.
Here is a simplified example model to illustrate the issue:
namespace ReportSample
{
public class SubSample
{
public string Name { get; set; }
public string Value { get; set; }
}
public class SampleModel
{
public SubSample SubSample { get; set; }
}
}
SampleModel.cshtml
#using ReportSample
#*#model ReportSample.SampleModel*#
<table style="width: 7.5in" align="center">
<tr>
<td align="center">
<h1>
Sample Report
</h1>
</td>
</tr>
<tr>
<td>
<table style="width: 100%">
<tr>
<td colspan="2">
<b>Name:</b> #Model.SubSample.Name
</td>
<td colspan="2">
<b>Value:</b> #Model.SubSample.Value
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center">
<h1>
Sub-sample Data
</h1>
</td>
<td>
<table style="width: 100%">
#Model.SubSample
</table>
</td>
</tr>
</table>
SubSample.cshtml
#model ReportSample.SubSample
#using FSWebportal.Infrastructure.Mvc;
<tr class="observation-row">
<td class="observation-label">
#model.Name
</td>
<td class="observation-view">
#model.Value
</td>
</tr>
Basic RazorEngine calls:
private void html_Click(object sender, EventArgs e)
{
var gen = new RazorEngineGenerator();
var cshtmlTemplate = File.ReadAllText("Sample.cshtml");
var sample = new SampleModel() { SubSample = new SubSample() { Name = "name", Value = "value" } };
var html = gen.GenerateHtml(sample, cshtmlTemplate);
}
public string GenerateHtml<T>(T model, string cshtmlTemplate)
{
var config = new TemplateServiceConfiguration();
using (var service = RazorEngineService.Create(config))
{
return service.RunCompile(cshtmlTemplate, "", typeof(T), model);
}
}
Sample HTML Output:
Sample Report
Name: name
Value: value
Sub-sample Data
ReportSample.SubSample
I'm sorry but I don't think I fully understand your question but I have a small idea of what you are trying to do...
I think what you want are searching for are partial templates (using #Include)!
using RazorEngine;
using RazorEngine.Templating;
using System;
namespace TestRunnerHelper
{
public class SubModel
{
public string SubModelProperty { get; set; }
}
public class MyModel
{
public string ModelProperty { get; set; }
public SubModel SubModel { get; set; }
}
class Program
{
static void Main(string[] args)
{
var service = Engine.Razor;
// In this example I'm using the default configuration, but you should choose a different template manager: http://antaris.github.io/RazorEngine/TemplateManager.html
service.AddTemplate("part", #"my template");
// If you leave the second and third parameters out the current model will be used.
// If you leave the third we assume the template can be used for multiple types and use "dynamic".
// If the second parameter is null (which is default) the third parameter is ignored.
// To workaround in the case you want to specify type "dynamic" without specifying a model use Include("p", new object(), null)
service.AddTemplate("template", #"<h1>#Include(""part"", #Model.SubModel, typeof(TestRunnerHelper.SubModel))</h1>");
service.Compile("template", typeof(MyModel));
service.Compile("part", typeof(SubModel));
var result = service.Run("template", typeof(MyModel), new MyModel { ModelProperty = "model", SubModel = new SubModel { SubModelProperty = "submodel"} });
Console.WriteLine("Result is: {0}", result);
}
}
}
I have added documentation for this here: https://antaris.github.io/RazorEngine/LayoutAndPartial.html
However I think making #Model.SubSample work is possible as well, you can either change the SubSample property to be of type TemplateWriter or make the SubSample class implement IEncodedString. But I think you should consider partial templates first!

validation and model binding in MVC5

I am making an application which has student class.
it is as follows. this is sample pseudo description. (don`t bother about syntax)
public class student{
[required]
string name;
int id;
List<course> courses;
}
public class course{
[required]
string name;
}
student class has list of courses. now when i bind this as model to view. view has a text field for name and the grid for courses which has course name. if i submit the form with out entering the name of student.the validation fires automatically. but for the courses it is not firing up. though on the controller side model state is considered invalid. but client side validation is not firing up.
again i am posting sort of pesudocode can`t post actual code.
#using (Html.BeginForm("create", "student", FormMethod.Post, new { id = "createStudent" }))
{
#Html.TextboxFor(m=>m.name)
#Html.ValidationMessageFor(m=>m.Name)
<table><tr>
<td>
#Html.TextBox("courses[0].name")<br/>
#Html.ValidationMessage("course[0].name")
</td>
</table>
<input type="submit"></input>
}
now clicking on submit for doesnot fire validation for course name . but model.state is invalid.
if i enter something in course name the model is validated.
but i want to display validation on the page as it is firing for student name.
Please suggest me something
note:- i also tried (for course name) this.
#Html.TextBox("courses[0].Name", "", new { #class = "input-xlarge", id = "txtPropName" })
#Html.ValidationMessageFor(m=>m.courses[0].Name)

Viewmodel IEnumerable property is empty

I'm in the middle of making an ASP .NET MVC4 based app. I'm a complete newb in that field. The idea is quite simple - have a some members in DB, show them listed, select desired ones via check boxes and redirect to some other controller which would do something with the previously selected members.
Problem is passing the list of members from View to the Controller. I've thought it would work with ViewModel. It certainly works from Controller to the View, but not the other way.
My ViewModel:
public class MembersViewModel
{
public IEnumerable<Directory_MVC.Models.Member> MembersEnum { get; set; }
public string Test { get; set; }
}
Snippet of my Controller:
public class MembersController : Controller
{
private MainDBContext db = new MainDBContext();
public ActionResult Index()
{
var model = new Directory_MVC.ViewModels.MembersViewModel();
// populating from DB
model.MembersEnum = db.Members.Include(m => m.Group).Include(m => m.Mother).Include(m => m.Father);
model.Test = "abc";
return View(model);
}
[HttpPost]
public ActionResult GoToSendEmail(Directory_MVC.ViewModels.MembersViewModel returnedStruct)
{
if (ModelState.IsValid)
{
// it is valid here
return Redirect("http:\\google.com");
}
}
Snippet of my View:
#model Directory_MVC.ViewModels.MembersViewModel
#{
ViewBag.Title = "Members listing";
var lineCount = 0;
string lineStyle;
}
#using (Html.BeginForm("GoToSendEmail", "Members", FormMethod.Post))
{
<table>
#foreach (var item in Model.MembersEnum)
{
lineCount++;
// set styling
if (lineCount % 2 == 1)
{
lineStyle = "odd-line";
}
else
{
lineStyle = "even-line";
}
<tr class="#lineStyle">
<td>
#Html.EditorFor(modelItem => item.Selected)
</td>
<td>
#Html.DisplayFor(modelItem => item.FirstName)
</td>
<td>
#Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
#Html.DisplayFor(modelItem => item.Mother.FirstName) #Html.DisplayFor(modelItem => item.Mother.LastName)
</td>
<td>
#Html.DisplayFor(modelItem => item.Father.FirstName) #Html.DisplayFor(modelItem => item.Father.LastName)
</td>
<!-- other print-outs but not all properties of Member or Mother/father are printed -->
</tr>
}
</table>
<input type="submit" value="Send E-mail" />
}
The data are shown OK in the View. However, when I submit that form the returnedStruct.MembersEnum and Test string are both null in the Controller's method GoToSendEmail.
Is there a mistake or is there another possible way how to pass that members structure and check their Selected property?
Model binding to a collection works a little differently. Each item has to have an identifier so that inputs don't all have the same name. I've answered a similar question here.
#for (int i = 0; i < Model.MembersEnum.Count(); i++)
{
#Html.EditorFor(modelItem => modelItem.MembersEnum[i].FirstName)
}
...which should render something like...
<input type="text" name="MembersEnum[0].FirstName" value="" />
<input type="text" name="MembersEnum[1].FirstName" value="" />
<input type="text" name="MembersEnum[2].FirstName" value="" />
...which should then populate the collection in your ViewModel when picked up by the controller...
public ActionResult GoToSendEmail(ViewModels.MembersViewModel model)
As mentioned in the other answer, I'd have a look at some related articles from Scott Hansleman and Phil Haack.
You also mentioned that your string called Test is null when you submit to your POST action. You haven't added a field for this property anywhere within your form, so there's nothing for the model binder to bind to. If you add a field for it within your form then you should see the value in the POST action:
#Html.EditorFor(modelItem => modelItem.Test)
Html.BeginCollectionItem() helper did the job - BeginCollectionItem.

Resources