Foreign keys
Pick related records with a lookup or a select
When a field is a foreign key, like Booking.CustomerId, you want people to pick a customer, not type a number. Register the related type as a resource, and EF Core forms do the rest:
admin.AddEfCoreResource<Customer>(resource => { /* ... */ });
admin.AddEfCoreResource<Booking>(resource =>
{
resource.AllowCreate(create => create.Fields(fields =>
{
fields.Add(booking => booking.CustomerId);
}));
});CustomerId now gets a lookup: the field shows the selected customer, and Choose opens a sheet that searches as you type. It's labelled "Customer", after the resource. How customers look, search and sort in it comes from the Customer resource's reference settings.
Without a Customer resource, the field stays a number input, and the panel reports a configuration problem to tell you.
A select instead
For a short list, a select is quicker:
fields.Add(trip => trip.GuideId).UseSelectEditor(select => select.EmptyChoiceText = "No guide yet");Any choice editor works. A radio group with Appearance = RadioGroupAppearance.Cards shows each record as a card, with the resource's description and media.
Creating from a lookup
EnableCreate adds a New button to the lookup. It opens the related resource's create form in a sheet, and selects the new record when it's saved:
fields.Add(booking => booking.CustomerId).UseLookupSheetEditor(lookup => lookup.EnableCreate());The related resource needs AllowCreate. People who aren't allowed to use that resource don't see New.
Lookup settings
using StellarAdmin.Resources.Editors;
fields.Add(booking => booking.CustomerId).UseLookupSheetEditor(lookup =>
{
lookup.Field(field => field.Layout = LookupSheetEditorLayout.Input);
lookup.Sheet(sheet =>
{
sheet.Title = "Choose a customer";
sheet.SearchPlaceholder = "Name or email";
});
});| On | Settings |
|---|---|
Field | Layout (Card or Input), EmptyText, AllowClear, ShowMedia |
Sheet | Title, SearchPlaceholder, MinimumSearchLength, PageSize (20 by default), ShowMedia |
The fixed texts, like "Choose customer", "New" and "Load more", are global labels.
Form models
A custom form model has no foreign key metadata, so name the resource:
fields.Add(form => form.CustomerId).UseLookupSheetEditor(lookup => lookup.UseResource<Customer>());UseResource<T>() is on every choice editor and both lookups.
Related types that aren't resources
Countries, cities and other reference data often don't need pages of their own. Load them straight from EF Core with UseItems:
using StellarAdmin.EntityFrameworkCore;
fields.Add(destination => destination.CountryId).UseSelectEditor(select =>
select.UseItems<Country>(
country => country.Id,
country => country.Name,
items => items.OrderBy(country => country.Name)));The value has to match the field's type. The items builder also has UseDescription, UseGroup, and UseIcon, UseAvatar, UseImage or UseCode for media. On a lookup, SearchOn sets what the search matches.
Lookups without EF Core
For data from an API or another store, write a LookupSource:
using StellarAdmin.Resources.Editors;
public class AirportSource(AirportClient client) : LookupSource<Airport, string>
{
public override string GetValue(Airport airport) => airport.Code;
public override string GetTitle(Airport airport) => airport.City;
public override string? GetCode(Airport airport) => airport.Code;
public override Task<IReadOnlyCollection<Airport>> FindAsync(
IReadOnlyCollection<string> values, CancellationToken cancellationToken) =>
client.FindAsync(values, cancellationToken);
public override Task<LookupPage<Airport>> SearchAsync(
LookupQuery query, CancellationToken cancellationToken) =>
client.SearchAsync(query.Term, query.Skip, query.Take, cancellationToken);
}fields.Add(guide => guide.HomeBase).UseLookupSheetEditor(lookup => lookup.UseSource<AirportSource>());FindAsync loads the records already picked. SearchAsync gets the search term (null when empty) and the page to return. The source can take services in its constructor.
A resource with a custom data source doesn't need a lookup source: UseResource<T>() loads its records through ListAsync.