I have experience with ASP.NET webforms but now I'm working with different frameworks so I need a little help. I'm new to ASP.NET MVC with Razor pages and .NET Core and I need to complete this task using those technologies.
I have a view model and for example purposes let's say it is a person view model. Person
has some properties like Name, Age, Gender etc.. and it has a property Hobbies which is an IEnumerable<Hobbies>
that stores the hobbies of each person. Hobbies have properties like Description, TimeSpent etc.
I have a form where I can edit one person and save it. On this form I have a button to post the form and save all the information to the database.
My problem is that I need to be able to assign and delete hobbies dynamically without posting the entire form. For example, a person can have multiple hobbies, so I have a Plus button to add a new hobby and store it in the view model without posting the entire form, because the user still wants to add more hobbies and when he finishes, then he hits the save button so he can save all the information.
How can I do this? I tried to do it with an Ajax call on the ADD Hobbies button where I passed the person to the action using JSON.stringify(Person)
and I was able to add an hobby to the view model, but then I understood that the view model is always formatted with the information of the first load of the page, so I was not manipulating the view model in memory. I was always adding just one hobby every time I clicked the button ADD.
Another thing I have to do is to render the inputs for each hobby that the user adds so he can type the Description of the hobby etc.
I can't even have two forms nested, because I tried to have an ajax.BeginForm to the AddHobbies inside the Save Form but this is not possible too.
Can anyone help me with that? I know it would be better if i posted my code but i cant. The real scenario is much more complex than this, but this Person and Hobbies is quite good to explain what I need.
My Classes (Just Example, not the real case scenario)
public class Person
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public int Age { get; set; }
public string Gender { get; set; }
[ForeignKey(nameof(Hobbies))]
public int HobbieId { get; set; }
public IEnumerable<Hobbie> Hobbies { get; set; }
public class Hobbie
{
public int Id { get; set; }
public string Description { get; set; }
public double TimeSpent { get; set; }
}
}
Controller API Action:
public JsonResult OnPostAddHobbieAsync([FromBody]Person person)
{
try
{
person.Hobbies.Add(new Hobbie() { Description = "My Hobbie", TimeSpent= 120 });
return new JsonResult(person);
}
catch (Exception ex)
{
return new JsonResult("Error generating new credentials. Please contact your administrator.");
}
}
Razor Page:
<form id="registerForm" method="post" enctype="multipart/form-data">
<!--// Person information inputs-->
<div class="form-group">
<label asp-for="Person.Name"></label>
<div class="input-group">
<input asp-for="Person.Name" class="form-control" autocomplete="off" />
</div>
<span asp-validation-for="Person.Name" class="text-danger"></span>
</div>
<!----- All the persons Properties Inputs
--- All the persons Properties Inputs
--- All the persons Properties Inputs-->
<label asp-for="Person.Hobbies"></label>
<br />
<a class="btn btn-primary" style="border-radius:100px" onclick="addHobbie()"><i class="fas fa-plus"></i></a> <!--// Button to add a new hobbie-->
<div class="form-group" id="HobbiesDiv">
@foreach(var hobbie in Model.Person.Hobbies) <!--// this is too loop through the Hobbies and render inputs so the user can type the description-->
{
<a class="btn btn-danger" onclick="DeleteHobbie();" style="border-radius:100px"><i class="fas fa-minus fa-fw"></i></a> <!--// button to delete an existing hobbie-->
<div id="customWrapper">
<label asp-for="@hobbie.Description"></label>
<input asp-for="@Hobbie.Description" />
<span asp-validation-for="@Hobbie.Description"></span>
</div>
}
</div>
<div class="col-12 pt-5">
<button class="btn btn-primary float-right" type="submit"><i class="fas fa-save"></i> Save</button> <!--// when this button is clicked, it posts the form and save all the information added-->
<a asp-page="/Controller/Index" type="button" class="btn btn-danger float-right mr-2">Cancel</a>
</div>
<input asp-for="Person.Id" />
<script>
function addHobbie() {
var json = @Html.Raw(Json.Serialize(@Model.Person));
var url = "/Controller/Edit?handler=AddHobbieAsync";
$.ajax({
url: url,
type: "POST",
dataType: "json",
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(json),
success: function (data) {
alert(data.customFields);
// I successfully have a new hobbie in data. Next Time I call this fucntion, I loose the hobbies that where returned in the previous call. Also, I have
//to bind the new hobbie that was added, but since it doesn't cause a postback, the foreach loop still does not render any tags. I dont know if this is the better aproach
//to achieve my goal
},
error: function (xhr, textStatus, errorThrown) {
alert(xhr.responseText);
}
});
}
</script>
You can define a static
variable to store the addhobbies.
public static IList<Hobbie> Hobbies { get; set; }
And I suggest use partial view to display the hobbies, otherwise when submit the whole model, model binding will go wrong.
A simple demo based on your codes.
Model:
public class Person
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public int Age { get; set; }
public string Gender { get; set; }
public List<Hobbie> Hobbies { get; set; }
}
public class Hobbie
{
public int Id { get; set; }
public string Description { get; set; }
public double TimeSpent { get; set; }
}
Index.cshtml:
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<form id="registerForm" method="post" enctype="multipart/form-data">
<div class="form-group">
<label asp-for="Person.Name"></label>
<div class="input-group">
<input asp-for="Person.Name" class="form-control" autocomplete="off" />
</div>
<span asp-validation-for="Person.Name" class="text-danger"></span>
</div>
<label asp-for="Person.Hobbies"></label>
<br />
<a class="btn btn-primary" style="border-radius:100px" onclick="addHobbie()"><i class="fas fa-plus"></i></a> <!--// Button to add a new hobbie-->
<div class="form-group" id="HobbiesDiv">
@if (Model.Person.Hobbies != null)
{
<partial name="_HobbiesPartial" model="Model.Person.Hobbies" />
}
</div>
<div class="col-12 pt-5">
<button class="btn btn-primary float-right" type="submit"><i class="fas fa-save"></i> Save</button> <!--// when this button is clicked, it posts the form and save all the information added-->
<a asp-page="/Index" type="button" class="btn btn-danger float-right mr-2">Cancel</a>
</div>
<input asp-for="Person.Id" type="hidden" />
</form>
@section scripts{
<link rel="stylesheet" href="https://pro.fontawesome.com/releases/v5.10.0/css/all.css" integrity="sha384-AYmEC3Yw5cVb3ZcuHtOA93w35dYTsvhLPVnYs9eStHfGJvOvKxVfELGroGkvsg+p" crossorigin="anonymous" />
<script>
function addHobbie() {
var url = "/Index?handler=AddHobbie";
$.ajax({
url: url,
type: "POST",
headers: {
'RequestVerificationToken': $('input[name="__RequestVerificationToken"]').val()
},
success: function (data) {
$('#HobbiesDiv').empty();
$('#HobbiesDiv').html(data)
},
error: function (xhr, textStatus, errorThrown) {
alert(xhr.responseText);
}
});
}
</script>
}
_HobbiesPartial.cshtml:
@model List<RazorTest.Models.Hobbie>
@{
Layout = null;
}
@for (int i = 0; i < Model.Count; i++)
{
<a class="btn btn-danger" onclick="DeleteHobbie();" style="border-radius:100px"><i class="fas fa-minus"></i></a> <!--// button to delete an existing hobbie-->
<div id="customWrapper">
<label asp-for="@Model[i].Description"></label>
<input name="Person.Hobbies[@i].Description" value="@Model[i].Description" />
<span asp-validation-for="@Model[i].Description"></span>
</div>
}
Index.cshtml.cs:
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
public IndexModel(ILogger<IndexModel> logger)
{
_logger = logger;
}
[BindProperty]
public Person Person { get; set; }
public static List<Hobbie> Hobbies { get; set; }
public void OnGet()
{
Person = new Person
{
Id = 1,
Name = "ABC",
Hobbies = new List<Hobbie>
{
new Hobbie{ Description = "my hobbie", TimeSpent = 120 }
}
};
Hobbies = Person.Hobbies;
}
public void OnPost()
{
var person = Person;
}
public IActionResult OnPostAddHobbie()
{
try
{
Hobbies.Add(new Hobbie() { Description = "my hobbie", TimeSpent = 120 });
return Partial("_HobbiesPartial", Hobbies);
}
catch (Exception ex)
{
return new JsonResult("error generating new credentials. please contact your administrator.");
}
}
}
Result: