I am working with Asp.Net MVC, and I have a DTO that looks like this:
public class TaskDTO
{
public string TaskName { get; set; }
public string NextTaskName { get; set; }
public bool IsBasicTask { get; set; }
public int EstimatedTime { get; set; }
public List<ResourceTaskDTO> RequiredResources { get; set; }
}
public class ResourceTaskDTO
{
public string ResourceName { get; set; }
public int Id { get; set; }
public int Count { get; set; }
}
What I am trying to do, is that on the view side of this, I want to have forms and tables to fill up the TaskDTO. On the view side, I have the normal forms, and also a table that is populated by javascript to add the ResourceTaskDTO.
@model CMBuilder.Models.Api.TaskDTO
@{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Task</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="TaskName" class="control-label"></label>
<input asp-for="TaskName" class="form-control" />
<span asp-validation-for="TaskName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="NextTaskName" class="control-label"></label>
<input asp-for="NextTaskName" class="form-control" />
<span asp-validation-for="NextTaskName" class="text-danger"></span>
</div>
<div class="form-group form-check">
<label class="form-check-label">
<input class="form-check-input" asp-for="IsBasicTask" /> @Html.DisplayNameFor(model => model.IsBasicTask)
</label>
</div>
<div class="form-group">
<label asp-for="EstimatedTime" class="control-label"></label>
<input asp-for="EstimatedTime" class="form-control" />
<span asp-validation-for="EstimatedTime" class="text-danger"></span>
</div>
<div class="form-group">
<table class="table table-bordered" id="ResourceTable">
<tr>
<th>Resource Name</th>
<th>Id</th>
<th>Count</th>
<th><button type="button" name="add" id="btn_AddResource" class="btn btn-success btn-sm add"><span class="glyphicon glyphicon-plus"></span></button></th>
</tr>
</table>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts
{
<script>
i = 0;
$("#btn_AddResource").click(function () {
var html = '';
html += '<tr>';
html += '<td><input type="text" asp-for="RequiredResources[i].ResourceName" class="form-control" /></td>';
html += '<td><input type="text" asp-for="RequiredResources[i].Id" class="form-control" /></label></td>';
html += '<td><input type="text" asp-for="RequiredResources[i].Count" class="form-control" /></td>';
html += '<td></td></tr>';
$('#ResourceTable').append(html);
i++;
});
</script>
}
However, when I access the "RequiredResources" attribute in the ResourceTaskDTO in the controller's "Create" method, it is NULL and it seems that the javascript code did not hook into the "RequiredResources" attribute.
This is my Controller Code:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("TaskName,NextTaskName,IsBasicTask,EstimatedTime,RequiredResources")] TaskDTO task)
{
if (ModelState.IsValid)
{
CMBuilderHandler Handler = new CMBuilderHandler(_service);
var res = await Handler.CreateTask(task);
if(res)
{
return Ok("Success");
}
else
{
return NotFound("Something went bad!");
}
}
return View(task);
}
What is wrong with my code? Why does task.RequiredResources null when I get it back on the controller?
I am working with Asp.Net MVC, and I have a DTO that looks like this:
public class TaskDTO
{
public string TaskName { get; set; }
public string NextTaskName { get; set; }
public bool IsBasicTask { get; set; }
public int EstimatedTime { get; set; }
public List<ResourceTaskDTO> RequiredResources { get; set; }
}
public class ResourceTaskDTO
{
public string ResourceName { get; set; }
public int Id { get; set; }
public int Count { get; set; }
}
What I am trying to do, is that on the view side of this, I want to have forms and tables to fill up the TaskDTO. On the view side, I have the normal forms, and also a table that is populated by javascript to add the ResourceTaskDTO.
@model CMBuilder.Models.Api.TaskDTO
@{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Task</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="TaskName" class="control-label"></label>
<input asp-for="TaskName" class="form-control" />
<span asp-validation-for="TaskName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="NextTaskName" class="control-label"></label>
<input asp-for="NextTaskName" class="form-control" />
<span asp-validation-for="NextTaskName" class="text-danger"></span>
</div>
<div class="form-group form-check">
<label class="form-check-label">
<input class="form-check-input" asp-for="IsBasicTask" /> @Html.DisplayNameFor(model => model.IsBasicTask)
</label>
</div>
<div class="form-group">
<label asp-for="EstimatedTime" class="control-label"></label>
<input asp-for="EstimatedTime" class="form-control" />
<span asp-validation-for="EstimatedTime" class="text-danger"></span>
</div>
<div class="form-group">
<table class="table table-bordered" id="ResourceTable">
<tr>
<th>Resource Name</th>
<th>Id</th>
<th>Count</th>
<th><button type="button" name="add" id="btn_AddResource" class="btn btn-success btn-sm add"><span class="glyphicon glyphicon-plus"></span></button></th>
</tr>
</table>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts
{
<script>
i = 0;
$("#btn_AddResource").click(function () {
var html = '';
html += '<tr>';
html += '<td><input type="text" asp-for="RequiredResources[i].ResourceName" class="form-control" /></td>';
html += '<td><input type="text" asp-for="RequiredResources[i].Id" class="form-control" /></label></td>';
html += '<td><input type="text" asp-for="RequiredResources[i].Count" class="form-control" /></td>';
html += '<td></td></tr>';
$('#ResourceTable').append(html);
i++;
});
</script>
}
However, when I access the "RequiredResources" attribute in the ResourceTaskDTO in the controller's "Create" method, it is NULL and it seems that the javascript code did not hook into the "RequiredResources" attribute.
This is my Controller Code:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("TaskName,NextTaskName,IsBasicTask,EstimatedTime,RequiredResources")] TaskDTO task)
{
if (ModelState.IsValid)
{
CMBuilderHandler Handler = new CMBuilderHandler(_service);
var res = await Handler.CreateTask(task);
if(res)
{
return Ok("Success");
}
else
{
return NotFound("Something went bad!");
}
}
return View(task);
}
What is wrong with my code? Why does task.RequiredResources null when I get it back on the controller?
Share Improve this question asked Mar 22, 2019 at 0:06 MantrackerMantracker 61311 silver badges24 bronze badges 7-
1
asp-for
will not be expanded, it will not evaluate, when added to the markup with client-side JavaScript. You will need to set the name attribute and values yourself for each form input. – Jasen Commented Mar 22, 2019 at 0:16 - If that's the case, what's the best way to implement what I am trying to do? I need a button that dynamically adds forms in a webpage, that creates asp-for to hook into the List of ResourceTaskDTO of my TaskDTO – Mantracker Commented Mar 22, 2019 at 0:19
- 1 You insert plain html without server-side evaluated tag helpers. If you want to take advantage of the tag helpers you'll need to use an AJAX request to render the partial view snippet on the server then attach it to your document after that response is returned. – Jasen Commented Mar 22, 2019 at 0:24
- I am still not 100% sure that I understand so correct me if I'm wrong, but I think what you are saying what I need to do is to not use the javascript code to append the html here, but when the btn_AddResource is clicked, create an AJAX POST request to insert the ResourceName, Id, and Count for the RequiredResourcesDTO, is that right? – Mantracker Commented Mar 22, 2019 at 0:30
- Because you want dynamically generated form elements you need to use JavaScript. However, any html you add to the document will not be processed by the Razor engine therefore you cannot rely on tag helpers for this code. To help getting the correct html you can do a prototype view with a few "added rows" then peek at the generated html. That will help you to know how to craft correct markup without the tag helpers. The AJAX route is not wrong but involves extra network requests but may have benefit if you require values from the server. – Jasen Commented Mar 22, 2019 at 0:36
1 Answer
Reset to default 5Any new additions to the DOM with client-side JavaScript will not be processed by the Razor view engine so you cannot rely on server-side evaluated markup. Since asp-for
is a Razor Tag Helper it will not be expanded.
You will need to set the name and value attributes yourself for each form input.
$("#btn_AddResource").click(function () {
var html = '';
...
html += '<td><input type="text" name="RequiredResources[' + i + '].ResourceName" value="" class="form-control" /></td>';
html += '<td><input type="text" name="RequiredResources[' + i + '].Id" value="" class="form-control" /></label></td>';
...
$('#ResourceTable').append(html);
i++;
});
The name will need to match your models' properties to bind the values when submitted to the controller action.