Reputation: 77
I'm having trouble updating a second dropdown list when a value has been selected in the first one in ASP.NET Core Razor Pages.
I have the following Model:
public class OfferModel
{
[BindProperty]
public CreateOfferViewModel ViewModel { get; set; }
public List<SelectListItem> ServiceTypes { get; set; }
public List<SelectListItem> ServiceSubTypes { get; set; }
private readonly IOfferAppService _offerAppService;
public OfferModel(IOfferAppService service)
{
_offerAppService = service;
}
public virtual async Task OnGetAsync()
{
var serviceTypeLookup = await _offerAppService.GetServiceTypeLookupAsync();
ServiceTypes = serviceTypeLookup.Items
.Select(x => new SelectListItem(x.Name, x.Id.ToString()))
.ToList();
var serviceSubTypeLookup = await _offerAppService.GetServiceSubTypeLookupAsync();
ServiceSubTypes = serviceSubTypeLookup.Items
.Select(x => new SelectListItem(x.Name, x.Id.ToString()))
.ToList();
}
}
And the CreateOfferViewModel:
public class CreateOfferViewModel
{
[SelectItems(nameof(ServiceTypes))]
public Guid ServiceTypeId { get; set; }
[SelectItems(nameof(ServiceSubTypes))]
public Guid ServiceSubTypeId { get; set; }
}
And the View:
<select asp-for="ViewModel.ServiceSubTypeId" asp-items="Model.ServiceSubTypes"></select>
<select asp-for="ViewModel.ServiceTypeId" asp-items="Model.ServiceTypes" ></select>
I have tried creating another method like so:
public virtual async Task OnGetSelectServiceTypeAsync(Guid serviceTypeId)
{
var serviceSubTypeLookup = await _offerAppService.GetServiceSubTypeLookupAsync(serviceTypeId);
ServiceSubTypes = serviceSubTypeLookup.Items
.Select(x => new SelectListItem(x.Name, x.Id.ToString()))
.ToList();
}
And add a reference in the view to call it with jQuery:
<a asp-page-handler="SelectServiceType" asp-route-id="@Model.ViewModel.ServiceTypeId">Update</a>
But I get a NullReferenceException.
What should I do? What is the correct way of doing this?
Upvotes: 0
Views: 1909
Reputation: 36715
But I get a NullReferenceException.
That is because you use asp-route-id="@Model.ViewModel.ServiceTypeId"
but do not set any value to it.
There are two ways to meet your requirements.
Model:
public class CreateOfferViewModel
{
public Guid ServiceTypeId { get; set; }
public Guid ServiceSubTypeId { get; set; }
}
public class ServiceSubTypes
{
public Guid Id { get; set; }
public string Name { get; set; }
[ForeignKey("ServiceTypes")]
public Guid ServiceTypeId { get; set; }
public ServiceTypes ServiceTypes { get; set; }
}
public class ServiceTypes
{
public Guid Id { get; set; }
public string Name { get; set; }
public List<ServiceSubTypes> ServiceSubTypes { get; set; }
}
The first way is to use ajax:
Index.cshtml:
@page
@model IndexModel
<select asp-for="ViewModel.ServiceTypeId" asp-items="Model.ServiceTypes"></select>
<select asp-for="ViewModel.ServiceSubTypeId"></select>
@section Scripts
{
<script>
$(function () {
$("#ViewModel_ServiceTypeId").on("change", function () {
var ServiceTypeId = $(this).val();
$("#ViewModel_ServiceSubTypeId").empty();
$.getJSON(`?handler=SelectServiceType&serviceTypeId=${ServiceTypeId}`, (data) => {
$.each(data, function (i, item) {
console.log(data);
console.log(item);
$("#ViewModel_ServiceSubTypeId").append(`<option value="${item.id}">${item.name}</option>`);
});
});
});
});
</script>
}
Index.cshtml.cs:
public class IndexModel : PageModel
{
[BindProperty]
public CreateOfferViewModel ViewModel { get; set; }
public List<SelectListItem> ServiceTypes { get; set; }
public List<SelectListItem> ServiceSubTypes { get; set; }
private readonly IOfferAppService _offerAppService;
public IndexModel(IOfferAppService service)
{
_offerAppService = service;
}
public virtual async Task OnGetAsync()
{
var serviceTypeLookup = await _offerAppService.GetServiceTypeLookupAsync();
ServiceTypes = serviceTypeLookup
.Select(x => new SelectListItem(x.Name, x.Id.ToString()))
.ToList();
var serviceSubTypeLookup = await _offerAppService.GetServiceSubTypeLookupAsync();
ServiceSubTypes = serviceSubTypeLookup
.Select(x => new SelectListItem(x.Name, x.Id.ToString()))
.ToList();
}
public virtual async Task<JsonResult> OnGetSelectServiceTypeAsync(Guid serviceTypeId)
{
var serviceSubTypeLookup = await _offerAppService.GetServiceSubTypeLookupAsync(serviceTypeId);
return new JsonResult(serviceSubTypeLookup);
}
}
Service:
public interface IOfferAppService
{
Task<List<ServiceTypes>> GetServiceTypeLookupAsync();
Task<List<ServiceSubTypes>> GetServiceSubTypeLookupAsync();
Task<List<ServiceSubTypes>> GetServiceSubTypeLookupAsync(Guid id);
}
public class OfferApp : IOfferAppService
{
private readonly RazorProj3_1Context _context;
public OfferApp(RazorProj3_1Context context)
{
_context = context;
}
public async Task<List<ServiceSubTypes>> GetServiceSubTypeLookupAsync()
{
var model =await _context.ServiceSubTypes.ToListAsync();
return model;
}
public async Task<List<ServiceSubTypes>> GetServiceSubTypeLookupAsync(Guid id)
{
var model = await _context.ServiceSubTypes
.Where(a => a.ServiceTypeId == id).ToListAsync();
return model;
}
public async Task<List<ServiceTypes>> GetServiceTypeLookupAsync()
{
var model = await _context.ServiceTypes.ToListAsync();
return model;
}
}
The second way is like what you want to use jquery to add the value to url,but this way has a disadvantage,it would expose the URL in the browser address bar:
Index.cshtml:
@page
@model IndexModel
<select asp-for="ViewModel.ServiceTypeId" asp-items="Model.ServiceTypes"></select>
<select asp-for="ViewModel.ServiceSubTypeId" asp-items="Model.ServiceSubTypes"></select>
<a href="">Update</a>
@section Scripts
{
<script>
$(function () {
$("#ViewModel_ServiceTypeId").on("change", function () {
var ServiceTypeId = $(this).val();
var url = $("a").attr("href", "?handler=SelectServiceType&serviceTypeId=" + ServiceTypeId);
});
});
</script>
}
Index.cshtml.cs:
public class IndexModel : PageModel
{
[BindProperty]
public CreateOfferViewModel ViewModel { get; set; }
public List<SelectListItem> ServiceTypes { get; set; }
public List<SelectListItem> ServiceSubTypes { get; set; }
private readonly IOfferAppService _offerAppService;
public IndexModel(IOfferAppService service)
{
_offerAppService = service;
}
public virtual async Task OnGetSelectServiceTypeAsync(Guid serviceTypeId)
{
var serviceTypeLookup = await _offerAppService.GetServiceTypeLookupAsync();
ServiceTypes = serviceTypeLookup
.Select(x => new SelectListItem(x.Name, x.Id.ToString()))
.ToList();
//obtain the first select item selected
var data = ServiceTypes.Where(a => a.Value == serviceTypeId.ToString()).FirstOrDefault();
data.Selected = true;
var serviceSubTypeLookup = await _offerAppService.GetServiceSubTypeLookupAsync(serviceTypeId);
ServiceSubTypes = serviceSubTypeLookup
.Select(x => new SelectListItem(x.Name, x.Id.ToString()))
.ToList();
}
}
Upvotes: 1