Skip to content

Commit d8b3a27

Browse files
committed
feat: Add OData support for Blazor AutoComplete component
- Introduced ODataOptions class for configuring OData data source parameters. - Implemented ODataQueryBuilder to construct OData query strings based on search parameters. - Created ODataResponseParser to parse OData JSON responses into typed collections. - Added unit tests for OData data source, query builder, and response parser to ensure functionality and correctness. - Included support for various OData features such as filtering, selecting, ordering, and counting.
1 parent cb7cdae commit d8b3a27

16 files changed

Lines changed: 2418 additions & 1 deletion

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ appsettings.json
8686
claude_workspace/
8787
.claude-workspace/
8888
docs/
89-
89+
*.nupkg
90+
*.snupkg
9091
############################
9192
# Test & Build Output
9293
############################

EasyAppDev.Blazor.AutoComplete.sln

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoComplete.Tests", "tests
2323
EndProject
2424
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoComplete.PerformanceTests", "tests\AutoComplete.PerformanceTests\AutoComplete.PerformanceTests.csproj", "{B2C3D4E5-F6A7-8901-BCDE-F12345678901}"
2525
EndProject
26+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAppDev.Blazor.AutoComplete.OData", "src\EasyAppDev.Blazor.AutoComplete.OData\EasyAppDev.Blazor.AutoComplete.OData.csproj", "{CC7C0450-CC37-44DE-B0DB-F2719DC6A197}"
27+
EndProject
28+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoComplete.ODataTests", "tests\AutoComplete.ODataTests\AutoComplete.ODataTests.csproj", "{3F97AFAE-6181-44CA-B641-2BD1CB3104E7}"
29+
EndProject
2630
Global
2731
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2832
Debug|Any CPU = Debug|Any CPU
@@ -117,6 +121,30 @@ Global
117121
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|x64.Build.0 = Release|Any CPU
118122
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|x86.ActiveCfg = Release|Any CPU
119123
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|x86.Build.0 = Release|Any CPU
124+
{CC7C0450-CC37-44DE-B0DB-F2719DC6A197}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
125+
{CC7C0450-CC37-44DE-B0DB-F2719DC6A197}.Debug|Any CPU.Build.0 = Debug|Any CPU
126+
{CC7C0450-CC37-44DE-B0DB-F2719DC6A197}.Debug|x64.ActiveCfg = Debug|Any CPU
127+
{CC7C0450-CC37-44DE-B0DB-F2719DC6A197}.Debug|x64.Build.0 = Debug|Any CPU
128+
{CC7C0450-CC37-44DE-B0DB-F2719DC6A197}.Debug|x86.ActiveCfg = Debug|Any CPU
129+
{CC7C0450-CC37-44DE-B0DB-F2719DC6A197}.Debug|x86.Build.0 = Debug|Any CPU
130+
{CC7C0450-CC37-44DE-B0DB-F2719DC6A197}.Release|Any CPU.ActiveCfg = Release|Any CPU
131+
{CC7C0450-CC37-44DE-B0DB-F2719DC6A197}.Release|Any CPU.Build.0 = Release|Any CPU
132+
{CC7C0450-CC37-44DE-B0DB-F2719DC6A197}.Release|x64.ActiveCfg = Release|Any CPU
133+
{CC7C0450-CC37-44DE-B0DB-F2719DC6A197}.Release|x64.Build.0 = Release|Any CPU
134+
{CC7C0450-CC37-44DE-B0DB-F2719DC6A197}.Release|x86.ActiveCfg = Release|Any CPU
135+
{CC7C0450-CC37-44DE-B0DB-F2719DC6A197}.Release|x86.Build.0 = Release|Any CPU
136+
{3F97AFAE-6181-44CA-B641-2BD1CB3104E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
137+
{3F97AFAE-6181-44CA-B641-2BD1CB3104E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
138+
{3F97AFAE-6181-44CA-B641-2BD1CB3104E7}.Debug|x64.ActiveCfg = Debug|Any CPU
139+
{3F97AFAE-6181-44CA-B641-2BD1CB3104E7}.Debug|x64.Build.0 = Debug|Any CPU
140+
{3F97AFAE-6181-44CA-B641-2BD1CB3104E7}.Debug|x86.ActiveCfg = Debug|Any CPU
141+
{3F97AFAE-6181-44CA-B641-2BD1CB3104E7}.Debug|x86.Build.0 = Debug|Any CPU
142+
{3F97AFAE-6181-44CA-B641-2BD1CB3104E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
143+
{3F97AFAE-6181-44CA-B641-2BD1CB3104E7}.Release|Any CPU.Build.0 = Release|Any CPU
144+
{3F97AFAE-6181-44CA-B641-2BD1CB3104E7}.Release|x64.ActiveCfg = Release|Any CPU
145+
{3F97AFAE-6181-44CA-B641-2BD1CB3104E7}.Release|x64.Build.0 = Release|Any CPU
146+
{3F97AFAE-6181-44CA-B641-2BD1CB3104E7}.Release|x86.ActiveCfg = Release|Any CPU
147+
{3F97AFAE-6181-44CA-B641-2BD1CB3104E7}.Release|x86.Build.0 = Release|Any CPU
120148
EndGlobalSection
121149
GlobalSection(SolutionProperties) = preSolution
122150
HideSolutionNode = FALSE
@@ -129,5 +157,7 @@ Global
129157
{BA42D5A3-C877-4729-8EE5-EBCC7A49B51E} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
130158
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
131159
{B2C3D4E5-F6A7-8901-BCDE-F12345678901} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
160+
{CC7C0450-CC37-44DE-B0DB-F2719DC6A197} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
161+
{3F97AFAE-6181-44CA-B641-2BD1CB3104E7} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
132162
EndGlobalSection
133163
EndGlobal

README.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ A high-performance AutoComplete component for Blazor with AI-powered semantic se
1212
- **High Performance** - Virtualization for large datasets, debounced input
1313
- **Native AOT Ready** - Source generators, zero reflection, fully trimmable
1414
- **AI Semantic Search** - Optional package with OpenAI/Azure embeddings
15+
- **OData Integration** - Optional package for OData v3/v4 server-side filtering
1516
- **Accessible** - WCAG 2.1 AA, ARIA 1.2 Combobox pattern, keyboard navigation
1617
- **Theming** - 4 design presets (Material, Fluent, Modern, Bootstrap), CSS variables
1718
- **8 Display Modes** - Built-in layouts eliminate template boilerplate
@@ -276,6 +277,120 @@ Or use structured `ThemeOverrides`:
276277
<AutoComplete InputId="search" AriaLabel="Search products" ... />
277278
```
278279

280+
## OData Integration
281+
282+
Optional package for querying OData v3/v4 endpoints with automatic `$filter` generation.
283+
284+
### Installation
285+
286+
```bash
287+
dotnet add package EasyAppDev.Blazor.AutoComplete.OData
288+
```
289+
290+
### Usage
291+
292+
```razor
293+
@using EasyAppDev.Blazor.AutoComplete
294+
@using EasyAppDev.Blazor.AutoComplete.OData
295+
@inject HttpClient Http
296+
297+
<AutoComplete TItem="Product"
298+
DataSource="@_odataSource"
299+
TextField="@(p => p.Name)"
300+
@bind-Value="@selectedProduct"
301+
Placeholder="Search products..." />
302+
303+
@code {
304+
private ODataDataSource<Product> _odataSource = null!;
305+
private Product? selectedProduct;
306+
307+
protected override void OnInitialized()
308+
{
309+
var options = new ODataOptions
310+
{
311+
EndpointUrl = "https://api.example.com/odata/products",
312+
FilterStrategy = ODataFilterStrategy.StartsWith,
313+
Top = 20
314+
};
315+
_odataSource = new ODataDataSource<Product>(Http, options, "Name");
316+
}
317+
}
318+
```
319+
320+
### Multi-Field Search
321+
322+
```csharp
323+
// Search across multiple fields (combined with OR)
324+
_odataSource = new ODataDataSource<Product>(
325+
Http,
326+
options,
327+
searchFieldNames: new[] { "Name", "Description", "Category" });
328+
```
329+
330+
Generated OData: `$filter=(startswith(tolower(Name),'search') or startswith(tolower(Description),'search') or startswith(tolower(Category),'search'))`
331+
332+
### OData v3 Support
333+
334+
```csharp
335+
var options = new ODataOptions
336+
{
337+
EndpointUrl = "https://legacy-api.example.com/odata/products",
338+
Version = ODataVersion.V3, // Use v3 syntax
339+
FilterStrategy = ODataFilterStrategy.Contains
340+
};
341+
```
342+
343+
### Filter Strategy Mapping
344+
345+
| Strategy | OData v4 | OData v3 |
346+
|----------|----------|----------|
347+
| `StartsWith` | `startswith(field,'value')` | Same |
348+
| `Contains` | `contains(field,'value')` | `substringof('value',field)` |
349+
| `FuzzyFallback` | `contains()` + client re-rank | `substringof()` + client re-rank |
350+
351+
### OData Parameters
352+
353+
| Parameter | Type | Default | Description |
354+
|-----------|------|---------|-------------|
355+
| `EndpointUrl` | `string` | Required | OData endpoint URL |
356+
| `Version` | `ODataVersion` | `V4` | OData protocol version |
357+
| `FilterStrategy` | `ODataFilterStrategy` | `StartsWith` | Filter type |
358+
| `Top` | `int` | `100` | Max results ($top) |
359+
| `Select` | `string[]?` | `null` | Fields to return ($select) |
360+
| `OrderBy` | `string?` | `null` | Sort order ($orderby) |
361+
| `AdditionalFilter` | `string?` | `null` | Static filter ANDed with search |
362+
| `CaseInsensitive` | `bool` | `true` | Use tolower() wrapper |
363+
| `MinSearchLength` | `int` | `1` | Min chars before API call |
364+
| `CustomHeaders` | `Dictionary<string,string>?` | `null` | HTTP headers (e.g., Authorization) |
365+
366+
### Fluent Builder
367+
368+
```csharp
369+
var config = AutoCompleteConfig<Product>.Create()
370+
.WithODataSource(Http, "https://api.example.com/odata/products", "Name",
371+
opts => {
372+
opts.FilterStrategy = ODataFilterStrategy.Contains;
373+
opts.Top = 20;
374+
})
375+
.WithDisplayMode(ItemDisplayMode.TitleWithDescription)
376+
.Build();
377+
```
378+
379+
### Service Registration
380+
381+
```csharp
382+
// Configuration-based
383+
builder.Services.AddAutoCompleteOData(builder.Configuration, "ODataSettings");
384+
385+
// Explicit configuration
386+
builder.Services.AddAutoCompleteOData(
387+
"https://api.example.com/odata/products",
388+
options => {
389+
options.FilterStrategy = ODataFilterStrategy.Contains;
390+
options.Top = 50;
391+
});
392+
```
393+
279394
## AI Semantic Search
280395

281396
Optional package for meaning-based search using embeddings.
@@ -451,6 +566,7 @@ Configuration in `appsettings.json`:
451566
| `EasyAppDev.Blazor.AutoComplete` | Core component |
452567
| `EasyAppDev.Blazor.AutoComplete.Generators` | Source generators (build-time only) |
453568
| `EasyAppDev.Blazor.AutoComplete.AI` | Semantic search with embeddings |
569+
| `EasyAppDev.Blazor.AutoComplete.OData` | OData v3/v4 server-side filtering |
454570

455571
## Requirements
456572

samples/AutoComplete.Playground/Components/Pages/Home.razor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
<li><a href="/all-features">All Features Showcase</a> - Comprehensive demo</li>
3737
<li><a href="/theming-demo">Theming Demo</a> - Dynamic theming system</li>
3838
<li><a href="/ai-demo">AI Demo</a> - Semantic search capabilities</li>
39+
<li><a href="/odata-demo">OData Demo</a> - OData v3/v4 integration</li>
3940
</ul>
4041

4142
<h6 class="mt-4">Quick Start</h6>

0 commit comments

Comments
 (0)