ViewForge 1.0.0
See the version list below for details.
dotnet add package ViewForge --version 1.0.0
NuGet\Install-Package ViewForge -Version 1.0.0
<PackageReference Include="ViewForge" Version="1.0.0" />
<PackageVersion Include="ViewForge" Version="1.0.0" />
<PackageReference Include="ViewForge" />
paket add ViewForge --version 1.0.0
#r "nuget: ViewForge, 1.0.0"
#:package ViewForge@1.0.0
#addin nuget:?package=ViewForge&version=1.0.0
#tool nuget:?package=ViewForge&version=1.0.0
ViewForge ⚒️
A high-performance .NET library that dynamically maps SQL Views to strongly-typed models with built-in dynamic filtering, sorting, and pagination — all via a clean, fluent API. No manual SQL building required.
✨ Features
- 🎯 Auto Model Mapping — Maps view columns to C# properties (snake_case, PascalCase, camelCase, custom)
- 🔍 Dynamic Filtering — Filter by any field:
string,int,double,decimal,DateTime,Guid,bool, and all nullable variants - 📊 Dynamic Sorting — Sort by any property with
asc/descsupport, multi-column sorting - 📄 Pagination — Built-in
PagedResult<T>with fully parameterizedpage/pageSize - 🏷️ Attribute-Based Config —
[ViewName],[ViewColumn],[ViewIgnore],[Filterable],[Sortable] - ⚡ Expression Tree Based — Builds
Expression<Func<T, bool>>dynamically — SQL-injection safe - 🎛️ Fluent API + String API — Use type-safe expressions or query string filters
- 🌍 Multi-Target — .NET 7 / .NET 8 / .NET 9 / .NET 10
📦 Installation
dotnet add package ViewForge
Or via Package Manager:
Install-Package ViewForge
🚀 Quick Start
1. Define Your View Model
using ViewForge.Attributes;
[ViewName("vw_product_summary")]
public class ProductSummaryView
{
public Guid Id { get; set; }
public string ProductName { get; set; }
public decimal TotalSales { get; set; }
public int Quantity { get; set; }
public DateTime CreatedDate { get; set; }
public Guid? CategoryId { get; set; }
public string? CategoryName { get; set; }
[ViewIgnore]
public string DisplayName => $"{ProductName} ({CategoryName})";
}
2. Register ViewForge
builder.Services.AddViewForge(options =>
{
options.DefaultNamingConvention = NamingConvention.SnakeCase;
options.DefaultPageSize = 25;
options.MaxPageSize = 100;
options.CaseInsensitiveFilters = true;
});
3. Use in Your Controller
[HttpGet]
public async Task<PagedResult<ProductSummaryView>> GetProducts(
[FromQuery] string? filters,
[FromQuery] string? sorting,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20,
CancellationToken ct = default)
{
var filterGroup = _filterParser.Parse(filters);
var sortDescriptors = SortBuilder.Parse(sorting);
return await _dbContext.ProductSummaries
.ApplyFilters(filterGroup, _filterBuilder)
.ApplySorting(sortDescriptors, _sortBuilder)
.ToPagedResultAsync(page, pageSize, ct);
}
🔍 Dynamic Filtering
Supported Filter Operators
| Operator | Alias | Example | Description |
|---|---|---|---|
| Equals | eq |
Name~eq~Keyboard |
Exact match |
| NotEquals | neq |
Status~neq~Inactive |
Not equal |
| Contains | contains |
Name~contains~key |
String contains (case-insensitive) |
| StartsWith | startswith |
Name~startswith~Pro |
String starts with |
| EndsWith | endswith |
Name~endswith~board |
String ends with |
| GreaterThan | gt |
Price~gt~100 |
Greater than |
| GreaterThanOrEqual | gte |
Price~gte~100 |
Greater than or equal |
| LessThan | lt |
Quantity~lt~10 |
Less than |
| LessThanOrEqual | lte |
Quantity~lte~10 |
Less than or equal |
| In | in |
Category~in~A,B,C |
Value in list |
| IsNull | null |
SupplierId~null |
Value is null |
| IsNotNull | notnull |
SupplierId~notnull |
Value is not null |
| Between | btw |
Date~btw~2024-01-01,2024-12-31 |
Between two values |
Supported Types
All operators work with: string, int, long, double, decimal, float, bool, DateTime, DateTimeOffset, Guid, TimeSpan — and all nullable variants (int?, DateTime?, Guid?, etc.).
Query String Format
GET /api/products?filters=ProductName~contains~keyboard;Price~gt~100;CreatedDate~btw~2024-01-01,2024-12-31&sorting=TotalSales desc,ProductName asc&page=1&pageSize=20
Multiple filters are separated by ; and combined with AND logic.
Programmatic API
// Build filters programmatically
var filters = FilterGroup.And(
FilterDescriptor.Create("ProductName", FilterOperator.Contains, "keyboard"),
FilterDescriptor.Create("Price", FilterOperator.GreaterThan, 100m),
FilterDescriptor.Create("CategoryId", FilterOperator.IsNotNull)
);
// Or use OR logic
var orFilters = FilterGroup.Or(
FilterDescriptor.Create("Status", FilterOperator.Equals, "Active"),
FilterDescriptor.Create("Priority", FilterOperator.Equals, "High")
);
📊 Dynamic Sorting
// From query string
var sorts = SortBuilder.Parse("TotalSales desc, ProductName asc");
// Alternative minus syntax
var sorts2 = SortBuilder.Parse("-TotalSales, ProductName");
// Programmatic
var sorts3 = new List<SortDescriptor>
{
SortDescriptor.Create("TotalSales", SortDirection.Descending),
SortDescriptor.Create("ProductName", SortDirection.Ascending)
};
📄 Pagination
Pagination is fully parameterized — page and pageSize come from external sources (controller parameters, query strings, etc.):
// page & pageSize are passed from controller parameters
public async Task<PagedResult<ProductView>> GetProducts(
int page = 1, int pageSize = 20, CancellationToken ct = default)
{
return await _dbContext.ProductSummaries
.ApplyFilters(filterGroup, _filterBuilder)
.ApplySorting(sortDescriptors, _sortBuilder)
.ToPagedResultAsync(page, pageSize, ct);
}
PagedResult Response
{
"items": [...],
"totalCount": 150,
"page": 1,
"pageSize": 20,
"totalPages": 8,
"hasPreviousPage": false,
"hasNextPage": true
}
🏷️ Attributes
| Attribute | Target | Description |
|---|---|---|
[ViewName("name")] |
Class | Maps class to SQL view name |
[ViewColumn("col")] |
Property | Maps property to specific column name |
[ViewIgnore] |
Property | Excludes property from mapping/filtering |
[Filterable] |
Property | Marks property as filterable (with optional operator restriction) |
[Sortable] |
Property | Marks property as sortable |
[ViewName("vw_orders", Schema = "sales")]
public class OrderView
{
[ViewColumn("order_total")]
public decimal Total { get; set; }
[Filterable(AllowedOperators = new[] { FilterOperator.Equals, FilterOperator.In })]
public string Status { get; set; }
[ViewIgnore]
public string ComputedField => "...";
}
🔧 Configuration
builder.Services.AddViewForge(options =>
{
options.DefaultNamingConvention = NamingConvention.SnakeCase; // snake_case column mapping
options.AllPropertiesFilterableByDefault = true; // all props filterable
options.AllPropertiesSortableByDefault = true; // all props sortable
options.CaseInsensitiveFilters = true; // case-insensitive string filters
options.DefaultPageSize = 20; // default page size
options.MaxPageSize = 100; // max allowed page size
options.FilterSeparator = "~"; // PropertyName~operator~value
options.FilterDelimiter = ";"; // filter1;filter2;filter3
});
Available Options
| Option | Type | Default | Description |
|---|---|---|---|
DefaultNamingConvention |
NamingConvention |
SnakeCase |
Column name mapping convention |
AllPropertiesFilterableByDefault |
bool |
true |
All properties filterable unless [ViewIgnore] |
AllPropertiesSortableByDefault |
bool |
true |
All properties sortable unless [ViewIgnore] |
CaseInsensitiveFilters |
bool |
true |
Case-insensitive string comparisons |
DefaultPageSize |
int |
20 |
Default items per page |
MaxPageSize |
int |
100 |
Maximum allowed page size |
FilterSeparator |
string |
~ |
Separator within a filter expression |
FilterDelimiter |
string |
; |
Delimiter between multiple filters |
📁 Project Structure
ViewForge/
├── src/
│ ├── ViewForge/ # Core NuGet library
│ │ ├── Attributes/ # ViewName, ViewColumn, ViewIgnore, Filterable, Sortable
│ │ ├── Builders/ # FilterBuilder, SortBuilder, FilterParser
│ │ ├── Configuration/ # ViewForgeOptions
│ │ ├── Enums/ # FilterOperator, SortDirection, LogicalOperator
│ │ ├── Extensions/ # DI registration, IQueryable extensions
│ │ ├── Mapping/ # ViewMapper, NamingConventionHelper
│ │ ├── Models/ # PagedResult, FilterDescriptor, SortDescriptor
│ │ └── Providers/ # IViewQueryProvider, EfCoreViewQueryProvider
│ │
│ ├── ViewForge.Sample/ # Sample API project
│ └── ViewForge.Tests/ # Unit tests (50 tests)
│
├── .github/workflows/ # CI/CD pipelines
├── ViewForge.sln
└── README.md
🤝 Contributing
Contributions are welcome! Feel free to open issues and submit pull requests.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'feat: add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
📄 License
This project is licensed under the MIT License.
🔗 Links
Made with ⚒️ by ViewForge
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.EntityFrameworkCore (>= 10.0.0-preview.3.25171.6)
- Microsoft.EntityFrameworkCore.Relational (>= 10.0.0-preview.3.25171.6)
-
net7.0
- Microsoft.EntityFrameworkCore (>= 7.0.20)
- Microsoft.EntityFrameworkCore.Relational (>= 7.0.20)
-
net8.0
- Microsoft.EntityFrameworkCore (>= 8.0.12)
- Microsoft.EntityFrameworkCore.Relational (>= 8.0.12)
-
net9.0
- Microsoft.EntityFrameworkCore (>= 9.0.4)
- Microsoft.EntityFrameworkCore.Relational (>= 9.0.4)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.