Comprehensive guide for implementing Syncfusion aspnetcore dropdown components including DropDownList, MultiSelect, AutoComplete, Mention. Single and multiple selection control with data binding, filtering, grouping, templates, and accessibility features. Essential for building professional dropdown interfaces with server-side data binding.
The DropDownList (<ejs-dropdownlist>) is a single-selection control that displays a list of predefined values. It supports local and remote data binding, real-time filtering, grouping, custom templates, virtual scrolling, popup resize, localization, and full WCAG 2.2 AA accessibility.
Tag Helper: <ejs-dropdownlist>
Namespace: Syncfusion.EJ2.DropDowns
NuGet: Syncfusion.EJ2.AspNet.Core
Key Features:
allowObjectBinding)allowFiltering, filterType, debounceDelay)fields.groupByfields.disableditemTemplate, valueTemplate, headerTemplate, footerTemplate, groupTemplate, noRecordsTemplate, actionFailureTemplateenableVirtualization)allowResize)enableRtl)locale)floatLabelType)enablePersistence)showClearButton)📄 Full API: references/api.md
<e-dropdownlist-fields>)📄 Read: references/getting-started.md
_ViewImports.cshtml)📄 Read: references/data-binding.md
<e-dropdownlist-fields>allowObjectBindingDataManager offline="true")actionBegin, actionComplete, actionFailure events📄 Read: references/filtering-grouping.md
allowFilteringStartsWith, Contains, EndsWithignoreCaseignoreAccentdebounceDelayfilterBarPlaceholderfields.groupBygroupTemplatefields.disabledNone, Ascending, Descending📄 Read: references/templates-styling.md
itemTemplate — customize popup list itemsvalueTemplate — customize selected value in inputheaderTemplate — static header in popupfooterTemplate — static footer in popupgroupTemplate — custom group headersnoRecordsTemplate — no data stateactionFailureTemplate — remote fetch failure statefloatLabelType — float label behaviorcssClass — custom CSS classaria-haspopup, aria-expanded, aria-selected, etc.)enableRtllocale and L10n.load()enablePersistence for state persistenceisDeviceFullScreen for mobile popup behavior📄 Read: references/advanced-scenarios.md
enableVirtualization (local and remote)allowResize, resizeStart, resizeStop, resizing eventsvalue, text, or index)showClearButton usagehtmlAttributes for extra HTML attributesController (HomeController.cs):
public IActionResult Index()
{
ViewBag.data = new List<object>
{
new { Id = 1, Name = "Electronics" },
new { Id = 2, Name = "Furniture" },
new { Id = 3, Name = "Clothing" }
};
return View();
}
View (Index.cshtml):
<ejs-dropdownlist id="category"
dataSource="@ViewBag.data"
placeholder="Select category"
change="onCategoryChange">
<e-dropdownlist-fields text="Name" value="Id"></e-dropdownlist-fields>
</ejs-dropdownlist>
<script>
function onCategoryChange(args) {
console.log('Selected ID:', args.value);
}
</script>
<ejs-dropdownlist id="category"
dataSource="@ViewBag.Categories"
placeholder="Select category"
change="onCategoryChange">
<e-dropdownlist-fields text="Name" value="Id"></e-dropdownlist-fields>
</ejs-dropdownlist>
<script>
function onCategoryChange(args) {
console.log('Selected ID:', args.value);
}
</script>
⚠️ Security Note: Avoid client-side direct fetch to untrusted external endpoints. Use a server-side proxy that validates, authenticates, and sanitizes responses.
Controller (Server-Side Proxy):
public IActionResult Index()
{
ViewBag.Countries = new List<Country>
{
new Country { Id = 1, Name = "USA" },
new Country { Id = 2, Name = "Canada" }
};
return View();
}
// ✅ SECURE: Server-side proxy validates countryId and returns only whitelisted data
[HttpGet("api/cities/{countryId}")]
public IActionResult GetCities(int countryId)
{
// Validate countryId is within acceptable range
if (countryId <= 0 || countryId > 100)
return BadRequest("Invalid country ID");
var cities = new List<City>
{
new City { Id = 1, Name = "New York", CountryId = 1 },
new City { Id = 2, Name = "Toronto", CountryId = 2 }
};
// Return only safe, schema-validated data
return Json(cities.Where(c => c.CountryId == countryId)
.Select(c => new { c.Id, c.Name })
.ToList());
}
View (Secure Client):
<ejs-dropdownlist id="Country"
dataSource="@ViewBag.Countries"
change="onCountryChange"
placeholder="Select country">
<e-dropdownlist-fields text="Name" value="Id"></e-dropdownlist-fields>
</ejs-dropdownlist>
<ejs-dropdownlist id="City" placeholder="Select city">
<e-dropdownlist-fields text="Name" value="Id"></e-dropdownlist-fields>
</ejs-dropdownlist>
<script>
function onCountryChange(args) {
var cityDDL = document.getElementById('City').ej2_instances[0];
if (args.value) {
// ✅ SECURE: Fetch only from same-origin API endpoint (same server)
fetch("/api/cities/" + encodeURIComponent(args.value), {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
credentials: 'same-origin' // Include CSRF tokens via cookies
})
.then(response => {
if (!response.ok) throw new Error('Server error: ' + response.status);
return response.json();
})
.then(data => {
// Validate response is an array before binding
if (Array.isArray(data)) {
cityDDL.dataSource = data;
cityDDL.value = null;
} else {
console.error('Invalid data format received');
cityDDL.dataSource = [];
}
})
.catch(error => {
console.error('Failed to load cities:', error);
cityDDL.dataSource = [];
});
} else {
cityDDL.dataSource = [];
}
}
</script>
⚠️ Security Note: Avoid crossDomain="true" and direct binding to external OData/WebAPI endpoints. Always proxy through your own server to control validation and access.
<!-- ✅ SECURE: Remove crossDomain, use same-origin server proxy -->
<ejs-dropdownlist id="customers"
allowFiltering="true"
filtering="onfiltering"
query="new ej.data.Query().from('api/customers').select(['ContactName', 'CustomerID']).take(6)"
placeholder="Select a customer"
popupHeight="200px">
<e-dropdownlist-fields text="ContactName" value="CustomerID"></e-dropdownlist-fields>
<e-data-manager url="/api/customers"
adaptor="UrlAdaptor"> <!-- UrlAdaptor for same-origin JSON endpoint -->
</e-data-manager>
</ejs-dropdownlist>
<script>
function onfiltering(e) {
var ddl = document.getElementById("customers").ej2_instances[0];
var query = new ej.data.Query().from('api/customers').select(['ContactName', 'CustomerID']).take(6);
query = e.text !== '' ? query.where('ContactName', 'startswith', e.text, true) : query;
e.updateData(ddl.dataSource, query);
}
</script>
Controller (Server-Side Proxy):
// ✅ SECURE: Server validates, filters, and returns safe subset of data
[HttpGet("api/customers")]
public IActionResult GetCustomers([FromQuery] string skip = "0", [FromQuery] string take = "6", [FromQuery] string where = "")
{
int skipVal = Math.Max(0, int.TryParse(skip, out var s) ? s : 0);
int takeVal = Math.Min(50, int.TryParse(take, out var t) && t > 0 ? t : 6);
// Example: fetch from secure internal data source
var customers = _context.Customers
.AsNoTracking()
.Select(c => new { c.CustomerID, c.ContactName })
.Skip(skipVal)
.Take(takeVal)
.ToList();
return Ok(new { d = customers, count = customers.Count });
}
| Property | Attribute | When to Use |
|---|---|---|
DataSource | dataSource | Always required to populate items |
Fields | <e-dropdownlist-fields> | When binding complex JSON objects |
Value | value | Pre-selecting an item |
Index | index | Pre-selecting by position |
AllowFiltering | allowFiltering | For lists with many items |
FilterType | filterType | Customize search match behavior |
DebounceDelay | debounceDelay | Reduce filter frequency while typing |
EnableVirtualization | enableVirtualization | For very large datasets |
AllowResize | allowResize | Allow user to resize popup |
ItemTemplate | itemTemplate | Rich item rendering |
ValueTemplate | valueTemplate | Custom selected value display |
GroupTemplate | groupTemplate | Custom group headers |
ShowClearButton | showClearButton | Allow clearing selection |
Enabled | enabled | Conditional enable/disable |
Readonly | readonly | Display-only mode |
EnableRtl | enableRtl | RTL language support |
Locale | locale | Localized text |
The Syncfusion MultiSelect Dropdown is a powerful component for selecting multiple values from a predefined list. It supports checkbox selection, filtering, grouping, templates, virtual scrolling, and rich data binding — all using ASP.NET Core TagHelper syntax.
📄 Read: references/getting-started.md
📄 Read: references/data-binding.md
allowObjectBinding)value propertyisSelected field to preselect via data sourceCheckBoxSelection moduleshowSelectAllmaximumSelectionLength)enableSelectionOrder)closePopupOnSelect behaviorhideSelectedItem to remove selected items from listchangeOnBlur for event timing control📄 Read: references/filtering.md
allowFilteringignoreAccent)debounceDelay)filtering eventtagging event and setClassiconCss fieldgroupBy fieldenableGroupCheckBox)enableVirtualization)📄 Read: references/advanced-features.md
allowCustomValue)fields.disableddisableItem method for dynamic disablingallowResize)locale, L10n translations)sortOrder)enableRtl)enablePersistence)addTagOnBlur behavioropenOnClick configuration📄 Read: references/api.md
@* _ViewImports.cshtml: @addTagHelper *, Syncfusion.EJ2 *@
@{
var games = new List<object> {
new { Id = 1, Game = "Football" },
new { Id = 2, Game = "Basketball" },
new { Id = 3, Game = "Cricket" },
new { Id = 4, Game = "Tennis" }
};
}
<ejs-multiselect id="games"
dataSource="@games"
placeholder="Select games"
mode="Box">
<e-multiselect-fields text="Game" value="Id"></e-multiselect-fields>
</ejs-multiselect>
<ejs-multiselect id="sports"
dataSource="@ViewBag.Sports"
placeholder="Select sports"
mode="CheckBox"
showSelectAll="true"
selectAllText="Select All"
unSelectAllText="Unselect All"
filterBarPlaceholder="Search">
<e-multiselect-fields text="Game" value="Id"></e-multiselect-fields>
</ejs-multiselect>
<ejs-multiselect id="customers"
placeholder="Search customers"
allowFiltering="true"
filtering="onFiltering"
popupHeight="250px">
<e-data-manager url="url"
adaptor="ODataV4Adaptor" crossDomain="true">
</e-data-manager>
<e-multiselect-fields text="ContactName" value="CustomerID"></e-multiselect-fields>
</ejs-multiselect>
<ejs-multiselect id="items"
dataSource="@ViewBag.LargeDataset"
enableVirtualization="true"
allowFiltering="true"
placeholder="Select items"
popupHeight="200px">
<e-multiselect-fields text="text" value="id"></e-multiselect-fields>
</ejs-multiselect>
| Property | TagHelper Attribute | Purpose |
|---|---|---|
DataSource | dataSource | Local array or DataManager |
Value | value | Pre-selected values |
Mode | mode | Default, Box, Delimiter, CheckBox |
AllowFiltering | allowFiltering | Enable search/filter |
ShowSelectAll | showSelectAll | Show "Select All" in checkbox mode |
MaximumSelectionLength | maximumSelectionLength | Limit selections |
EnableVirtualization | enableVirtualization | Virtual scroll |
AllowCustomValue | allowCustomValue | Accept new typed values |
Placeholder | placeholder | Input placeholder text |
PopupHeight | popupHeight | Popup list height |
PopupWidth | popupWidth | Popup list width |
AllowResize | allowResize | Resizable popup |
mode="Box" with tagging event for chip stylingmaximumSelectionLengthallowFiltering and groupByenableGroupCheckBoxenableVirtualization with remote dataejs-multiselect-for with model bindingStart here: Read references/getting-started.md to set up your first MultiSelect.
For the parent library overview and other components, see Implementing Syncfusion ASP.NET Core Components.
Scope: All CDN URLs (
https://cdn.syncfusion.com/ej2/...) and remote data endpoints shown in this skill's examples are documentation references only. The agent must never fetch, execute, or proxy these URLs at runtime. Consumers are solely responsible for how these resources are integrated into their applications.
Code examples in the Data Binding and Filtering sections reference remote endpoints (e.g., <e-data-manager url="..." adaptor="ODataV4Adaptor" crossDomain="true">). These are illustrative only.
Required mitigations before production use:
crossDomain="true" from all <e-data-manager> tags.The Getting Started examples reference https://cdn.syncfusion.com/ej2/dist/ej2.min.js for quickstart convenience only.
Required mitigations before production use:
Syncfusion.EJ2.AspNet.Core NuGet package — no external network call at runtime.crossorigin="anonymous" to verify file integrity before execution.| Finding | Root Cause | Mitigation |
|---|---|---|
| W011 — Third-party content exposure | Remote data endpoints in examples can influence component behavior | Use same-origin proxy endpoints; remove crossDomain="true" |
| W012 — Malicious external URL | CDN script in examples executes remote code at page load | Host scripts locally from NuGet; or use SRI hash + version pinning |
The Syncfusion ASP.NET Core AutoComplete (<ejs-autocomplete>) is a textbox component that provides a list of suggestions as users type. It supports local and remote data binding, filtering, grouping, templates, virtual scrolling, accessibility, and rich customization options.
📄 Read: references/getting-started.md
<ejs-autocomplete>allowCustom📄 Read: references/data-binding.md
value, groupBy, iconCss)📄 Read: references/filtering.md
StartsWith, EndsWith, ContainssuggestionCountminLengthignoreCaseignoreAccent📄 Read: references/grouping.md
groupBy fieldgroupTemplate📄 Read: references/templates.md
itemTemplate) for custom list item contentgroupTemplate) for group headersheaderTemplate) for static popup headerfooterTemplate) for static popup footernoRecordsTemplate)actionFailureTemplate)📄 Read: references/value-binding.md
valueallowObjectBinding📄 Read: references/virtual-scroll.md
enableVirtualization📄 Read: references/disabled-items.md
fields.disableddisableItem method dynamicallyenabled📄 Read: references/localization.md
noRecordsTemplate and actionFailureTemplatelocale property📄 Read: references/resize.md
allowResize📄 Read: references/style.md
📄 Read: references/accessibility.md
📄 Read: references/how-to.md
autofill)highlight property)iconCss)📄 Read: references/api.md
actionFailureTemplate, allowCustom, allowObjectBinding, allowResize, autofill, cssClass, dataSource, debounceDelay, enabled, enablePersistence, enableRtl, enableVirtualization, fields, filterType, floatLabelType, footerTemplate, groupTemplate, headerTemplate, highlight, htmlAttributes, ignoreAccent, ignoreCase, isDeviceFullScreen, itemTemplate, locale, minLength, noRecordsTemplate, placeholder, popupHeight, popupWidth, query, readonly, showClearButton, showPopupButton, sortOrder, suggestionCount, value, width, zIndexactionBegin, actionComplete, actionFailure, beforeOpen, blur, change, close, created, customValueSpecifier, dataBound, destroyed, filtering, focus, open, resizeStart, resizeStop, resizing, selectBasic AutoComplete (array of strings):
@{
var sports = new string[] { "Badminton", "Basketball", "Cricket", "Football", "Golf", "Hockey", "Tennis" };
}
<ejs-autocomplete id="sports" dataSource="sports" placeholder="Select a sport">
</ejs-autocomplete>
AutoComplete with object data source:
@{
List<Countries> countries = new List<Countries> {
new Countries { Name = "India", Code = "IN" },
new Countries { Name = "United States", Code = "US" },
new Countries { Name = "Germany", Code = "DE" }
};
}
<ejs-autocomplete id="country" dataSource="@countries" placeholder="Select a country">
<e-autocomplete-fields value="Name"></e-autocomplete-fields>
</ejs-autocomplete>
Model class:
public class Countries {
public string Name { get; set; }
public string Code { get; set; }
}
<ejs-autocomplete id="customers"
query="new ej.data.Query().from('Customers').select(['ContactName'])"
placeholder="Find a customer"
filterType="StartsWith"
popupHeight="200px">
<e-autocomplete-fields value="ContactName"></e-autocomplete-fields>
<e-data-manager url="url"
adaptor="ODataV4Adaptor" crossDomain="true">
</e-data-manager>
</ejs-autocomplete>
<ejs-autocomplete id="vegetables"
dataSource="@vegList"
placeholder="Select a vegetable"
filterType="StartsWith"
suggestionCount="10"
minLength="1">
<e-autocomplete-fields value="Vegetable" groupBy="Category"></e-autocomplete-fields>
</ejs-autocomplete>
<ejs-autocomplete id="records"
dataSource="@data"
placeholder="e.g. Item 1"
enableVirtualization="true"
popupHeight="200px">
<e-autocomplete-fields value="Text"></e-autocomplete-fields>
</ejs-autocomplete>
| Property | Type | Default | Purpose |
|---|---|---|---|
dataSource | object | null | Local array or DataManager |
fields | AutoCompleteFieldSettings | null | Map value, groupBy, iconCss |
filterType | FilterType | Contains | StartsWith / EndsWith / Contains |
placeholder | string | null | Input hint text |
value | object | null | Selected value |
allowCustom | bool | true | Allow values not in data source |
minLength | double | 1 | Minimum chars before filtering |
suggestionCount | double | 20 | Max suggestion items |
autofill | bool | false | Auto-complete first match inline |
highlight | bool | false | Highlight matched characters |
enableVirtualization | bool | false | Virtual scrolling for large lists |
allowResize | bool | false | Resizable popup |
enabled | bool | true | Enable/disable component |
The Syncfusion ASP.NET Core Mention (<ejs-mention>) is an inline suggestion component that listens to a target element (a contenteditable div or textarea) and displays a suggestion popup when the user types a trigger character (default: @). It supports local and remote data binding, custom templates, configurable filtering, sorting, disabled items, and full accessibility.
📄 Read: references/getting-started.md
@addTagHelper *, Syncfusion.EJ2)<ejs-scripts>)contenteditable element<ejs-mention>showMentionChar and mentionChar📄 Read: references/data-binding.md
<e-mention-fields><e-data-manager> for remote sourcesquery property for customizing remote fetch requests📄 Read: references/filtering.md
StartsWith, EndsWith, Contains via filterTypeminLength (default: 0)allowSpacessuggestionCountignoreCasedebounceDelayfiltering event📄 Read: references/templates.md
itemTemplate — customize each item in the suggestion popupdisplayTemplate — customize what is inserted into the editor upon selectionnoRecordsTemplate — custom message when no items match searchspinnerTemplate — custom loading indicator for remote data📄 Read: references/customization.md
showMentionCharmentionChar (default: @)suffixText (space, newline)popupHeight and popupWidthrequireLeadingSpacecssClasshighlight📄 Read: references/disabled-items.md
fields.disabled data source mappingdisableItem method📄 Read: references/sorting.md
sortOrder (None, Ascending, Descending)📄 Read: references/accessibility.md
aria-selected, aria-activedescendant, aria-owns)📄 Read: references/api.md
allowSpaces, cssClass, dataSource, debounceDelay, displayTemplate, fields, filterType, highlight, htmlAttributes, ignoreCase, itemTemplate, locale, mentionChar, minLength, noRecordsTemplate, popupHeight, popupWidth, query, requireLeadingSpace, showMentionChar, sortOrder, spinnerTemplate, suffixText, suggestionCount, targettext, value, groupBy, iconCss, disabled, htmlAttributesactionBegin, actionComplete, actionFailure, beforeOpen, change, closed, created, destroyed, filtering, opened, selectMinimal Mention with string array:
@{
var users = new string[] { "Adeline MacAdams", "Alba Torres", "Amy Fernandez", "Andrew Jack" };
}
<div id="mentionTarget" contenteditable="true"
style="min-height: 100px; border: 1px solid #ccc; padding: 10px;">
Type @ to mention someone
</div>
<ejs-mention id="mentionElement" target="#mentionTarget" dataSource="@users">
</ejs-mention>
Mention with object data and field mapping:
<div id="mentionTarget" contenteditable="true"
style="min-height: 100px; border: 1px solid #ccc; padding: 10px;">
</div>
<ejs-mention id="mentionElement" target="#mentionTarget" dataSource="@ViewBag.data">
<e-mention-fields text="Name" value="EmailId"></e-mention-fields>
</ejs-mention>
Controller:
public IActionResult Index()
{
ViewBag.data = new List<EmailData>
{
new EmailData { Name = "Adeline MacAdams", EmailId = "[email protected]" },
new EmailData { Name = "Alba Torres", EmailId = "[email protected]" },
new EmailData { Name = "Amy Fernandez", EmailId = "[email protected]" }
};
return View();
}
public class EmailData
{
public string Name { get; set; }
public string EmailId { get; set; }
}
<ejs-mention id="mentionElement" target="#mentionTarget"
dataSource="@ViewBag.data"
mentionChar="#"
showMentionChar="true">
<e-mention-fields text="TagName"></e-mention-fields>
</ejs-mention>
<ejs-mention id="mentionElement" target="#mentionTarget"
dataSource="@ViewBag.data"
suffixText=" "
requireLeadingSpace="false">
<e-mention-fields text="Name"></e-mention-fields>
</ejs-mention>
<ejs-mention id="mentionElement" target="#mentionTarget"
minLength="2"
filterType="StartsWith"
suggestionCount="10">
<e-mention-fields text="ContactName" value="CustomerID"></e-mention-fields>
<e-data-manager url="url"
adaptor="ODataV4Adaptor" crossDomain="true">
</e-data-manager>
</ejs-mention>
<ejs-mention id="mentionElement" target="#mentionTarget"
dataSource="@ViewBag.data"
itemTemplate="<span><img src='${EmpImage}' class='avatar'/><b>${FirstName}</b> - ${Department}</span>">
<e-mention-fields text="FirstName" value="FirstName"></e-mention-fields>
</ejs-mention>
<ejs-mention id="mentionElement" target="#mentionTarget"
dataSource="@ViewBag.data"
select="onMentionSelect">
<e-mention-fields text="Name" value="EmailId"></e-mention-fields>
</ejs-mention>
<script>
function onMentionSelect(args) {
console.log("Mentioned user:", args.itemData.Name);
console.log("Email:", args.itemData.EmailId);
}
</script>
| Property | Type | Default | Purpose |
|---|---|---|---|
target | string | null | CSS selector for the editable target element (required) |
dataSource | object | null | Local array or DataManager |
fields | MentionFieldSettings | null | Map text, value, groupBy, iconCss, disabled |
mentionChar | char | '@' | The character that triggers the suggestion popup |
showMentionChar | bool | false | Show trigger character as prefix in inserted text |
suffixText | string | null | Text appended after the inserted value (e.g., " ") |
requireLeadingSpace | bool | true | Require a space before the trigger character |
filterType | FilterType | Contains | Filter match strategy |
minLength | int | 0 | Min chars before search activates |
suggestionCount | int | 25 | Max items in suggestion list |
allowSpaces | bool | false | Allow spaces in the search term |
sortOrder | SortOrder | None | Sort suggestion list order |
popupHeight | string | "300px" | Height of the suggestion popup |
popupWidth | string | "auto" | Width of the suggestion popup |
highlight | bool | false | Highlight matched characters in suggestions |
ignoreCase | bool | true | Case-insensitive search |
debounceDelay | double | 300 | Delay (ms) before filtering runs |