I'm trying to code a form to insert Objects into a database (via api POST petition), everything works fine but InputSelect is not working. It shows me the data but doesn't sends the selected option when I click the submit button
The form code:
@using ClinicaVistaalegre.Shared.Models
@inject HttpClient Http
<EditForm Model="@Cita" OnValidSubmit="@OnValidSubmit">
<h3>Crear cita</h3>
<hr />
<div >
<label for="Motivo" >
Motivo
</label>
<div >
<InputText id="Motivo" placeholder="Motivo"
@bind-Value="Cita.Motivo" />
</div>
</div>
<div >
<label for="FechaHora" >
Fecha y Hora
</label>
<div >
@*<inp @bind=Cita.FechaHora @bind:format="yyyy-MM-ddTHH:mm" type="datetime-local"/>*@
<InputDate id="FechaHora" placeholder="FechaHora"
@bind-Value="Cita.FechaHora" />
</div>
</div>
<div >
<label for="MedicoId" >
Contacto
</label>
<div >
<InputSelect @bind-Value="Cita.MedicoId" >
@foreach (Medico m in medicos)
{
<option value="@m.Id">@m.Apellidos</option>
}
</InputSelect>
</div>
</div>
<div >
<label for="PacienteId" >
Paciente
</label>
<div >
<div >
<InputText id="Paciente" placeholder="Paciente"
@bind-Value="Cita.PacienteId" />
</div>
</div>
</div>
<button type="submit" >@TextoBoton</button>
<DataAnnotationsValidator/>
</EditForm>
@code {
private Medico[]? medicos;
protected override async Task OnInitializedAsync() =>
medicos = await Http.GetFromJsonAsync<Medico[]>("api/Medicos");
[Parameter] public Cita Cita { get; set; } = new Cita();
[Parameter] public String TextoBoton { get; set; } = "Guardar";
[Parameter] public EventCallback OnValidSubmit { get; set; }
}
Container code:
@page "/creacitas"
@using ClinicaVistaalegre.Shared.Models
@using System.Net.Http.Json
@inject HttpClient Http
@inject NavigationManager NavMan
<PageTitle>Citas</PageTitle>
<h1>Crear cita</h1>
<FormularioCita TextoBoton="Crear cita" OnValidSubmit="@CrearCita" Cita="@cita"/>
@code {
Cita cita = new Cita();
async Task CrearCita(){
//var json = JsonConvert.SerializeObject(cita);
//await Http.PostAsync("api/Citas", new StringContent(json, UnicodeEncoding.UTF8, "application/json"));
//await Http.PostAsync("api/Citas", new StringContent(JsonConvert.SerializeObject(json)));
//UriHelper.
//var addItem = new TodoItem { Name = newItemName, IsComplete = false };
HttpResponseMessage mensaje = await Http.PostAsJsonAsync("api/Citas", cita);
if (mensaje.IsSuccessStatusCode)
{
NavMan.NavigateTo("fetchcitas");
}
}
}
Cita and Medico Model:
public class Cita
{
public int Id { get; set; }
[Required]
public string PacienteId { get; set; }
[JsonProperty(Required = Required.AllowNull)]
public Paciente? Paciente { get; set; }
[Required]
public string MedicoId { get; set; }
[JsonProperty(Required = Required.AllowNull)]
public Medico? Medico { get; set; }
public string? Motivo { get; set; }
public DateTime FechaHora { get; set; }
}
public class Medico
{
public string Id { get; set; }
public string Apellidos { get; set; }
public string Especialidad { get; set; }
[JsonProperty(Required = Required.AllowNull)]
public List<Cita> Citas { get; set; }
[JsonProperty(Required = Required.AllowNull)]
public List<Mensaje> Mensajes { get; set; }
}
Chrome browser debug tool give me this error:
blazor.webassembly.js:1 crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: Object reference not set to an instance of an object.
System.NullReferenceException: Object reference not set to an instance of an object.
at ClinicaVistaalegre.Client.Shared.FormularioCita.<BuildRenderTree>b__0_8(RenderTreeBuilder __builder3) in C:\Users\santanitaxx1050\Desktop\ClinicaVistaalegre\ClinicaVistaalegre\Client\Shared\FormularioCita.razor:line 32
at Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddContent(Int32 sequence, RenderFragment fragment)
at Microsoft.AspNetCore.Components.Forms.InputSelect`1[[System.String, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].BuildRenderTree(RenderTreeBuilder builder)
at Microsoft.AspNetCore.Components.ComponentBase.<.ctor>b__6_0(RenderTreeBuilder builder)
at Microsoft.AspNetCore.Components.Rendering.ComponentState.RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment, Exception& renderFragmentException)
window.Module.s.printErr @ blazor.webassembly.js:1
But the compiled page has the correct option value:
<option value="b04efe29-a306-40b1-8799-41b99b215a69">sanchez luque</option>
I tried removing and the error above doesn't appear but the POST petition doesn't get the InputSelect data and tries to insert an object with MedicoId null field
Edit: Maybe an InputSelect parsing problem???
Edit 2: If i change Medicos[] to List I don't have the first error but when I try to submit it appears me like the value it's not correct due to the
Edit 3:
I managed to make an almost successfull api petition with enet answer and removing DataAnnotationsValidator in EditForm but I have this incomplete object:
{
"id": 0,
"pacienteId": "1d49f54a-91cb-4980-8983-9a70bd1c668d",
"paciente": null,
"medicoId": null,
"medico": null,
"motivo": "pruebacreate",
"fechaHora": "0001-01-01T00:00:00"
}
paciente and medico should be null, that's not the problem, the problem here is that medicoId is null and it has no sense because it shows the options and values correctly
CodePudding user response:
The issue is related to how you create a two way data binding between the container component and its child component - FormularioCita
You may do the following:
container code:
@code {
// Instantiate an instance of the Cita object with
// default values
private Cita Cita {get; set;} = new Cita();
}
What you want to do is to pass the Cita instance to the FormularioCita
component, allow the user to edit the form data, and when she presses the "submit" button, pass the Cita instance back to the parent component from which it is posted to a Web Api end point... In order to enable that, you should define two parameter properties as follows:
FormularioCita.razor
[Parameter] public Cita Cita { get; set; }
[Parameter] public EventCallback<Cita> CitaChanged { get; set; }
Note: The first parameter property is an automatic property used by Blazor to pass values between components. You should not modify its value. In other words, you should not bind it to the EditForm
component. Instead, you should define a local copy of it to be used locally:
private Cita cita;
And:
protected override void OnParametersSet()
{
if (cita != Cita )
{
cita = Cita;
}
}
And your form should look like this:
<EditForm Model="@cita" OnValidSubmit="OnValidSubmit">
Note that the Model attribute is assigned the local variable cita
, not the Cita
property. Note also that the OnValidSubmit
is assigned the local method OnValidSubmit
, defined as follows:
private void OnValidSubmit()
{
// This method is automatically called by microsoft when the form
// data values pass validation (triggered after pressing the
// "submit" button). In this method you invoke the event callback
// defined above, passing to the the parent component the modified
// version of the Cita parameter
CitaChanged.InvokeAsync(cita);
}
After this method is executed, the Cita
property defined in the container, contains the newly modified values, and you can post it to the web api end point. How ? By altering the Cita property from
private Cita Cita {get; set;} = new Cita();
To something like this:
private Cita _cita = new Cita();
private Cita Cita
{
get { return _cita; }
set
{
if( _cita != value)
{
_cita = value;
_ = CrearCita();
}
}
}
This is how you instantiate the FormularioCita
component from the container:
<FormularioCita TextoBoton="Crear cita" @bind-Cita="@Cita"/>
Note that we do not pass the CrearCita
method as a parameter, but call it locally from a property.
The above code illustrate how to create a two-way data binding between components. It can be done differently, which is the preferred way, in the current case. Pass the Cita
object and the name of the method to call back - CrearCita
<FormularioCita TextoBoton="Crear cita" Cita="@Cita" CitaChanged="CrearCita"/>
Note that it is actually what you did, but you should also define CrearCita
as accepting a parameter, which you did not, something like this:
private async Task CrearCita(Cita cita){...}
This was the main issue with your code, which is why an error related to null reference is thrown. You are trying to post the local instance of Cita, without the values added in the EditForm...
CodePudding user response:
The simple solution is to only add an empty value option into InputSelect
InputSelect:
<InputSelect id="Medico" placeholder="Medico" @bind-Value="@Cita.MedicoId">
<option value="">---</option>
@if(medicos!=null){
foreach (Medico m in medicos)
{
<option value="@m.Id">@m.Apellidos</option>
}
}
</InputSelect>
It is finally working!