├── Samples ├── .bowerrc ├── Views │ ├── _ViewStart.cshtml │ ├── _ViewImports.cshtml │ ├── Widgets │ │ ├── Scheduler.cshtml │ │ ├── Chart.cshtml │ │ ├── RangeSelector.cshtml │ │ ├── PivotGrid.cshtml │ │ ├── Sparkline.cshtml │ │ ├── PieChart.cshtml │ │ └── DataGrid.cshtml │ └── Shared │ │ └── _Layout.cshtml ├── bower.json ├── wwwroot │ └── web.config ├── web.config ├── Program.cs ├── Models │ └── Northwind │ │ ├── Shipper.cs │ │ ├── Category.cs │ │ ├── Order_Details.cs │ │ ├── Customer.cs │ │ ├── Supplier.cs │ │ ├── Product.cs │ │ ├── Order.cs │ │ ├── Employee.cs │ │ └── NorthwindContext.cs ├── Controllers │ ├── WidgetsController.cs │ ├── GoogleCalendarController.cs │ └── NorthwindController.cs ├── Samples.xproj ├── Startup.cs └── project.json ├── coverage ├── .gitignore ├── install.cmd └── run.cmd ├── global.json ├── DevExtreme.AspNet.TagHelpers ├── GeneratedAttribute.cs ├── AssemblyInfo.cs ├── InnerScriptTagHelper.cs ├── Data │ ├── DatasourceTagHelper.cs │ ├── PivotGridDatasourceTagHelper.cs │ ├── ItemsDatasourceTagHelper.cs │ ├── UrlDatasourceTagHelper.cs │ ├── StoreDatasourceTagHelper.cs │ └── LoadActionDatasourceTagHelper.cs ├── CollectionItemTagHelper.cs ├── Utils.cs ├── project.json ├── DevExtreme.AspNet.TagHelpers.xproj ├── DevExtreme.AspNet.Data.Portions.cs ├── TagHelperContextExtensions.cs ├── WidgetTagHelper.cs └── HierarchicalTagHelper.cs ├── NuGet.Config ├── .gitignore ├── DevExtreme.AspNet.TagHelpers.Generator ├── project.json ├── TargetElementInfo.cs ├── EnumerableExtensions.cs ├── TargetElementsRegistry.cs ├── Utils.cs ├── DevExtreme.AspNet.TagHelpers.Generator.xproj ├── CompetitivePropsRegistry.cs ├── XElementExtensions.cs ├── EnumInfo.cs ├── CollectionItemsRegistry.cs ├── TagInfo.cs ├── TagPropertyInfo.cs ├── Program.cs ├── Generator.cs ├── ClassBuilder.cs ├── PropTypeRegistry.cs ├── TagInfoPreProcessor.cs └── meta │ ├── IntellisenseData_15.2_spec.xml │ └── PropOf.xml ├── DevExtreme.AspNet.TagHelpers.Tests ├── project.json ├── DevExtreme.AspNet.TagHelpers.Tests.xproj ├── AssertIgnoreWhitespaces.cs ├── CollectionItemTagHelperTests.cs ├── ValidateParentTests.cs ├── DatasourceTests.cs ├── TestPage.cs ├── HierarchicalTagHelperTests.cs └── RenderingTests.cs ├── appveyor.yml ├── LICENSE ├── replace-meta.ps1 ├── DevExtreme.AspNet.TagHelpers.sln └── README.md /Samples/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "wwwroot/lib" 3 | } 4 | -------------------------------------------------------------------------------- /Samples/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /coverage/.gitignore: -------------------------------------------------------------------------------- 1 | OpenCover.* 2 | ReportGenerator.* 3 | report 4 | coverage.xml 5 | nuget.exe 6 | dnxpath.txt -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "projects": [ "DevExtreme.AspNet.TagHelpers", "DevExtreme.AspNet.TagHelpers.Tests", "DevExtreme.AspNet.TagHelpers.Generator", "Samples" ] 3 | } 4 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers/GeneratedAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DevExtreme.AspNet.TagHelpers { 4 | 5 | class GeneratedAttribute : Attribute { } 6 | 7 | } 8 | -------------------------------------------------------------------------------- /Samples/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using DevExtreme.AspNet.TagHelpers 2 | 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | @addTagHelper *, DevExtreme.AspNet.TagHelpers 5 | -------------------------------------------------------------------------------- /NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | .vs/ 4 | **/wwwroot/lib/ 5 | *.suo 6 | *.user 7 | project.lock.json 8 | launchSettings.json 9 | DevExtreme.AspNet.TagHelpers/**/*.g.cs 10 | coverage/GeneratorTestsData/ 11 | -------------------------------------------------------------------------------- /Samples/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Samples", 3 | "private": true, 4 | 5 | "dependencies": { 6 | "bootstrap": "3.3.5", 7 | "devextreme": "15.2.11", 8 | "devextreme-aspnet-data": "^1.0.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /coverage/install.cmd: -------------------------------------------------------------------------------- 1 | if not exist nuget.exe (PowerShell -Command wget https://dist.nuget.org/win-x86-commandline/latest/nuget.exe -OutFile nuget.exe) 2 | nuget install ReportGenerator -Version 2.3.2.0 3 | nuget install OpenCover -Version 4.6.166 4 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Generator/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildOptions": { 3 | "emitEntryPoint": true 4 | }, 5 | 6 | "dependencies": { 7 | "Microsoft.NETCore.App": { 8 | "type": "platform", 9 | "version": "1.0.0" 10 | } 11 | }, 12 | 13 | "frameworks": { 14 | "netcoreapp1.0": { 15 | "imports": [ "dotnet" ] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Samples/wwwroot/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Generator/TargetElementInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace DevExtreme.AspNet.TagHelpers.Generator { 7 | 8 | public class TargetElementInfo { 9 | public string Tag; 10 | public string ParentTag; 11 | public string BindingAttribute; 12 | public bool IsSelfClosing; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /Samples/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Generator/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace DevExtreme.AspNet.TagHelpers.Generator { 7 | 8 | public static class EnumerableExtensions { 9 | 10 | public static IEnumerable Concat(this IEnumerable items, T item) { 11 | return items.Concat(new[] { item }); 12 | } 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.CompilerServices; 4 | 5 | [assembly: InternalsVisibleTo("DevExtreme.AspNet.TagHelpers.Tests")] 6 | 7 | [assembly: AssemblyCompany("%meta_company%")] 8 | [assembly: AssemblyCopyright("%meta_copyright%")] 9 | [assembly: AssemblyDescription("%meta_description%")] 10 | [assembly: AssemblyProduct("DevExtreme.AspNet.TagHelpers")] 11 | [assembly: AssemblyVersion("0.0")] 12 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Generator/TargetElementsRegistry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace DevExtreme.AspNet.TagHelpers.Generator { 7 | 8 | static class TargetElementsRegistry { 9 | public readonly static ICollection InnerScriptTargets = new List(); 10 | public readonly static ICollection DatasourceTargets = new List(); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Samples/Views/Widgets/Scheduler.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.WidgetName = "Scheduler"; 3 | } 4 | 5 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Samples/Views/Widgets/Chart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.WidgetName = "Chart"; 3 | } 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | <legend vertical-alignment="Bottom" horizontal-alignment="Center" /> 16 | <tooltip enabled="true" format="Currency" /> 17 | </dx-chart> -------------------------------------------------------------------------------- /coverage/run.cmd: -------------------------------------------------------------------------------- 1 | where dnx > dnxpath.txt 2 | set /p dnxpath=<dnxpath.txt 3 | 4 | call dnu build ..\DevExtreme.AspNet.TagHelpers 5 | 6 | opencover.4.6.166\tools\OpenCover.Console.exe ^ 7 | "-target:%dnxpath%" ^ 8 | "-targetargs: --lib %~dp0\..\DevExtreme.AspNet.TagHelpers\bin\Debug\net451 -p ..\DevExtreme.AspNet.TagHelpers.Tests xunit.runner.dnx" ^ 9 | "-filter:+[DevExtreme*]*" ^ 10 | "-excludebyattribute:DevExtreme.AspNet.TagHelpers.GeneratedAttribute" ^ 11 | -hideskipped:All -output:coverage.xml -register:user 12 | 13 | ReportGenerator.2.3.2.0\tools\ReportGenerator.exe -reports:coverage.xml -targetdir:report -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers/InnerScriptTagHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Razor.TagHelpers; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace DevExtreme.AspNet.TagHelpers { 8 | 9 | public partial class InnerScriptTagHelper : TagHelper { 10 | 11 | public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { 12 | var content = await output.GetChildContentAsync(); 13 | 14 | context.GetInnerScripts().Add(content.GetContent()); 15 | } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Samples/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Hosting; 7 | 8 | namespace Samples { 9 | public class Program { 10 | public static void Main(string[] args) { 11 | var host = new WebHostBuilder() 12 | .UseKestrel() 13 | .UseContentRoot(Directory.GetCurrentDirectory()) 14 | .UseIISIntegration() 15 | .UseStartup<Startup>() 16 | .Build(); 17 | 18 | host.Run(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Samples/Views/Widgets/RangeSelector.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.WidgetName = "Range Selector"; 3 | } 4 | 5 | <dx-range-selector> 6 | <datasource url='@Url.Action("ShipsByMonth", "Northwind", new { shipper = "Federal Shipping" })' /> 7 | 8 | <margin top="50" /> 9 | <size height="400" /> 10 | 11 | <chart> 12 | <common-series-settings type="StepArea" argument-field="month" /> 13 | <series value-field="totalAmount" color="yellow" /> 14 | <series value-field="amount" /> 15 | </chart> 16 | 17 | <scale value-type="DateTime" tick-interval="new { months = 1 }" minor-tick-interval="new { months = 1 }" /> 18 | <slider-marker format="MonthAndYear" /> 19 | </dx-range-selector> -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers/Data/DatasourceTagHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace DevExtreme.AspNet.TagHelpers.Data { 7 | 8 | public abstract class DatasourceTagHelper : HierarchicalTagHelper { 9 | 10 | protected sealed override string Key { 11 | get { return "dataSource"; } 12 | } 13 | 14 | protected sealed override string FullKey { 15 | get { return "DevExtreme.AspNet.TagHelpers.Data.Datasource"; } 16 | } 17 | 18 | protected internal override bool ValidateParentFullKey(string parentFullKey) { 19 | return parentFullKey != null; 20 | } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Samples/Views/Widgets/PivotGrid.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.WidgetName = "Pivot Grid"; 3 | } 4 | 5 | <dx-pivot-grid height="500" allow-expand-all="true" allow-sorting="true" allow-sorting-by-summary="true" allow-filtering="true"> 6 | 7 | <datasource controller="Northwind" load-action="SalesCube"> 8 | <field datafield="categoryName" area="Row" width="120" /> 9 | <field datafield="productName" area="Row" width="150" /> 10 | <field datafield="orderDate" area="Column" datatype="Date" /> 11 | <field datafield="sum" datatype="Number" summary-type="Sum" format="Currency" area="Data" /> 12 | </datasource> 13 | 14 | <field-chooser enabled="true" layout="Layout0" /> 15 | <scrolling mode="Virtual" /> 16 | </dx-pivot-grid> -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers/CollectionItemTagHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace DevExtreme.AspNet.TagHelpers { 7 | 8 | public abstract class CollectionItemTagHelper : HierarchicalTagHelper { 9 | 10 | protected internal override void AppendConfig(IDictionary<string, object> parentConfig) { 11 | if(Config.Count < 1) 12 | return; 13 | 14 | if(!parentConfig.ContainsKey(Key)) 15 | parentConfig[Key] = new List<object>(); 16 | 17 | var collectionConfig = (ICollection<object>)parentConfig[Key]; 18 | collectionConfig.Add(Config); 19 | } 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Samples/Views/Widgets/Sparkline.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.WidgetName = "Sparkline"; 3 | } 4 | 5 | @foreach(var shipper in new[] { "Federal Shipping", "Speedy Express", "United Package" }) { 6 | <div class="well" style="width: 400px"> 7 | <div class="row"> 8 | <div class="col-sm-12">@shipper</div> 9 | </div> 10 | <div class="row"> 11 | <div class="col-sm-12"> 12 | 13 | <dx-sparkline argument-field="month" value-field="amount" type="Bar" show-min-max="true" show-first-last="true"> 14 | <datasource url='@Url.Action("ShipsByMonth", "Northwind", new { shipper = shipper })' /> 15 | </dx-sparkline> 16 | 17 | </div> 18 | </div> 19 | </div> 20 | } 21 | -------------------------------------------------------------------------------- /Samples/Models/Northwind/Shipper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | 6 | namespace Samples.Models.Northwind { 7 | 8 | [Table("Shippers")] 9 | public partial class Shipper { 10 | public Shipper() { 11 | Orders = new HashSet<Order>(); 12 | } 13 | 14 | [Key] 15 | public int ShipperID { get; set; } 16 | [Required] 17 | [MaxLength(40)] 18 | public string CompanyName { get; set; } 19 | [MaxLength(24)] 20 | public string Phone { get; set; } 21 | 22 | [InverseProperty("Shipper")] 23 | public virtual ICollection<Order> Orders { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Samples/Models/Northwind/Category.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | 6 | namespace Samples.Models.Northwind { 7 | 8 | [Table("Categories")] 9 | public partial class Category { 10 | public Category() { 11 | Products = new HashSet<Product>(); 12 | } 13 | 14 | [Key] 15 | public int CategoryID { get; set; } 16 | [Required] 17 | [MaxLength(15)] 18 | public string CategoryName { get; set; } 19 | public string Description { get; set; } 20 | 21 | public byte[] Picture { get; set; } 22 | 23 | [InverseProperty("Category")] 24 | public virtual ICollection<Product> Products { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Samples/Models/Northwind/Order_Details.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | 6 | namespace Samples.Models.Northwind { 7 | [Table("Order Details")] 8 | public partial class Order_Details { 9 | public int OrderID { get; set; } 10 | public int ProductID { get; set; } 11 | public float Discount { get; set; } 12 | public short Quantity { get; set; } 13 | public decimal UnitPrice { get; set; } 14 | 15 | [ForeignKey("OrderID")] 16 | [InverseProperty("Order_Details")] 17 | public virtual Order Order { get; set; } 18 | 19 | [ForeignKey("ProductID")] 20 | [InverseProperty("Order_Details")] 21 | public virtual Product Product { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Tests/project.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "dependencies": { 4 | "DevExtreme.AspNet.TagHelpers": { 5 | "target": "project" 6 | }, 7 | "Microsoft.AspNetCore.Mvc.Razor": "1.0.0", 8 | "xunit": "2.2.0-beta2-*", 9 | "dotnet-test-xunit": "2.2.0-preview2-*" 10 | }, 11 | 12 | "testRunner": "xunit", 13 | 14 | "frameworks": { 15 | "netcoreapp1.0": { 16 | "imports": [ "dotnet" ], 17 | "dependencies": { 18 | "Microsoft.NETCore.App": { 19 | "type": "platform", 20 | "version": "1.0.0" 21 | } 22 | } 23 | }, 24 | "net451": { 25 | "frameworkAssemblies": { 26 | "System.Text.RegularExpressions": "" 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers/Utils.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Serialization; 3 | using System; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace DevExtreme.AspNet.TagHelpers { 8 | 9 | static class Utils { 10 | 11 | public static string WrapDomTemplateValue(string text) { 12 | if(text != null && text.StartsWith("#")) 13 | return "$(" + JsonConvert.SerializeObject(text) + ")"; 14 | 15 | return text; 16 | } 17 | 18 | public static string SerializeConfig(object config) { 19 | return JsonConvert.SerializeObject(config, new JsonSerializerSettings { 20 | ContractResolver = new CamelCasePropertyNamesContractResolver(), 21 | #if DEBUG 22 | Formatting = Formatting.Indented 23 | #endif 24 | }); 25 | } 26 | 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers/Data/PivotGridDatasourceTagHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Razor.TagHelpers; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace DevExtreme.AspNet.TagHelpers.Data { 8 | 9 | public partial class PivotGridDatasourceTagHelper : DatasourceTagHelper { 10 | 11 | public override int Order { 12 | get { return base.Order + 1; } 13 | } 14 | 15 | protected internal override void AppendConfig(IDictionary<string, object> parentConfig) { 16 | if(Config.Count < 1) 17 | return; 18 | 19 | if(!parentConfig.ContainsKey(Key)) 20 | parentConfig[Key] = new Dictionary<string, object>(); 21 | 22 | var sharedConfig = parentConfig[Key] as IDictionary<string, object>; 23 | foreach(var entry in Config) 24 | sharedConfig.Add(entry); 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '{build}' 2 | 3 | # cache-bust 2016-05-20 11:03 4 | 5 | cache: 6 | - '%LOCALAPPDATA%\Microsoft\dotnet -> appveyor.yml' 7 | - '%USERPROFILE%\.nuget\packages -> appveyor.yml, **\project.json' 8 | 9 | install: 10 | - appveyor DownloadFile https://raw.githubusercontent.com/dotnet/cli/v1.0.0-preview2/scripts/obtain/dotnet-install.ps1 11 | - powershell -File dotnet-install.ps1 12 | - set PATH=%PATH%;%LOCALAPPDATA%\Microsoft\dotnet 13 | 14 | before_build: 15 | - dotnet restore -v minimal 16 | - powershell -File replace-meta.ps1 %APPVEYOR_BUILD_NUMBER% %APPVEYOR_REPO_TAG_NAME% 17 | 18 | build_script: 19 | - cmd /c "cd DevExtreme.AspNet.TagHelpers.Generator && dotnet run" 20 | - dotnet build DevExtreme.AspNet.TagHelpers 21 | 22 | test_script: 23 | - dotnet test DevExtreme.AspNet.TagHelpers.Tests 24 | 25 | after_test: 26 | - dotnet pack DevExtreme.AspNet.TagHelpers --configuration=Release 27 | 28 | artifacts: 29 | - path: DevExtreme.AspNet.TagHelpers\bin\Release\*.nupkg -------------------------------------------------------------------------------- /Samples/Controllers/WidgetsController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Samples.Controllers { 8 | 9 | public class WidgetsController { 10 | 11 | public IActionResult DataGrid() { 12 | return new ViewResult(); 13 | } 14 | 15 | public IActionResult PivotGrid() { 16 | return new ViewResult(); 17 | } 18 | 19 | public IActionResult Sparkline() { 20 | return new ViewResult(); 21 | } 22 | 23 | public IActionResult RangeSelector() { 24 | return new ViewResult(); 25 | } 26 | 27 | public IActionResult Chart() { 28 | return new ViewResult(); 29 | } 30 | 31 | public IActionResult PieChart() { 32 | return new ViewResult(); 33 | } 34 | 35 | public IActionResult Scheduler() { 36 | return new ViewResult(); 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0", 3 | 4 | "authors": [ "%meta_company%" ], 5 | "copyright": "%meta_copyright%", 6 | "description": "%meta_description%", 7 | 8 | "packOptions": { 9 | "licenseUrl": "%meta_license_url%", 10 | "projectUrl": "%meta_project_url%" 11 | }, 12 | 13 | "buildOptions": { 14 | "nowarn": [ "1591" ], 15 | "xmlDoc": true 16 | }, 17 | 18 | "dependencies": { 19 | "Microsoft.AspNetCore.Mvc.Core": "1.0.0", 20 | "Microsoft.AspNetCore.Mvc.Formatters.Json": "1.0.0", 21 | "Microsoft.AspNetCore.Razor.Runtime": "1.0.0", 22 | "Microsoft.AspNetCore.Mvc.ViewFeatures": "1.0.0", 23 | "DevExtreme.AspNet.Data": "1.0.0" 24 | }, 25 | 26 | "frameworks": { 27 | "net451": { 28 | "frameworkAssemblies": { 29 | "System.Runtime.Serialization": "4.0.0.0" 30 | } 31 | }, 32 | "netstandard1.6": { 33 | "imports": [ "dotnet" ] 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Developer Express Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Generator/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.RegularExpressions; 5 | using System.Threading.Tasks; 6 | 7 | namespace DevExtreme.AspNet.TagHelpers.Generator { 8 | 9 | static class Utils { 10 | 11 | public static string ToCamelCase(string input) { 12 | return Char.ToUpper(input[0]) + input.Substring(1); 13 | } 14 | 15 | public static string ToKebabCase(string input) { 16 | return Regex.Replace(input, @"([a-z\d])([A-Z])", @"$1-$2").ToLower(); 17 | } 18 | 19 | public static string NormalizeDescription(string text) { 20 | if(String.IsNullOrWhiteSpace(text)) 21 | return ""; 22 | 23 | text = text.Trim(); 24 | text = Regex.Replace(text, @"\s+", " "); 25 | text = Regex.Replace(text, @"\</?[^>]+\>", ""); // strip tags 26 | text = Regex.Replace(text, @"_(\w+)_", "'$1'"); // _abc_ -> 'abc' 27 | 28 | if(text.Contains("_")) 29 | Console.WriteLine(text); 30 | 31 | return text; 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers/Data/ItemsDatasourceTagHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.Formatters; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | 10 | namespace DevExtreme.AspNet.TagHelpers.Data { 11 | 12 | public partial class ItemsDatasourceTagHelper : StoreDatasourceTagHelper { 13 | JsonOutputFormatter _jsonFormatter; 14 | 15 | public ItemsDatasourceTagHelper(JsonOutputFormatter jsonFormatter) { 16 | _jsonFormatter = jsonFormatter; 17 | } 18 | 19 | public IEnumerable Items { get; set; } 20 | 21 | protected override string FormatStoreFactory(string args) { 22 | return "new DevExpress.data.ArrayStore(" + args + ")"; 23 | } 24 | 25 | protected override void PopulateStoreConfig(IDictionary<string, object> config) { 26 | using(var writer = new StringWriter()) { 27 | _jsonFormatter.WriteObject(writer, Items); 28 | config["data"] = new JRaw(writer.ToString()); 29 | } 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers/DevExtreme.AspNet.TagHelpers.xproj: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> 3 | <PropertyGroup> 4 | <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion> 5 | <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> 6 | </PropertyGroup> 7 | <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" /> 8 | <PropertyGroup Label="Globals"> 9 | <ProjectGuid>d5419359-3c96-4b83-93fe-a0585001dad7</ProjectGuid> 10 | <RootNamespace>DevExtreme.AspNet.TagHelpers</RootNamespace> 11 | <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath> 12 | <OutputPath Condition="'$(OutputPath)'=='' ">.\bin</OutputPath> 13 | </PropertyGroup> 14 | <PropertyGroup> 15 | <SchemaVersion>2.0</SchemaVersion> 16 | </PropertyGroup> 17 | <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" /> 18 | </Project> -------------------------------------------------------------------------------- /Samples/Samples.xproj: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> 3 | <PropertyGroup> 4 | <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion> 5 | <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> 6 | </PropertyGroup> 7 | <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" /> 8 | <PropertyGroup Label="Globals"> 9 | <ProjectGuid>8d865209-134f-461d-957b-d11d20094b60</ProjectGuid> 10 | <RootNamespace>Samples</RootNamespace> 11 | <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath> 12 | <OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath> 13 | </PropertyGroup> 14 | <PropertyGroup> 15 | <SchemaVersion>2.0</SchemaVersion> 16 | <DevelopmentServerPort>62288</DevelopmentServerPort> 17 | </PropertyGroup> 18 | <Import Project="$(VSToolsPath)\DotNet.Web\Microsoft.DotNet.Web.targets" Condition="'$(VSToolsPath)' != ''" /> 19 | </Project> -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Generator/DevExtreme.AspNet.TagHelpers.Generator.xproj: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> 3 | <PropertyGroup> 4 | <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion> 5 | <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> 6 | </PropertyGroup> 7 | <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" /> 8 | <PropertyGroup Label="Globals"> 9 | <ProjectGuid>773446a6-c8e1-4e89-86f0-1c1406990382</ProjectGuid> 10 | <RootNamespace>DevExtreme.AspNet.TagHelpers.Generator</RootNamespace> 11 | <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath> 12 | <OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath> 13 | </PropertyGroup> 14 | 15 | <PropertyGroup> 16 | <SchemaVersion>2.0</SchemaVersion> 17 | </PropertyGroup> 18 | <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" /> 19 | </Project> -------------------------------------------------------------------------------- /Samples/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Logging; 5 | using Samples.Models.Northwind; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Threading.Tasks; 10 | 11 | namespace Samples { 12 | 13 | public class Startup { 14 | 15 | public void ConfigureServices(IServiceCollection services) { 16 | services.AddMvc(); 17 | 18 | services 19 | .AddLogging() 20 | .AddEntityFrameworkSqlServer() 21 | .AddDbContext<NorthwindContext>(); 22 | } 23 | 24 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { 25 | loggerFactory.AddConsole(); 26 | 27 | if(env.IsDevelopment()) { 28 | app.UseDeveloperExceptionPage(); 29 | app.UseDatabaseErrorPage(); 30 | } 31 | 32 | app.UseStaticFiles(); 33 | app.UseMvc(routes => routes.MapRoute(name: "default", template: "{controller=Widgets}/{action=DataGrid}")); 34 | } 35 | 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /Samples/Views/Widgets/PieChart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.WidgetName = "Pie Chart"; 3 | } 4 | 5 | <dx-pie-chart type="Donut" inner-radius="0.2"> 6 | <datasource controller="Northwind" load-action="SalesByCategory" /> 7 | 8 | <title text="Sales by Category" /> 9 | <size height="600" /> 10 | 11 | <legend orientation="Horizontal" 12 | item-text-position="Right" 13 | horizontal-alignment="Center" 14 | vertical-alignment="Bottom" 15 | column-count="4" /> 16 | 17 | <series argument-field="category" value-field="count"> 18 | <label visible="true" position="PieChartLabelPosition.Inside" background-color="transparent" /> 19 | </series> 20 | 21 | <series argument-field="category" value-field="sales"> 22 | <label visible="true" 23 | format="Currency" 24 | position="PieChartLabelPosition.Columns" 25 | customize-text="customizeSalesLabel"> 26 | <connector visible="true" width="0.5" /> 27 | <font size="16" /> 28 | </label> 29 | </series> 30 | 31 | <script> 32 | function customizeSalesLabel(arg) { 33 | return arg.valueText + " (" + arg.percentText + ")"; 34 | } 35 | </script> 36 | </dx-pie-chart> 37 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Tests/DevExtreme.AspNet.TagHelpers.Tests.xproj: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> 3 | <PropertyGroup> 4 | <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion> 5 | <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> 6 | </PropertyGroup> 7 | <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" /> 8 | <PropertyGroup Label="Globals"> 9 | <ProjectGuid>c88bb805-165b-42ab-9826-8a1cde5451d9</ProjectGuid> 10 | <RootNamespace>DevExtreme.AspNet.TagHelpers.Tests</RootNamespace> 11 | <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath> 12 | <OutputPath Condition="'$(OutputPath)'=='' ">.\bin</OutputPath> 13 | </PropertyGroup> 14 | <PropertyGroup> 15 | <SchemaVersion>2.0</SchemaVersion> 16 | </PropertyGroup> 17 | <ItemGroup> 18 | <Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" /> 19 | </ItemGroup> 20 | <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" /> 21 | </Project> -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Generator/CompetitivePropsRegistry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace DevExtreme.AspNet.TagHelpers.Generator { 8 | 9 | #warning see https://github.com/aspnet/Razor/issues/570 10 | class CompetitivePropsRegistry { 11 | static IDictionary<string, string> _types = new Dictionary<string, string>(); 12 | static IDictionary<string, string> _fullNames = new Dictionary<string, string>(); 13 | 14 | public static void Register(string fullName, string type) { 15 | var nameParts = fullName.Substring("DevExtreme.AspNet.TagHelpers.".Length).Split('.'); 16 | 17 | if(nameParts.Length < 3) 18 | return; 19 | 20 | var tailName = String.Join(".", nameParts.Skip(nameParts.Length - 3)); 21 | 22 | Validate(fullName, tailName, type); 23 | 24 | _types[tailName] = type; 25 | _fullNames[tailName] = fullName; 26 | } 27 | 28 | static void Validate(string fullName, string tailName, string type) { 29 | if(_types.ContainsKey(tailName) && _types[tailName] != type) { 30 | throw new Exception($"Competitive props with different type detected: {_fullNames[tailName]} vs {fullName}"); 31 | } 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Samples/Models/Northwind/Customer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | 6 | namespace Samples.Models.Northwind { 7 | 8 | [Table("Customers")] 9 | public partial class Customer { 10 | public Customer() { 11 | Orders = new HashSet<Order>(); 12 | } 13 | 14 | [MaxLength(5)] 15 | [Key] 16 | public string CustomerID { get; set; } 17 | [MaxLength(60)] 18 | public string Address { get; set; } 19 | [MaxLength(15)] 20 | public string City { get; set; } 21 | [Required] 22 | [MaxLength(40)] 23 | public string CompanyName { get; set; } 24 | [MaxLength(30)] 25 | public string ContactName { get; set; } 26 | [MaxLength(30)] 27 | public string ContactTitle { get; set; } 28 | [MaxLength(15)] 29 | public string Country { get; set; } 30 | [MaxLength(24)] 31 | public string Fax { get; set; } 32 | [MaxLength(24)] 33 | public string Phone { get; set; } 34 | [MaxLength(10)] 35 | public string PostalCode { get; set; } 36 | [MaxLength(15)] 37 | public string Region { get; set; } 38 | 39 | [InverseProperty("Customer")] 40 | public virtual ICollection<Order> Orders { get; set; } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Generator/XElementExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.Xml.Linq; 6 | 7 | namespace DevExtreme.AspNet.TagHelpers.Generator { 8 | 9 | static class XElementExtensions { 10 | 11 | public static string GetName(this XElement el) { 12 | return el.Attribute("Name").Value; 13 | } 14 | 15 | public static void SetName(this XElement el, string name) { 16 | el.Attribute("Name").SetValue(name); 17 | } 18 | 19 | public static string GetRawType(this XElement el) { 20 | return el.Attribute("Type").Value; 21 | } 22 | 23 | public static bool IsChildTagElement(this XElement el) { 24 | return el.Attribute("{http://www.w3.org/2001/XMLSchema-instance}type")?.Value == "IntellisenseObjectInfo"; 25 | } 26 | 27 | public static string GetDescription(this XElement el) { 28 | return el.Attribute("Description")?.Value; 29 | } 30 | 31 | public static XElement GetPropElement(this XElement el, string propName) { 32 | return el 33 | .Element("Properties") 34 | .Elements("IntellisenseObjectPropertyInfo") 35 | .FirstOrDefault(e => propName.Equals(GetName(e), StringComparison.OrdinalIgnoreCase)); 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /Samples/Controllers/GoogleCalendarController.cs: -------------------------------------------------------------------------------- 1 | using DevExtreme.AspNet.Data; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Newtonsoft.Json.Linq; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Net.Http; 8 | using System.Threading.Tasks; 9 | using DevExtreme.AspNet.TagHelpers; 10 | 11 | namespace Samples.Controllers { 12 | 13 | public class GoogleCalendarController { 14 | 15 | [HttpGet] 16 | public async Task<object> Appointments(DataSourceLoadOptions loadOptions) { 17 | var url = "https://www.googleapis.com/calendar/v3/calendars/" 18 | + "f7jnetm22dsjc3npc2lu3buvu4@group.calendar.google.com" 19 | + "/events?key=" + "AIzaSyBnNAISIUKe6xdhq1_rjor2rxoI3UlMY7k"; 20 | 21 | using(var client = new HttpClient()) { 22 | var json = await client.GetStringAsync(url); 23 | var appointments = from i in JObject.Parse(json)["items"] 24 | select new { 25 | text = (string)i["summary"], 26 | startDate = (DateTime?)i["start"]["dateTime"], 27 | endDate = (DateTime?)i["end"]["dateTime"] 28 | }; 29 | 30 | return DataSourceLoader.Load(appointments, loadOptions); 31 | } 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers/Data/UrlDatasourceTagHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.Rendering; 3 | using Microsoft.AspNetCore.Mvc.Routing; 4 | using Microsoft.AspNetCore.Mvc.ViewFeatures; 5 | using Microsoft.AspNetCore.Razor.TagHelpers; 6 | using Newtonsoft.Json; 7 | using Newtonsoft.Json.Linq; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | using System.Threading.Tasks; 12 | 13 | namespace DevExtreme.AspNet.TagHelpers.Data { 14 | 15 | public partial class UrlDatasourceTagHelper : StoreDatasourceTagHelper { 16 | IUrlHelperFactory _urlHelperFactory; 17 | 18 | public UrlDatasourceTagHelper(IUrlHelperFactory urlHelper) { 19 | _urlHelperFactory = urlHelper; 20 | } 21 | 22 | public string Url { get; set; } 23 | 24 | [ViewContext, HtmlAttributeNotBound] 25 | public ViewContext ViewContext { get; set; } 26 | 27 | protected override string FormatStoreFactory(string args) { 28 | return "new DevExpress.data.CustomStore(" + args + ")"; 29 | } 30 | 31 | protected override void PopulateStoreConfig(IDictionary<string, object> config) { 32 | var urlHelper = _urlHelperFactory.GetUrlHelper(ViewContext); 33 | config["load"] = new JRaw("function() { return $.getJSON(" + JsonConvert.SerializeObject(urlHelper.Content(Url)) + "); }"); 34 | } 35 | 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /Samples/Models/Northwind/Supplier.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | 6 | namespace Samples.Models.Northwind { 7 | 8 | [Table("Suppliers")] 9 | public partial class Supplier { 10 | public Supplier() { 11 | Products = new HashSet<Product>(); 12 | } 13 | 14 | [Key] 15 | public int SupplierID { get; set; } 16 | [MaxLength(60)] 17 | public string Address { get; set; } 18 | [MaxLength(15)] 19 | public string City { get; set; } 20 | [Required] 21 | [MaxLength(40)] 22 | public string CompanyName { get; set; } 23 | [MaxLength(30)] 24 | public string ContactName { get; set; } 25 | [MaxLength(30)] 26 | public string ContactTitle { get; set; } 27 | [MaxLength(15)] 28 | public string Country { get; set; } 29 | [MaxLength(24)] 30 | public string Fax { get; set; } 31 | public string HomePage { get; set; } 32 | [MaxLength(24)] 33 | public string Phone { get; set; } 34 | [MaxLength(10)] 35 | public string PostalCode { get; set; } 36 | [MaxLength(15)] 37 | public string Region { get; set; } 38 | 39 | [InverseProperty("Supplier")] 40 | public virtual ICollection<Product> Products { get; set; } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Samples/Models/Northwind/Product.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | 6 | namespace Samples.Models.Northwind { 7 | 8 | [Table("Products")] 9 | public partial class Product { 10 | public Product() { 11 | Order_Details = new HashSet<Order_Details>(); 12 | } 13 | 14 | [Key] 15 | public int ProductID { get; set; } 16 | public int? CategoryID { get; set; } 17 | public bool Discontinued { get; set; } 18 | [Required] 19 | [MaxLength(40)] 20 | public string ProductName { get; set; } 21 | [MaxLength(20)] 22 | public string QuantityPerUnit { get; set; } 23 | public short? ReorderLevel { get; set; } 24 | public int? SupplierID { get; set; } 25 | public decimal? UnitPrice { get; set; } 26 | public short? UnitsInStock { get; set; } 27 | public short? UnitsOnOrder { get; set; } 28 | 29 | [InverseProperty("Product")] 30 | public virtual ICollection<Order_Details> Order_Details { get; set; } 31 | 32 | [ForeignKey("CategoryID")] 33 | [InverseProperty("Products")] 34 | public virtual Category Category { get; set; } 35 | 36 | [ForeignKey("SupplierID")] 37 | [InverseProperty("Products")] 38 | public virtual Supplier Supplier { get; set; } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /replace-meta.ps1: -------------------------------------------------------------------------------- 1 | param ([string]$build_number, [string]$tag) 2 | 3 | $meta_version_numeric = "0.0" 4 | 5 | if ($tag -match '^v?(([.\d]+)[\w-]*)$') { 6 | $meta_version_full = $matches[1] 7 | $meta_version_numeric = $matches[2] 8 | } elseif ($build_number) { 9 | $meta_version_full = "$meta_version_numeric-ci-build$build_number" 10 | } else { 11 | $meta_version_full = $meta_version_numeric 12 | } 13 | 14 | $meta_company = "Developer Express Inc." 15 | $meta_copyright = "Copyright (c) $meta_company" 16 | $meta_description = "ASP.NET Core (MVC 6) Tag Helpers for DevExtreme Widgets" 17 | $meta_license_url = "https://github.com/DevExpress/DevExtreme.AspNet.TagHelpers/blob/master/LICENSE" 18 | $meta_project_url = "https://github.com/DevExpress/DevExtreme.AspNet.TagHelpers" 19 | 20 | $targets = ("DevExtreme.AspNet.TagHelpers\AssemblyInfo.cs", "DevExtreme.AspNet.TagHelpers\project.json") 21 | 22 | $targets | %{ 23 | $path = "$PSScriptRoot\$_" 24 | 25 | (Get-Content $path) | %{ 26 | $_ -replace '(AssemblyVersion.+?")[^"]+', "`${1}$meta_version_numeric" ` 27 | -replace '("version":.+?")[^"]+', "`${1}$meta_version_full" ` 28 | -replace '%meta_company%', $meta_company ` 29 | -replace '%meta_copyright%', $meta_copyright ` 30 | -replace '%meta_description%', $meta_description ` 31 | -replace '%meta_license_url%', $meta_license_url ` 32 | -replace '%meta_project_url%', $meta_project_url 33 | } | Set-Content $path 34 | } -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers/DevExtreme.AspNet.Data.Portions.cs: -------------------------------------------------------------------------------- 1 | using DevExtreme.AspNet.Data; 2 | using DevExtreme.AspNet.Data.Helpers; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.AspNetCore.Mvc.ModelBinding; 5 | using Microsoft.Net.Http.Headers; 6 | using Newtonsoft.Json; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Threading.Tasks; 11 | 12 | namespace DevExtreme.AspNet.TagHelpers { 13 | 14 | [ModelBinder(BinderType = typeof(DataSourceLoadOptionsBinder))] 15 | public class DataSourceLoadOptions : DataSourceLoadOptionsBase { 16 | } 17 | 18 | public class DataSourceLoadOptionsBinder : IModelBinder { 19 | 20 | public Task BindModelAsync(ModelBindingContext bindingContext) { 21 | var loadOptions = new DataSourceLoadOptions(); 22 | DataSourceLoadOptionsParser.Parse(loadOptions, key => bindingContext.ValueProvider.GetValue(key).FirstOrDefault()); 23 | bindingContext.Result = ModelBindingResult.Success(loadOptions); 24 | return Task.FromResult(true); 25 | } 26 | 27 | } 28 | 29 | [Obsolete("This attribute is now obsolete and is no longer used. It was used as a workaround for the issue discussed at https://github.com/aspnet/Mvc/issues/4652")] 30 | public class DataSourceLoadOptionsAttribute : ModelBinderAttribute { 31 | 32 | public DataSourceLoadOptionsAttribute() { 33 | BinderType = typeof(DataSourceLoadOptionsBinder); 34 | } 35 | 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Tests/AssertIgnoreWhitespaces.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.RegularExpressions; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace DevExtreme.AspNet.TagHelpers.Tests { 9 | 10 | static class AssertIgnoreWhitespaces { 11 | 12 | public static void Equal(string expected, string actual) { 13 | Assert.Equal(RemoveWhitespace(expected), RemoveWhitespace(actual)); 14 | } 15 | 16 | public static void Contains(string expectedSubstring, string actualString) { 17 | Assert.Contains(RemoveWhitespace(expectedSubstring), RemoveWhitespace(actualString)); 18 | } 19 | 20 | public static void StartsWith(string expectedStartString, string actualString) { 21 | Assert.StartsWith(RemoveWhitespace(expectedStartString), RemoveWhitespace(actualString)); 22 | } 23 | 24 | public static void DoesNotMatch(string expectedRegexPattern, string actualString) { 25 | Assert.DoesNotMatch(expectedRegexPattern, RemoveWhitespace(actualString)); 26 | } 27 | 28 | static string RemoveWhitespace(string text) { 29 | text = Regex.Replace(text, @"[\r\n]", " "); 30 | text = Regex.Replace(text, @"(?<=\w)\s+(?=\w)", " "); 31 | text = Regex.Replace(text, @"(?<=\W)\s+(?=\w)", ""); 32 | text = Regex.Replace(text, @"(?<=\w)\s+(?=\W)", ""); 33 | text = Regex.Replace(text, @"(?<=\W)\s+(?=\W)", ""); 34 | return text; 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers/Data/StoreDatasourceTagHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Razor.TagHelpers; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text.RegularExpressions; 7 | using System.Threading.Tasks; 8 | 9 | namespace DevExtreme.AspNet.TagHelpers.Data { 10 | 11 | public abstract class StoreDatasourceTagHelper : DatasourceTagHelper { 12 | 13 | [HtmlAttributeName("key")] 14 | public string DatasourceKey { get; set; } 15 | 16 | public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { 17 | var store = new Dictionary<string, object>(); 18 | 19 | var key = ExpandCompoundKey(DatasourceKey); 20 | if(key != null) 21 | store["Key"] = key; 22 | 23 | PopulateStoreConfig(store); 24 | if(store.Count > 0) 25 | Config["store"] = new JRaw(FormatStoreFactory(Utils.SerializeConfig(store))); 26 | 27 | await base.ProcessAsync(context, output); 28 | } 29 | 30 | protected abstract string FormatStoreFactory(string args); 31 | 32 | protected abstract void PopulateStoreConfig(IDictionary<string, object> config); 33 | 34 | static object ExpandCompoundKey(string key) { 35 | if(String.IsNullOrEmpty(key)) 36 | return null; 37 | 38 | var components = Regex.Split(key, "\\s*[;,|]\\s*"); 39 | if(components.Length < 2) 40 | return components[0]; 41 | 42 | return components; 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Generator/EnumInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.RegularExpressions; 5 | using System.Threading.Tasks; 6 | 7 | namespace DevExtreme.AspNet.TagHelpers.Generator { 8 | 9 | class EnumInfo { 10 | public readonly string[] SortedValues; 11 | public readonly string[] Props; 12 | public readonly IDictionary<string, string> Renamings; 13 | 14 | public EnumInfo(string[] values, string[] props, IDictionary<string, string> renamings = null) { 15 | SortedValues = values; 16 | Props = props; 17 | Renamings = renamings; 18 | Array.Sort(SortedValues); 19 | } 20 | 21 | public IEnumerable<RenderingItem> EnumerateForRendering() { 22 | return SortedValues.Select((value, index) => new RenderingItem { 23 | IsFirst = index == 0, 24 | JavaScriptValue = value, 25 | CSharpValue = RenameValueForCSharp(value) 26 | }); 27 | } 28 | 29 | string RenameValueForCSharp(string value) { 30 | if(Renamings != null && Renamings.ContainsKey(value)) 31 | return Renamings[value]; 32 | 33 | return Utils.ToCamelCase(RemoveSpecialChars(value)); 34 | } 35 | 36 | static string RemoveSpecialChars(string value) { 37 | return Regex.Replace(value, @"[. ]([a-zA-Z])", m => m.Groups[1].Value.ToUpper()); 38 | } 39 | 40 | public class RenderingItem { 41 | public bool IsFirst; 42 | public string JavaScriptValue, CSharpValue; 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers/TagHelperContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Razor.TagHelpers; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace DevExtreme.AspNet.TagHelpers { 8 | 9 | static class TagHelperContextExtensions { 10 | const string 11 | PARENT_CONFIG_KEY = "devextreme-parent-config", 12 | PARENT_FULLKEY_KEY = "devextreme-parent-fullkey", 13 | INNER_SCRIPTS_KEY = "devextreme-inner-scripts"; 14 | 15 | public static IDictionary<string, object> GetParentConfig(this TagHelperContext context) { 16 | return context.GetItem<IDictionary<string, object>>(PARENT_CONFIG_KEY); 17 | } 18 | 19 | public static string GetParentFullKey(this TagHelperContext context) { 20 | return context.GetItem<string>(PARENT_FULLKEY_KEY); 21 | } 22 | 23 | public static ICollection<string> GetInnerScripts(this TagHelperContext context) { 24 | return context.GetItem<ICollection<string>>(INNER_SCRIPTS_KEY); 25 | } 26 | 27 | public static void SetParentConfig(this TagHelperContext context, IDictionary<string, object> config) { 28 | context.Items[PARENT_CONFIG_KEY] = config; 29 | } 30 | 31 | public static void SetParentFullKey(this TagHelperContext context, string fullKey) { 32 | context.Items[PARENT_FULLKEY_KEY] = fullKey; 33 | } 34 | 35 | public static void SetInnerScripts(this TagHelperContext context, ICollection<string> scripts) { 36 | context.Items[INNER_SCRIPTS_KEY] = scripts; 37 | } 38 | 39 | static T GetItem<T>(this TagHelperContext context, string key) { 40 | return context.Items.ContainsKey(key) ? (T)context.Items[key] : default(T); 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers/WidgetTagHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Razor.TagHelpers; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using System.Text; 7 | using Newtonsoft.Json; 8 | 9 | namespace DevExtreme.AspNet.TagHelpers { 10 | 11 | public abstract class WidgetTagHelper : HierarchicalTagHelper { 12 | 13 | public string ID { get; set; } 14 | 15 | public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { 16 | var innerScripts = new List<string>(); 17 | context.SetInnerScripts(innerScripts); 18 | 19 | await base.ProcessAsync(context, output); 20 | 21 | var id = GetIDForRendering(ID); 22 | output.TagName = "div"; 23 | output.Attributes.Add("id", id); 24 | output.Content.Clear(); 25 | 26 | output.PostElement.AppendHtml("<script>" + FormatStartupScript(id, innerScripts) + "</script>"); 27 | } 28 | 29 | string FormatStartupScript(string id, IEnumerable<string> innerScripts) { 30 | return "jQuery(function($) {" 31 | + "var options = " + Utils.SerializeConfig(Config) + ";" 32 | + "\n" 33 | + String.Join("\n", innerScripts.Select(s => s.TrimEnd())) 34 | + "\n" 35 | + $"$({JsonConvert.SerializeObject("#" + id)}).{Key}(options);" 36 | + "});"; 37 | } 38 | 39 | static string GetIDForRendering(string id) { 40 | if(String.IsNullOrWhiteSpace(id)) 41 | return "dx-" + Guid.NewGuid().ToString("N"); 42 | 43 | return id; 44 | } 45 | 46 | protected internal override bool ValidateParentFullKey(string parentFullKey) { 47 | return parentFullKey == null; 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /Samples/Models/Northwind/Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | 6 | namespace Samples.Models.Northwind { 7 | 8 | [Table("Orders")] 9 | public partial class Order { 10 | public Order() { 11 | Order_Details = new HashSet<Order_Details>(); 12 | } 13 | 14 | [Key] 15 | public int OrderID { get; set; } 16 | [MaxLength(5), Required] 17 | public string CustomerID { get; set; } 18 | public int? EmployeeID { get; set; } 19 | public decimal? Freight { get; set; } 20 | public DateTime? OrderDate { get; set; } 21 | public DateTime? RequiredDate { get; set; } 22 | [MaxLength(60)] 23 | public string ShipAddress { get; set; } 24 | [MaxLength(15)] 25 | public string ShipCity { get; set; } 26 | [MaxLength(15), Required] 27 | public string ShipCountry { get; set; } 28 | [MaxLength(40)] 29 | public string ShipName { get; set; } 30 | public DateTime? ShippedDate { get; set; } 31 | [MaxLength(10)] 32 | public string ShipPostalCode { get; set; } 33 | [MaxLength(15)] 34 | public string ShipRegion { get; set; } 35 | public int? ShipVia { get; set; } 36 | 37 | [InverseProperty("Order")] 38 | public virtual ICollection<Order_Details> Order_Details { get; set; } 39 | 40 | [ForeignKey("CustomerID")] 41 | [InverseProperty("Orders")] 42 | public virtual Customer Customer { get; set; } 43 | 44 | [ForeignKey("EmployeeID")] 45 | [InverseProperty("Orders")] 46 | public virtual Employee Employee { get; set; } 47 | 48 | [ForeignKey("ShipVia")] 49 | [InverseProperty("Orders")] 50 | public virtual Shipper Shipper { get; set; } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Samples/Models/Northwind/Employee.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | 6 | namespace Samples.Models.Northwind { 7 | 8 | [Table("Employees")] 9 | public partial class Employee { 10 | public Employee() { 11 | Orders = new HashSet<Order>(); 12 | } 13 | 14 | [Key] 15 | public int EmployeeID { get; set; } 16 | [MaxLength(60)] 17 | public string Address { get; set; } 18 | public DateTime? BirthDate { get; set; } 19 | [MaxLength(15)] 20 | public string City { get; set; } 21 | [MaxLength(15)] 22 | public string Country { get; set; } 23 | [MaxLength(4)] 24 | public string Extension { get; set; } 25 | [Required] 26 | [MaxLength(10)] 27 | public string FirstName { get; set; } 28 | public DateTime? HireDate { get; set; } 29 | [MaxLength(24)] 30 | public string HomePhone { get; set; } 31 | [Required] 32 | [MaxLength(20)] 33 | public string LastName { get; set; } 34 | public string Notes { get; set; } 35 | public byte[] Photo { get; set; } 36 | [MaxLength(255)] 37 | public string PhotoPath { get; set; } 38 | [MaxLength(10)] 39 | public string PostalCode { get; set; } 40 | [MaxLength(15)] 41 | public string Region { get; set; } 42 | public int? ReportsTo { get; set; } 43 | [MaxLength(30)] 44 | public string Title { get; set; } 45 | [MaxLength(25)] 46 | public string TitleOfCourtesy { get; set; } 47 | 48 | [InverseProperty("Employee")] 49 | public virtual ICollection<Order> Orders { get; set; } 50 | 51 | [ForeignKey("ReportsTo")] 52 | [InverseProperty("SubordinateEmployees")] 53 | public virtual Employee SupervisorEmployee { get; set; } 54 | 55 | [InverseProperty("SupervisorEmployee")] 56 | public virtual ICollection<Employee> SubordinateEmployees { get; set; } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Samples/project.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "dependencies": { 4 | "Microsoft.NETCore.App": { 5 | "version": "1.0.0", 6 | "type": "platform" 7 | }, 8 | "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", 9 | "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", 10 | "Microsoft.AspNetCore.Diagnostics": "1.0.0", 11 | "Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore": "1.0.0", 12 | "Microsoft.AspNetCore.Mvc": "1.0.0", 13 | "Microsoft.AspNetCore.Mvc.TagHelpers": "1.0.0", 14 | "Microsoft.AspNetCore.StaticFiles": "1.0.0", 15 | "Microsoft.AspNetCore.Razor.Tools": { 16 | "version": "1.0.0-preview2-final", 17 | "type": "build" 18 | }, 19 | "Microsoft.Extensions.Logging": "1.0.0", 20 | "Microsoft.Extensions.Logging.Console": "1.0.0", 21 | "Microsoft.EntityFrameworkCore": "1.0.0", 22 | "Microsoft.EntityFrameworkCore.Tools": { 23 | "version": "1.0.0-preview2-final", 24 | "type": "build" 25 | }, 26 | "Microsoft.EntityFrameworkCore.SqlServer": "1.0.0", 27 | "Microsoft.EntityFrameworkCore.SqlServer.Design": "1.0.0", 28 | "DevExtreme.AspNet.TagHelpers": { 29 | "target": "project" 30 | } 31 | }, 32 | 33 | "tools": { 34 | "Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final", 35 | "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final", 36 | "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final" 37 | }, 38 | 39 | "frameworks": { 40 | "netcoreapp1.0": { } 41 | }, 42 | 43 | "runtimeOptions": { 44 | "configProperties": { 45 | "System.GC.Server": true 46 | } 47 | }, 48 | 49 | "publishOptions": { 50 | "include": [ 51 | "wwwroot", 52 | "Views", 53 | "appsettings.json", 54 | "web.config" 55 | ] 56 | }, 57 | 58 | "buildOptions": { 59 | "emitEntryPoint": true, 60 | "preserveCompilationContext": true, 61 | "compile": { 62 | "exclude": [ 63 | "wwwroot", 64 | "node_modules" 65 | ] 66 | } 67 | }, 68 | 69 | "scripts": { 70 | "precompile": "../build.cmd" 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /Samples/Views/Widgets/DataGrid.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.WidgetName = "Data Grid"; 3 | } 4 | 5 | <dx-data-grid id="myGrid" height="500px"> 6 | <datasource controller="Northwind" load-action="Orders" key="orderID" 7 | update-action="UpdateOrder" insert-action="InsertOrder" delete-action="DeleteOrder" /> 8 | 9 | <column datafield="customerID" caption="Customer"> 10 | <lookup value-expr="value" display-expr="text"> 11 | <datasource controller="Northwind" load-action="CustomersLookup" key="value" /> 12 | </lookup> 13 | </column> 14 | <column datafield="orderDate" datatype="Date"> 15 | <header-filter group-interval='"quarter"' /> 16 | </column> 17 | <column datafield="freight"> 18 | <header-filter group-interval="100" /> 19 | </column> 20 | <column datafield="shipCountry" /> 21 | <column datafield="shipRegion" /> 22 | <column datafield="shipVia"> 23 | <lookup value-expr="value" display-expr="text"> 24 | <datasource controller="Northwind" load-action="ShippersLookup" key="value" /> 25 | </lookup> 26 | </column> 27 | 28 | <group-panel visible="true" /> 29 | <filter-row visible="true" /> 30 | <header-filter visible="true " /> 31 | <scrolling mode="Virtual" /> 32 | 33 | <editing allow-updating="true" allow-adding="true" allow-deleting="true" /> 34 | 35 | <remote-operations filtering="true" 36 | grouping="true" 37 | paging="true" 38 | sorting="true" 39 | summary="true" /> 40 | 41 | <grouping auto-expand-all="false" /> 42 | 43 | <summary> 44 | <total-item column="freight" summary-type="Sum" /> 45 | <group-item column="freight" summary-type="Sum" /> 46 | <group-item summary-type="Count" /> 47 | </summary> 48 | 49 | <master-detail enabled="true" template="detailTemplate" /> 50 | 51 | <script> 52 | 53 | // Nested tag helpers are not currently supported 54 | // This shows how one can fallback to the jQuery API to implement templates 55 | function detailTemplate(container, options) { 56 | $("<div>") 57 | .dxDataGrid({ 58 | dataSource: DevExpress.data.AspNet.createStore({ 59 | loadUrl: "@Url.Action("OrderDetails", "Northwind")" + "?" + $.param({ orderID: options.data.orderID }) 60 | }) 61 | }) 62 | .appendTo(container); 63 | } 64 | 65 | </script> 66 | 67 | </dx-data-grid> 68 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers/HierarchicalTagHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Razor.TagHelpers; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace DevExtreme.AspNet.TagHelpers { 9 | 10 | public abstract class HierarchicalTagHelper : TagHelper { 11 | protected readonly IDictionary<string, object> Config = new Dictionary<string, object>(); 12 | 13 | protected abstract string Key { get; } 14 | 15 | #warning see https://github.com/aspnet/Razor/issues/576 16 | protected abstract string FullKey { get; } 17 | 18 | public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { 19 | var parentConfig = context.GetParentConfig(); 20 | var parentFullKey = context.GetParentFullKey(); 21 | 22 | if(!ValidateParentFullKey(parentFullKey)) 23 | return; 24 | 25 | try { 26 | context.SetParentConfig(Config); 27 | context.SetParentFullKey(FullKey); 28 | await output.GetChildContentAsync(); 29 | } finally { 30 | context.SetParentConfig(parentConfig); 31 | context.SetParentFullKey(parentFullKey); 32 | } 33 | 34 | if(parentConfig != null) 35 | AppendConfig(parentConfig); 36 | } 37 | 38 | protected internal virtual bool ValidateParentFullKey(string parentFullKey) { 39 | if(parentFullKey == null) 40 | return false; 41 | 42 | var keyParts = FullKey.Split('.'); 43 | var parentKeyParts = parentFullKey.Split('.'); 44 | return keyParts.Take(keyParts.Length - 1).SequenceEqual(parentKeyParts); 45 | } 46 | 47 | protected internal virtual void AppendConfig(IDictionary<string, object> parentConfig) { 48 | if(Config.Count > 0) 49 | parentConfig[Key] = Config; 50 | } 51 | 52 | protected internal T GetConfigValue<T>(string key) { 53 | if(!Config.ContainsKey(key)) 54 | return default(T); 55 | 56 | var jRaw = Config[key] as JRaw; 57 | 58 | if(jRaw != null) 59 | return (T)jRaw.Value; 60 | 61 | return (T)Config[key]; 62 | } 63 | 64 | protected internal void SetConfigValue(string key, object value, bool isRaw = false) { 65 | if(isRaw) 66 | value = new JRaw(value); 67 | 68 | Config[key] = value; 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers/Data/LoadActionDatasourceTagHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.Rendering; 3 | using Microsoft.AspNetCore.Mvc.Routing; 4 | using Microsoft.AspNetCore.Mvc.ViewFeatures; 5 | using Microsoft.AspNetCore.Razor.TagHelpers; 6 | using Newtonsoft.Json.Linq; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Threading.Tasks; 11 | 12 | namespace DevExtreme.AspNet.TagHelpers.Data { 13 | 14 | public partial class LoadActionDatasourceTagHelper : StoreDatasourceTagHelper { 15 | IUrlHelperFactory _urlHelperFactory; 16 | 17 | public LoadActionDatasourceTagHelper(IUrlHelperFactory urlHelperFactory) { 18 | _urlHelperFactory = urlHelperFactory; 19 | } 20 | 21 | public string Controller { get; set; } 22 | public string LoadAction { get; set; } 23 | public string UpdateAction { get; set; } 24 | public string UpdateMethod { get; set; } 25 | public string InsertAction { get; set; } 26 | public string InsertMethod { get; set; } 27 | public string DeleteAction { get; set; } 28 | public string DeleteMethod { get; set; } 29 | public string OnBeforeSend { get; set; } 30 | 31 | [ViewContext, HtmlAttributeNotBound] 32 | public ViewContext ViewContext { get; set; } 33 | 34 | protected override string FormatStoreFactory(string args) { 35 | return "DevExpress.data.AspNet.createStore(" + args + ")"; 36 | } 37 | 38 | protected override void PopulateStoreConfig(IDictionary<string, object> config) { 39 | AddAction(config, "load", LoadAction, null); 40 | AddAction(config, "insert", InsertAction, InsertMethod); 41 | AddAction(config, "update", UpdateAction, UpdateMethod); 42 | AddAction(config, "delete", DeleteAction, DeleteMethod); 43 | 44 | if(!String.IsNullOrEmpty(OnBeforeSend)) 45 | config["onBeforeSend"] = new JRaw(OnBeforeSend); 46 | } 47 | 48 | 49 | void AddAction(IDictionary<string, object> config, string name, string action, string method) { 50 | if(!String.IsNullOrEmpty(action)) 51 | config[name + "Url"] = GetActionUrl(action); 52 | 53 | if(!String.IsNullOrEmpty(method)) 54 | config[name + "Method"] = method; 55 | } 56 | 57 | string GetActionUrl(string action) { 58 | var result = _urlHelperFactory.GetUrlHelper(ViewContext).Action(action, Controller); 59 | if(String.IsNullOrEmpty(result)) 60 | throw new Exception($"Unable to resolve Datasource action: '{Controller}.{action}'"); 61 | 62 | return result; 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Tests/CollectionItemTagHelperTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace DevExtreme.AspNet.TagHelpers.Tests { 9 | 10 | public class CollectionItemTagHelperTests { 11 | 12 | class TestCollectionItemTagHelper : CollectionItemTagHelper { 13 | string _key; 14 | 15 | public TestCollectionItemTagHelper(string key) { 16 | _key = key; 17 | } 18 | 19 | protected override string FullKey { 20 | get { throw new NotImplementedException(); } 21 | } 22 | 23 | protected override string Key { 24 | get { return _key; } 25 | } 26 | 27 | public TestCollectionItemTagHelper EnsureConfigNotEmpty() { 28 | SetConfigValue("any", "any"); 29 | return this; 30 | } 31 | 32 | } 33 | 34 | [Fact] 35 | public void CreatesListOnAppend() { 36 | var tagHelper = new TestCollectionItemTagHelper("key1"); 37 | var parentConfig = new Dictionary<string, object>(); 38 | 39 | tagHelper.EnsureConfigNotEmpty(); 40 | tagHelper.AppendConfig(parentConfig); 41 | 42 | Assert.IsAssignableFrom(typeof(IList), parentConfig["key1"]); 43 | } 44 | 45 | [Fact] 46 | public void DoesNotOverrideCreatedList() { 47 | var tagHelper1 = new TestCollectionItemTagHelper("key1"); 48 | var tagHelper2 = new TestCollectionItemTagHelper("key1"); 49 | var parentConfig = new Dictionary<string, object>(); 50 | 51 | tagHelper1.EnsureConfigNotEmpty(); 52 | tagHelper1.AppendConfig(parentConfig); 53 | 54 | var collection = parentConfig["key1"] as IList<object>; 55 | var item1 = collection[0]; 56 | 57 | tagHelper2.EnsureConfigNotEmpty(); 58 | tagHelper2.AppendConfig(parentConfig); 59 | 60 | Assert.Equal(2, collection.Count); 61 | Assert.Contains(item1, collection); 62 | } 63 | 64 | [Fact] 65 | public void IgnoresEmptyItems() { 66 | var parentConfig = new Dictionary<string, object>(); 67 | new TestCollectionItemTagHelper("key1").AppendConfig(parentConfig); 68 | 69 | Assert.False(parentConfig.ContainsKey("key1")); 70 | 71 | new TestCollectionItemTagHelper("key1").EnsureConfigNotEmpty().AppendConfig(parentConfig); 72 | new TestCollectionItemTagHelper("key1").AppendConfig(parentConfig); 73 | 74 | Assert.Equal(1, (parentConfig["key1"] as IList).Count); 75 | } 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /Samples/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | var sidebar = new[] { 3 | new { 4 | Name = "Grids", 5 | Widgets = new[] { "DataGrid", "PivotGrid" } 6 | }, 7 | new { 8 | Name = "Viz", 9 | Widgets = new[] { "Chart", "PieChart", "RangeSelector", "Sparkline" } 10 | }, 11 | new { 12 | Name = "Misc", 13 | Widgets= new[] { "Scheduler" } 14 | } 15 | }; 16 | } 17 | 18 | <!DOCTYPE html> 19 | <html> 20 | <head> 21 | <meta name="viewport" content="width=device-width" /> 22 | <title>Samples 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 49 | 50 |
51 |
52 | 53 |
54 | @foreach(var group in sidebar) { 55 |

@group.Name

56 | 61 | } 62 | 63 | 66 |
67 | 68 |
69 |

@ViewBag.WidgetName

70 | @RenderBody() 71 |
72 | 73 |
74 |
75 | 76 | 77 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Generator/CollectionItemsRegistry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace DevExtreme.AspNet.TagHelpers.Generator { 7 | 8 | static class CollectionItemsRegistry { 9 | static ICollection _knownCollectionItemFullKeys = new HashSet { 10 | "DevExtreme.AspNet.TagHelpers.dxDataGrid.Columns", 11 | "DevExtreme.AspNet.TagHelpers.dxDataGrid.SortByGroupSummaryInfo", 12 | "DevExtreme.AspNet.TagHelpers.dxDataGrid.Summary.GroupItems", 13 | "DevExtreme.AspNet.TagHelpers.dxDataGrid.Summary.TotalItems", 14 | "DevExtreme.AspNet.TagHelpers.dxChart.ArgumentAxis.ConstantLines", 15 | "DevExtreme.AspNet.TagHelpers.dxChart.ArgumentAxis.Strips", 16 | "DevExtreme.AspNet.TagHelpers.dxChart.Panes", 17 | "DevExtreme.AspNet.TagHelpers.dxChart.Series", 18 | "DevExtreme.AspNet.TagHelpers.dxChart.ValueAxis", 19 | "DevExtreme.AspNet.TagHelpers.dxChart.ValueAxis.ConstantLines", 20 | "DevExtreme.AspNet.TagHelpers.dxChart.ValueAxis.Strips", 21 | "DevExtreme.AspNet.TagHelpers.dxPieChart.Series", 22 | "DevExtreme.AspNet.TagHelpers.dxScheduler.Resources", 23 | "DevExtreme.AspNet.TagHelpers.dxRangeSelector.Chart.Series", 24 | "DevExtreme.AspNet.TagHelpers.Data.Datasource.Fields" 25 | }; 26 | 27 | static IDictionary _renamings = new Dictionary { 28 | { "columns", "column"}, 29 | { "groupItems", "groupItem" }, 30 | { "totalItems", "totalItem" }, 31 | { "constantLines", "constantLine" }, 32 | { "strips", "strip" }, 33 | { "panes", "pane" }, 34 | { "resources", "resource"}, 35 | { "fields", "field"} 36 | }; 37 | 38 | public static string GetModifiedElementName(string name) { 39 | return _renamings.ContainsKey(name) ? _renamings[name] : name; 40 | } 41 | 42 | public static bool IsKnownCollectionItem(string fullKey) { 43 | return _knownCollectionItemFullKeys.Contains(fullKey); 44 | } 45 | 46 | public static bool SuspectCollectionItem(string rawType) { 47 | if(String.IsNullOrEmpty(rawType)) 48 | return false; 49 | 50 | var parts = rawType.ToLower().Split('|'); 51 | 52 | if(!parts.Contains("array")) 53 | return false; 54 | 55 | if(parts.Length == 1) 56 | return true; 57 | 58 | if(parts.Length < 3 && parts.Contains("object")) 59 | return true; 60 | 61 | return false; 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.23107.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{34EF35AB-959B-4B43-A7CA-B9E2EEF9F49A}" 7 | ProjectSection(SolutionItems) = preProject 8 | global.json = global.json 9 | NuGet.Config = NuGet.Config 10 | EndProjectSection 11 | EndProject 12 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "DevExtreme.AspNet.TagHelpers.Generator", "DevExtreme.AspNet.TagHelpers.Generator\DevExtreme.AspNet.TagHelpers.Generator.xproj", "{773446A6-C8E1-4E89-86F0-1C1406990382}" 13 | EndProject 14 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "DevExtreme.AspNet.TagHelpers", "DevExtreme.AspNet.TagHelpers\DevExtreme.AspNet.TagHelpers.xproj", "{D5419359-3C96-4B83-93FE-A0585001DAD7}" 15 | EndProject 16 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "DevExtreme.AspNet.TagHelpers.Tests", "DevExtreme.AspNet.TagHelpers.Tests\DevExtreme.AspNet.TagHelpers.Tests.xproj", "{C88BB805-165B-42AB-9826-8A1CDE5451D9}" 17 | EndProject 18 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Samples", "Samples\Samples.xproj", "{8D865209-134F-461D-957B-D11D20094B60}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {773446A6-C8E1-4E89-86F0-1C1406990382}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {773446A6-C8E1-4E89-86F0-1C1406990382}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {773446A6-C8E1-4E89-86F0-1C1406990382}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {773446A6-C8E1-4E89-86F0-1C1406990382}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {D5419359-3C96-4B83-93FE-A0585001DAD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {D5419359-3C96-4B83-93FE-A0585001DAD7}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {D5419359-3C96-4B83-93FE-A0585001DAD7}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {D5419359-3C96-4B83-93FE-A0585001DAD7}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {C88BB805-165B-42AB-9826-8A1CDE5451D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {C88BB805-165B-42AB-9826-8A1CDE5451D9}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {C88BB805-165B-42AB-9826-8A1CDE5451D9}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {C88BB805-165B-42AB-9826-8A1CDE5451D9}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {8D865209-134F-461D-957B-D11D20094B60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {8D865209-134F-461D-957B-D11D20094B60}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {8D865209-134F-461D-957B-D11D20094B60}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {8D865209-134F-461D-957B-D11D20094B60}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | EndGlobal 47 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Generator/TagInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.Xml.Linq; 6 | 7 | namespace DevExtreme.AspNet.TagHelpers.Generator { 8 | 9 | class TagInfo { 10 | readonly TagInfoPreProcessor _preProcessor; 11 | 12 | public readonly XElement Element; 13 | public string Key; 14 | public readonly IEnumerable Namespace; 15 | public readonly string ParentTagName; 16 | public string BaseClassName = "HierarchicalTagHelper"; 17 | public readonly List ChildTagElements = new List(); 18 | public readonly List PropElements = new List(); 19 | public readonly List ExtraChildRestrictions = new List(); 20 | 21 | public TagInfo(XElement element, TagInfoPreProcessor preProcessor, IEnumerable ns, string parentTagName) { 22 | _preProcessor = preProcessor; 23 | Element = element; 24 | Namespace = ns; 25 | ParentTagName = parentTagName; 26 | Key = Element.GetName(); 27 | 28 | foreach(var prop in element.Element("Properties").Elements("IntellisenseObjectPropertyInfo")) { 29 | if(prop.IsChildTagElement()) 30 | ChildTagElements.Add(prop); 31 | else 32 | PropElements.Add(prop); 33 | } 34 | 35 | preProcessor.Process(this); 36 | } 37 | 38 | public string GetTagName() { 39 | return Utils.ToKebabCase(Element.GetName()); 40 | } 41 | 42 | public string GetNamespaceEntry() { 43 | var elementName = Element.GetName(); 44 | return elementName.StartsWith("dx") ? elementName : Utils.ToCamelCase(elementName); 45 | } 46 | 47 | public string GetFullKey() { 48 | return String.Join(".", Namespace) + "." + GetNamespaceEntry(); 49 | } 50 | 51 | public string GetClassName() { 52 | return GetNamespaceEntry() + "TagHelper"; 53 | } 54 | 55 | public string GetSummaryText() { 56 | return Utils.NormalizeDescription(Element.GetDescription()); 57 | } 58 | 59 | public TagInfo[] GenerateChildTags() { 60 | return ChildTagElements 61 | .Select(el => new TagInfo(el, _preProcessor, Namespace.Concat(GetNamespaceEntry()), GetTagName())) 62 | .OrderBy(t => t.GetTagName()) 63 | .ToArray(); 64 | } 65 | 66 | public string[] GetChildRestrictions(IEnumerable childTags) { 67 | return childTags.Concat(ExtraChildRestrictions).OrderBy(t => t).ToArray(); 68 | } 69 | 70 | public TagPropertyInfo[] GenerateProperties() { 71 | return PropElements 72 | .Select(el => new TagPropertyInfo(el)) 73 | .OrderBy(p => p.GetName()) 74 | .ToArray(); 75 | } 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Tests/ValidateParentTests.cs: -------------------------------------------------------------------------------- 1 | using DevExtreme.AspNet.TagHelpers.Data; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace DevExtreme.AspNet.TagHelpers.Tests { 9 | 10 | public class ValidateParentTests { 11 | 12 | class SampleInnerTag : HierarchicalTagHelper { 13 | string _fullKey; 14 | 15 | public SampleInnerTag(string fullKey) { 16 | _fullKey = fullKey; 17 | } 18 | 19 | protected override string Key { 20 | get { throw new NotImplementedException(); } 21 | } 22 | 23 | protected override string FullKey { 24 | get { return _fullKey; } 25 | } 26 | } 27 | 28 | class SampleWidgetTag : WidgetTagHelper { 29 | string _fullKey; 30 | 31 | public SampleWidgetTag(string fullKey) { 32 | _fullKey = fullKey; 33 | } 34 | 35 | protected override string Key { 36 | get { throw new NotImplementedException(); } 37 | } 38 | 39 | protected override string FullKey { 40 | get { return _fullKey; } 41 | } 42 | } 43 | 44 | class SampleDatasourceTag : DatasourceTagHelper { } 45 | 46 | [Fact] 47 | public void SameBranch_ImmediateChild() { 48 | var tag = new SampleInnerTag("MyTags.Parent.Child"); 49 | Assert.True(tag.ValidateParentFullKey("MyTags.Parent")); 50 | } 51 | 52 | [Fact] 53 | public void SameBranch_SkippedChildren() { 54 | var tag = new SampleInnerTag("MyTags.Parent.Child.Grandchild"); 55 | Assert.False(tag.ValidateParentFullKey("MyTags.Parent")); 56 | } 57 | 58 | [Fact] 59 | public void DifferentBranches() { 60 | var tag = new SampleInnerTag("MyTags.Parent1.Child.Grandchild"); 61 | Assert.False(tag.ValidateParentFullKey("MyTags.Parent2.Child")); 62 | } 63 | 64 | [Fact] 65 | public void OrphanChild() { 66 | var tag = new SampleInnerTag("MyTags.Parent.Child"); 67 | Assert.False(tag.ValidateParentFullKey(null)); 68 | } 69 | 70 | [Fact] 71 | public void WidgetValidationSucceedsIfNoParent() { 72 | var tag = new SampleWidgetTag("MyTags.Grid"); 73 | Assert.True(tag.ValidateParentFullKey(null)); 74 | } 75 | 76 | [Fact] 77 | public void WidgetValidationFailsIfParentExists() { 78 | var tag = new SampleWidgetTag("MyTags.Grid"); 79 | Assert.False(tag.ValidateParentFullKey("MyTags.Chart.Label")); 80 | } 81 | 82 | [Fact] 83 | public void DatasourceIsValidEverywhere() { 84 | var tag = new SampleDatasourceTag(); 85 | Assert.True(tag.ValidateParentFullKey("MyTags.Everywhere")); 86 | Assert.False(tag.ValidateParentFullKey(null)); 87 | } 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Generator/TagPropertyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.RegularExpressions; 5 | using System.Threading.Tasks; 6 | using System.Xml.Linq; 7 | 8 | namespace DevExtreme.AspNet.TagHelpers.Generator { 9 | 10 | class TagPropertyInfo { 11 | XElement _element; 12 | 13 | public TagPropertyInfo(XElement element) { 14 | _element = element; 15 | } 16 | 17 | public string GetName() { 18 | return Utils.ToCamelCase(_element.GetName()); 19 | } 20 | 21 | public string GetSummaryText() { 22 | return Utils.NormalizeDescription(_element.GetDescription()); 23 | } 24 | 25 | public string GetClrType() { 26 | var clrOverride = _element.Attribute(PropTypeRegistry.CLR_TYPE_OVERRIDE_ATTR)?.Value; 27 | if(!String.IsNullOrEmpty(clrOverride)) 28 | return clrOverride; 29 | 30 | var rawTypes = _element.GetRawType().Split('|'); 31 | var name = GetName(); 32 | 33 | bool 34 | canBeString = rawTypes.Any(t => t == "string"), 35 | canBeNumber = rawTypes.Any(t => t == "number" || t == "numeric"), 36 | canBeBool = rawTypes.Any(t => t == "bool" || t == "boolean"), 37 | canDeDate = rawTypes.Any(t => t == "date"), 38 | canBeFunc = rawTypes.Any(t => t.StartsWith("function")), 39 | canBeJQuery = rawTypes.Any(t => t == "jquery"), 40 | canBeAny = rawTypes.Any(t => t == "any"); 41 | 42 | if(rawTypes.Length == 1) { 43 | if(canBeString) 44 | return PropTypeRegistry.CLR_STRING; 45 | 46 | if(canBeBool) 47 | return PropTypeRegistry.CLR_BOOL; 48 | 49 | if(canBeNumber) 50 | return PropTypeRegistry.CLR_DOUBLE; 51 | 52 | if(canBeFunc) 53 | return PropTypeRegistry.SPECIAL_RAW_STRING; 54 | 55 | if(canBeAny) 56 | return PropTypeRegistry.CLR_OBJECT; 57 | 58 | if(canDeDate) 59 | return PropTypeRegistry.CLR_DATE; 60 | } 61 | 62 | if(rawTypes.Length == 2) { 63 | if(canBeString && canBeNumber) 64 | return PropTypeRegistry.CLR_STRING; 65 | 66 | if(canBeFunc && canBeString && Regex.IsMatch(name, "^On[A-Z]")) 67 | return PropTypeRegistry.SPECIAL_RAW_STRING; 68 | } 69 | 70 | if(canBeFunc && canBeJQuery) 71 | return PropTypeRegistry.SPECIAL_DOM_TEMPLATE; 72 | 73 | throw new Exception("Unable to resolve property type"); 74 | } 75 | 76 | public string GetCustomAttrName() { 77 | var name = GetName(); 78 | if(Regex.IsMatch(name, "^Data[A-Z]")) 79 | return "data" + Utils.ToKebabCase(name.Substring(4)); 80 | return null; 81 | } 82 | 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ASP.NET Core (MVC 6) Tag Helpers for DevExtreme Widgets 2 | 3 | [![Build status](https://ci.appveyor.com/api/projects/status/gyf4ghfeg5qjuxnl/branch/master?svg=true)](https://ci.appveyor.com/project/dxrobot/devextreme-aspnet-taghelpers/branch/master) 4 | [![NuGet](https://img.shields.io/nuget/v/DevExtreme.AspNet.TagHelpers.svg)](https://www.nuget.org/packages/DevExtreme.AspNet.TagHelpers) 5 | [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/DevExpress/DevExtreme.AspNet.TagHelpers/master/LICENSE) 6 | 7 | ## Synopsis 8 | 9 | With Tag Helpers, ASP.NET Core Razor markup becomes more HTML-friendly, 10 | and Visual Studio code assistance is extended with IntelliSense for tags and their attributes. 11 | [Read more...](https://docs.asp.net/en/latest/mvc/views/tag-helpers/intro.html) 12 | 13 | DevExtreme ASP.NET Core Tag Helpers simplify the use of 14 | [UI and Visualizatioin widgets](http://js.devexpress.com/Demos/WidgetsGallery/) 15 | in Razor views 16 | and connecting them to data exposed via MVC controllers. 17 | 18 | ```xml 19 | 20 | 21 | 22 | 23 | 24 | ``` 25 | 26 | ## Getting Started 27 | 28 | * [ASP.NET Core Prerequisites](https://docs.asp.net/en/latest/getting-started.html) 29 | * [Install Packages](https://github.com/DevExpress/DevExtreme.AspNet.TagHelpers/wiki/Install-Packages) 30 | * [Use DevExtreme Widgets](https://github.com/DevExpress/DevExtreme.AspNet.TagHelpers/wiki/Use-DevExtreme-Widgets) 31 | * [Specify Data Sources](https://github.com/DevExpress/DevExtreme.AspNet.TagHelpers/wiki/Specify-Data-Sources) 32 | * [Connect to Data Using Entity Framework Core](https://github.com/DevExpress/DevExtreme.AspNet.TagHelpers/wiki/Connect-to-Data-Using-Entity-Framework-Core) 33 | * [Use JavaScript with Tag Helpers (Event Handlers, Templates, etc)](https://github.com/DevExpress/DevExtreme.AspNet.TagHelpers/wiki/Use-JavaScript-with-Tag-Helpers-(Event-Handlers,-Templates,-etc)) 34 | * [Samples](https://github.com/DevExpress/DevExtreme.AspNet.TagHelpers/tree/master/Samples) 35 | 36 | ## API Reference 37 | 38 | DevExtreme ASP.NET Core Tag Helpers mirror 39 | [DevExtreme JavaScript API](http://js.devexpress.com/Documentation/ApiReference/). 40 | 41 | ## License 42 | 43 | Familiarize yourself with the 44 | [DevExtreme Commerical License](https://www.devexpress.com/Support/EULAs/DevExtreme.xml). 45 | [Free trial is available!](http://js.devexpress.com/Buy/) 46 | 47 | **DevExtreme ASP.NET Core Tag Helpers are released as a MIT-licensed (free and open-source) add-on to DevExtreme.** 48 | 49 | ## Support & Feedback 50 | 51 | * For general ASP.NET Core, MVC 6 and Entity Framework Core topics, follow [these guidelines](https://github.com/aspnet/Home/blob/dev/CONTRIBUTING.md) 52 | * For questions regarding DevExtreme libraries and JavaScript API, use [DevExpress Support Center](https://www.devexpress.com/Support/Center) 53 | * For DevExtreme ASP.NET Core Tag Helpers bugs, questions and suggestions, use the [GitHub issue tracker](https://github.com/DevExpress/DevExtreme.AspNet.TagHelpers/issues) 54 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Tests/DatasourceTests.cs: -------------------------------------------------------------------------------- 1 | using DevExtreme.AspNet.TagHelpers.Data; 2 | using Microsoft.AspNetCore.Razor.TagHelpers; 3 | using Newtonsoft.Json; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using Xunit; 9 | 10 | namespace DevExtreme.AspNet.TagHelpers.Tests { 11 | 12 | public class DatasourceTests { 13 | 14 | [Fact] 15 | public void DatasourceDoesNotAddEmptyConfig() { 16 | var page = new PivotDatasourcePage(); 17 | page.DataSources.Add(new TestStoreDatasourceTagHelper()); 18 | page.DataSources.Add(new PivotGridDatasourceTagHelper()); 19 | 20 | page.ExecuteSynchronously(); 21 | 22 | var dataSourceConfig = page.TopLevelTag.GetConfigValue>("dataSource"); 23 | Assert.Null(dataSourceConfig); 24 | } 25 | 26 | [Fact] 27 | public void PivotGridDatasourceExtendsStoreDatasource() { 28 | var page = new PivotDatasourcePage(); 29 | page.DataSources.Add(new PivotGridDatasourceTagHelper { OnChanged = "any" }); 30 | page.DataSources.Add(new TestStoreDatasourceTagHelper { DatasourceKey = "any" }); 31 | 32 | page.ExecuteSynchronously(); 33 | 34 | var dataSourceConfig = page.TopLevelTag.GetConfigValue>("dataSource"); 35 | Assert.True(dataSourceConfig.ContainsKey("OnChanged")); 36 | Assert.True(dataSourceConfig.ContainsKey("store")); 37 | } 38 | 39 | [Fact] 40 | public void PivotGridDatasourceCreatesConfig() { 41 | var page = new PivotDatasourcePage(); 42 | page.DataSources.Add(new PivotGridDatasourceTagHelper { OnChanged = "any" }); 43 | 44 | page.ExecuteSynchronously(); 45 | 46 | var dataSourceConfig = page.TopLevelTag.GetConfigValue>("dataSource"); 47 | Assert.True(dataSourceConfig.ContainsKey("OnChanged")); 48 | } 49 | 50 | class TestStoreDatasourceTagHelper : StoreDatasourceTagHelper { 51 | protected override string FormatStoreFactory(string args) { 52 | return null; 53 | } 54 | 55 | protected override void PopulateStoreConfig(IDictionary config) { 56 | } 57 | } 58 | 59 | class TestParentTagHelper : HierarchicalTagHelper { 60 | protected override string Key { 61 | get { return "any"; } 62 | } 63 | 64 | protected override string FullKey { 65 | get { return "any"; } 66 | } 67 | 68 | protected internal override bool ValidateParentFullKey(string parentFullKey) { 69 | return true; 70 | } 71 | } 72 | 73 | class PivotDatasourcePage : TestPage { 74 | 75 | public readonly IList DataSources = new List(); 76 | 77 | protected override void ExecuteCore() { 78 | Add(new TestParentTagHelper(), delegate { 79 | Add(DataSources.ToArray()); 80 | }); 81 | } 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Tests/TestPage.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc.Razor; 3 | using Microsoft.AspNetCore.Mvc.Rendering; 4 | using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; 5 | using Microsoft.AspNetCore.Razor.Runtime.TagHelpers; 6 | using Microsoft.AspNetCore.Razor.TagHelpers; 7 | using System; 8 | using System.Buffers; 9 | using System.Collections.Generic; 10 | using System.IO; 11 | using System.Linq; 12 | using System.Text; 13 | using System.Text.Encodings.Web; 14 | using System.Threading.Tasks; 15 | 16 | namespace DevExtreme.AspNet.TagHelpers.Tests { 17 | 18 | abstract class TestPage : RazorPage { 19 | Exception _execError; 20 | StringBuilder _outputBuilder = new StringBuilder(); 21 | TagHelperRunner _runner = new TagHelperRunner(); 22 | TagHelperScopeManager _scopeManager; 23 | TagHelperExecutionContext _execContext; 24 | 25 | HierarchicalTagHelper _topLevelTag; 26 | 27 | public TestPage() { 28 | _scopeManager = new TagHelperScopeManager(StartTagHelperWritingScope, EndTagHelperWritingScope); 29 | 30 | HtmlEncoder = HtmlEncoder.Default; 31 | ViewContext = new ViewContext { 32 | HttpContext = new DefaultHttpContext { 33 | RequestServices = new ServiceProviderMock() 34 | }, 35 | Writer = new StringWriter(_outputBuilder) 36 | }; 37 | } 38 | 39 | public HierarchicalTagHelper TopLevelTag { 40 | get { return _topLevelTag; } 41 | } 42 | 43 | public string GetOutputText() { 44 | return _outputBuilder.ToString(); 45 | } 46 | 47 | public void ExecuteSynchronously() { 48 | ExecuteAsync().Wait(); 49 | 50 | if(_execError != null) 51 | throw _execError; 52 | } 53 | 54 | public sealed override Task ExecuteAsync() { 55 | return Task.Factory.StartNew(ExecuteCore); 56 | } 57 | 58 | protected abstract void ExecuteCore(); 59 | 60 | protected void Add(ITagHelper tag, Action executeChildContent = null) { 61 | Add(new[] { tag }, executeChildContent); 62 | } 63 | 64 | protected async void Add(ITagHelper[] tags, Action executeChildContent = null) { 65 | if(_execError != null) 66 | return; 67 | 68 | _execContext = _scopeManager.Begin("", 0, "", WrapExecuteChildContent(executeChildContent)); 69 | 70 | foreach(var tag in tags) { 71 | if(_topLevelTag == null) 72 | _topLevelTag = tag as HierarchicalTagHelper; 73 | 74 | _execContext.Add(tag); 75 | } 76 | 77 | try { 78 | await _runner.RunAsync(_execContext); 79 | Write(_execContext.Output); 80 | } catch(Exception x) { 81 | #warning see https://github.com/aspnet/Hosting/issues/484 82 | _execError = x; 83 | } 84 | 85 | _execContext = _scopeManager.End(); 86 | } 87 | 88 | Func WrapExecuteChildContent(Action generator) { 89 | return delegate { 90 | if(generator != null) 91 | generator(); 92 | 93 | return Task.FromResult(true); // see http://stackoverflow.com/a/13127229 94 | }; 95 | } 96 | 97 | 98 | class ServiceProviderMock : IServiceProvider { 99 | IViewBufferScope _viewBufferScope = new MemoryPoolViewBufferScope( 100 | ArrayPool.Shared, 101 | ArrayPool.Shared 102 | ); 103 | 104 | public object GetService(Type serviceType) { 105 | if(serviceType == typeof(IViewBufferScope)) 106 | return _viewBufferScope; 107 | 108 | return null; 109 | } 110 | } 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Tests/HierarchicalTagHelperTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Xunit; 6 | 7 | namespace DevExtreme.AspNet.TagHelpers.Tests { 8 | 9 | public class HierarchicalTagHelperTests { 10 | 11 | class HierarchyTestPage : TestPage { 12 | 13 | protected override void ExecuteCore() { 14 | var child = new TestTagHelper("child"); 15 | child.SetConfigValue("childProp", "yes"); 16 | 17 | var grandChild = new TestTagHelper("grandChild"); 18 | grandChild.SetConfigValue("grandChildProp", "yes"); 19 | 20 | Add(new TestTagHelper("parent"), delegate { 21 | Add(child, delegate { Add(grandChild); }); 22 | }); 23 | } 24 | } 25 | 26 | class ParentValidationTestPage : TestPage { 27 | 28 | protected override void ExecuteCore() { 29 | Add(new TestTagHelper("parent"), delegate { 30 | Add(new TestTagHelper("invalid", false).EnsureConfigNotEmpty()); 31 | }); 32 | } 33 | } 34 | 35 | [Fact] 36 | public void ProvidesConfigAndFullKeyToChildren() { 37 | var page = new HierarchyTestPage(); 38 | page.ExecuteSynchronously(); 39 | 40 | var child = page.TopLevelTag.GetConfigValue>("child"); 41 | Assert.Equal("yes", child["childProp"]); 42 | 43 | var grandChild = child["grandChild"] as IDictionary; 44 | Assert.Equal("yes", grandChild["grandChildProp"]); 45 | } 46 | 47 | [Fact] 48 | public void AppendsConfigByKey() { 49 | var tagHelper = new TestTagHelper("key1"); 50 | var parentConfig = new Dictionary(); 51 | 52 | tagHelper.EnsureConfigNotEmpty(); 53 | tagHelper.AppendConfig(parentConfig); 54 | 55 | Assert.True(parentConfig.ContainsKey("key1")); 56 | } 57 | 58 | [Fact] 59 | public void DoesNotAppendEmptyConfig() { 60 | var tagHelper = new TestTagHelper("key1"); 61 | var parentConfig = new Dictionary(); 62 | 63 | tagHelper.AppendConfig(parentConfig); 64 | 65 | Assert.Equal(0, parentConfig.Count); 66 | } 67 | 68 | [Fact] 69 | public void ReturnsDefaultIfValueNotSet() { 70 | Assert.Equal(0, new TestTagHelper("any").GetConfigValue("any")); 71 | } 72 | 73 | [Fact] 74 | public void ReturnsRawValue() { 75 | var tagHelper = new TestTagHelper("any"); 76 | tagHelper.SetConfigValue("ABC", "OnClick", isRaw: true); 77 | 78 | Assert.Equal("OnClick", tagHelper.GetConfigValue("ABC")); 79 | } 80 | 81 | [Fact] 82 | public void DoesNotUpdateConfigIfParentIsWrong() { 83 | var page = new ParentValidationTestPage(); 84 | page.ExecuteSynchronously(); 85 | Assert.Null(page.TopLevelTag.GetConfigValue("invalid")); 86 | } 87 | 88 | class TestTagHelper : HierarchicalTagHelper { 89 | string _key; 90 | bool _isValid; 91 | 92 | public TestTagHelper(string key, bool isValid = true) { 93 | _key = key; 94 | _isValid = isValid; 95 | } 96 | 97 | protected override string Key { 98 | get { return _key; } 99 | } 100 | 101 | protected override string FullKey { 102 | get { return null; } 103 | } 104 | 105 | public TestTagHelper EnsureConfigNotEmpty() { 106 | SetConfigValue("any", "any"); 107 | return this; 108 | } 109 | 110 | protected internal override bool ValidateParentFullKey(string parentFullKey) { 111 | return _isValid; 112 | } 113 | } 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Generator/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.Xml.Linq; 6 | 7 | namespace DevExtreme.AspNet.TagHelpers.Generator { 8 | 9 | class Program { 10 | const string DX_VERSION = "15.2"; 11 | 12 | static void Main(string[] args) { 13 | var widgetNames = new HashSet { 14 | "dxChart", 15 | "dxDataGrid", 16 | "dxScheduler", 17 | "dxPieChart", 18 | "dxRangeSelector", 19 | "dxSparkline", 20 | "dxPivotGrid" 21 | }; 22 | 23 | var ns = new[] { "DevExtreme.AspNet.TagHelpers" }; 24 | var tagInfoPreProcessor = new TagInfoPreProcessor(); 25 | var generator = new Generator(outputRoot: "../"); 26 | generator.DeleteGeneratedFiles(ns); 27 | 28 | foreach(var obj in GetIntellisenseObjectsFor(widgetNames)) 29 | generator.GenerateClass(new TagInfo(obj, tagInfoPreProcessor, ns, null)); 30 | 31 | generator.GenerateEnums(ns, "Enums", EnumRegistry.KnownEnumns); 32 | 33 | generator.GenerateTargetElementsClass( 34 | ns, 35 | "InnerScriptTagHelper", 36 | TargetElementsRegistry.InnerScriptTargets.Select(CreateInnerScriptTarget) 37 | ); 38 | 39 | generator.GenerateTargetElementsClass( 40 | ns.Concat("Data"), 41 | "LoadActionDatasourceTagHelper", 42 | TargetElementsRegistry.DatasourceTargets.Select(GetDataSourceTargetFactory("load-action")) 43 | ); 44 | 45 | generator.GenerateTargetElementsClass( 46 | ns.Concat("Data"), 47 | "ItemsDatasourceTagHelper", 48 | TargetElementsRegistry.DatasourceTargets.Select(GetDataSourceTargetFactory("items")) 49 | ); 50 | 51 | generator.GenerateTargetElementsClass( 52 | ns.Concat("Data"), 53 | "UrlDatasourceTagHelper", 54 | TargetElementsRegistry.DatasourceTargets.Select(GetDataSourceTargetFactory("url")) 55 | ); 56 | 57 | generator.GenerateClass( 58 | CreatePivotGridDatasourceTag(tagInfoPreProcessor, ns), 59 | "PivotGridDatasourceTagHelper", 60 | isPartial: true, 61 | generateKeyProps: false 62 | ); 63 | 64 | Console.WriteLine("Done"); 65 | } 66 | 67 | static IEnumerable GetIntellisenseObjectsFor(ICollection names) { 68 | return XDocument.Load($"meta/IntellisenseData_{DX_VERSION}.xml") 69 | .Root 70 | .Elements("IntellisenseObjectInfo") 71 | .Where(o => names.Contains(o.GetName())); 72 | } 73 | 74 | static TargetElementInfo CreateInnerScriptTarget(string parentTagName) { 75 | return new TargetElementInfo { 76 | Tag = "script", 77 | ParentTag = parentTagName, 78 | IsSelfClosing = false 79 | }; 80 | } 81 | 82 | static Func GetDataSourceTargetFactory(string bindingAttribute) { 83 | return parentTagName => new TargetElementInfo { 84 | Tag = "datasource", 85 | BindingAttribute = bindingAttribute, 86 | ParentTag = parentTagName, 87 | IsSelfClosing = parentTagName != "dx-pivot-grid" 88 | }; 89 | } 90 | 91 | static TagInfo CreatePivotGridDatasourceTag(TagInfoPreProcessor tagInfoPreProcessor, IEnumerable ns) { 92 | var xDoc = XDocument.Load($"meta/IntellisenseData_{DX_VERSION}_spec.xml"); 93 | var element = new XElement(xDoc.Root.Elements("IntellisenseObjectInfo").Single(el => el.GetName() == "PivotGridDataSource")); 94 | element.SetName("datasource"); 95 | element.GetPropElement("store").Remove(); 96 | 97 | var result = new TagInfo(element, tagInfoPreProcessor, ns.Concat("Data"), parentTagName: "dx-pivot-grid"); 98 | result.Key = "dataSource"; 99 | result.BaseClassName = null; 100 | 101 | return result; 102 | } 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /Samples/Models/Northwind/NorthwindContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata; 3 | 4 | namespace Samples.Models.Northwind { 5 | public partial class NorthwindContext : DbContext { 6 | protected override void OnConfiguring(DbContextOptionsBuilder options) { 7 | options.UseSqlServer(@"data source=.\SQLEXPRESS;initial catalog=NORTHWND;integrated security=True"); 8 | } 9 | 10 | protected override void OnModelCreating(ModelBuilder modelBuilder) { 11 | modelBuilder.Entity(entity => { 12 | entity.HasIndex(e => e.CategoryName).HasName("CategoryName"); 13 | 14 | entity.Property(e => e.Description).HasColumnType("ntext"); 15 | 16 | entity.Property(e => e.Picture).HasColumnType("image"); 17 | }); 18 | 19 | modelBuilder.Entity(entity => { 20 | entity.HasIndex(e => e.City).HasName("City"); 21 | 22 | entity.HasIndex(e => e.Region).HasName("Region"); 23 | 24 | entity.HasIndex(e => new { e.CompanyName }).HasName("CompanyName"); 25 | 26 | entity.Property(e => e.CustomerID).HasColumnType("nchar(5)"); 27 | }); 28 | 29 | modelBuilder.Entity(entity => { 30 | entity.HasIndex(e => e.LastName).HasName("LastName"); 31 | 32 | entity.HasIndex(e => new { e.PostalCode }).HasName("PostalCode"); 33 | 34 | entity.Property(e => e.BirthDate).HasColumnType("datetime"); 35 | 36 | entity.Property(e => e.HireDate).HasColumnType("datetime"); 37 | 38 | entity.Property(e => e.Notes).HasColumnType("ntext"); 39 | 40 | entity.Property(e => e.Photo).HasColumnType("image"); 41 | }); 42 | 43 | modelBuilder.Entity(entity => { 44 | entity.HasKey(e => new { e.OrderID, e.ProductID }); 45 | 46 | entity.HasIndex(e => e.OrderID).HasName("OrdersOrder_Details"); 47 | 48 | entity.HasIndex(e => e.ProductID).HasName("ProductsOrder_Details"); 49 | 50 | entity.Property(e => e.Discount).HasDefaultValueSql("0"); 51 | 52 | entity.Property(e => e.Quantity).HasDefaultValueSql("1"); 53 | 54 | entity.Property(e => e.UnitPrice) 55 | .HasColumnType("money") 56 | .HasDefaultValueSql("0"); 57 | }); 58 | 59 | modelBuilder.Entity(entity => { 60 | entity.HasIndex(e => e.CustomerID).HasName("CustomersOrders"); 61 | 62 | entity.HasIndex(e => e.EmployeeID).HasName("EmployeesOrders"); 63 | 64 | entity.HasIndex(e => e.OrderDate).HasName("OrderDate"); 65 | 66 | entity.HasIndex(e => e.ShipPostalCode).HasName("ShipPostalCode"); 67 | 68 | entity.HasIndex(e => e.ShipVia).HasName("ShippersOrders"); 69 | 70 | entity.HasIndex(e => e.ShippedDate).HasName("ShippedDate"); 71 | 72 | entity.Property(e => e.CustomerID).HasColumnType("nchar(5)"); 73 | 74 | entity.Property(e => e.Freight) 75 | .HasColumnType("money") 76 | .HasDefaultValueSql("0"); 77 | 78 | entity.Property(e => e.OrderDate).HasColumnType("datetime"); 79 | 80 | entity.Property(e => e.RequiredDate).HasColumnType("datetime"); 81 | 82 | entity.Property(e => e.ShippedDate).HasColumnType("datetime"); 83 | }); 84 | 85 | modelBuilder.Entity(entity => { 86 | entity.HasIndex(e => e.CategoryID).HasName("CategoryID"); 87 | 88 | entity.HasIndex(e => e.ProductName).HasName("ProductName"); 89 | 90 | entity.HasIndex(e => e.SupplierID).HasName("SuppliersProducts"); 91 | 92 | entity.Property(e => e.Discontinued).HasDefaultValueSql("0"); 93 | 94 | entity.Property(e => e.ReorderLevel).HasDefaultValueSql("0"); 95 | 96 | entity.Property(e => e.UnitPrice) 97 | .HasColumnType("money") 98 | .HasDefaultValueSql("0"); 99 | 100 | entity.Property(e => e.UnitsInStock).HasDefaultValueSql("0"); 101 | 102 | entity.Property(e => e.UnitsOnOrder).HasDefaultValueSql("0"); 103 | }); 104 | 105 | modelBuilder.Entity(entity => { 106 | entity.Property(e => e.HomePage).HasColumnType("ntext"); 107 | }); 108 | } 109 | 110 | public virtual DbSet Categories { get; set; } 111 | public virtual DbSet Customers { get; set; } 112 | public virtual DbSet Employees { get; set; } 113 | public virtual DbSet Order_Details { get; set; } 114 | public virtual DbSet Orders { get; set; } 115 | public virtual DbSet Products { get; set; } 116 | public virtual DbSet Shippers { get; set; } 117 | public virtual DbSet Suppliers { get; set; } 118 | } 119 | } -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Generator/Generator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace DevExtreme.AspNet.TagHelpers.Generator { 8 | 9 | class Generator { 10 | static readonly string[] DEFAULT_USINGS = new[] { 11 | "System", 12 | "System.Collections.Generic", 13 | "Microsoft.AspNetCore.Razor.TagHelpers" 14 | }; 15 | 16 | string _outputRoot; 17 | 18 | public Generator(string outputRoot) { 19 | _outputRoot = outputRoot; 20 | } 21 | 22 | public void GenerateClass(TagInfo tag, string customClassName = null, bool isPartial = false, bool generateKeyProps = true) { 23 | var childTags = new List(); 24 | foreach(var child in tag.GenerateChildTags()) { 25 | childTags.Add(child.GetTagName()); 26 | GenerateClass(child); 27 | } 28 | 29 | var childRestrictions = tag.GetChildRestrictions(childTags); 30 | var className = customClassName ?? tag.GetClassName(); 31 | 32 | var builder = new ClassBuilder(); 33 | 34 | builder.AppendHeader(); 35 | builder.AppendUsings(DEFAULT_USINGS); 36 | builder.StartNamespaceBlock(tag.Namespace); 37 | 38 | builder.AppendSummary(tag.GetSummaryText()); 39 | 40 | if(!isPartial) 41 | builder.AppendGeneratedAttribute(); 42 | 43 | builder.AppendHtmlTargetAttribute(new TargetElementInfo { 44 | Tag = tag.GetTagName(), 45 | ParentTag = tag.ParentTagName, 46 | IsSelfClosing = !childRestrictions.Any() 47 | }); 48 | 49 | if(childRestrictions.Any()) 50 | builder.AppendAttribute("RestrictChildren", childRestrictions.Select(r => '"' + r + '"')); 51 | 52 | builder.StartClass(className, tag.BaseClassName, isPartial); 53 | builder.AppendEmptyLine(); 54 | 55 | if(generateKeyProps) { 56 | builder.AppendKeyProperty("Key", tag.Key); 57 | builder.AppendKeyProperty("FullKey", tag.GetFullKey()); 58 | } 59 | 60 | foreach(var prop in tag.GenerateProperties()) { 61 | CompetitivePropsRegistry.Register(tag.GetFullKey() + "." + prop.GetName(), prop.GetClrType()); 62 | builder.AppendProp(prop); 63 | } 64 | 65 | builder.EndBlock(); 66 | builder.AppendEmptyLine(); 67 | builder.EndBlock(); 68 | 69 | WriteToFile(tag.Namespace, className, builder.ToString()); 70 | } 71 | 72 | public void GenerateTargetElementsClass(IEnumerable ns, string className, IEnumerable targets) { 73 | var builder = new ClassBuilder(); 74 | 75 | builder.AppendHeader(); 76 | builder.AppendUsings(DEFAULT_USINGS); 77 | builder.StartNamespaceBlock(ns); 78 | 79 | foreach(var target in targets) 80 | builder.AppendHtmlTargetAttribute(target); 81 | 82 | builder.StartClass(className, baseClassName: null, isPartial: true); 83 | builder.EndBlock(); 84 | builder.AppendEmptyLine(); 85 | builder.EndBlock(); 86 | 87 | WriteToFile(ns, className, builder.ToString()); 88 | } 89 | 90 | public void GenerateEnums(IEnumerable ns, string className, IDictionary knownEnums) { 91 | var builder = new ClassBuilder(); 92 | 93 | builder.AppendHeader(); 94 | builder.AppendUsings(new[] { 95 | "Newtonsoft.Json", 96 | "Newtonsoft.Json.Converters", 97 | "System.Runtime.Serialization", 98 | }); 99 | builder.StartNamespaceBlock(ns); 100 | 101 | foreach(var entry in knownEnums) { 102 | builder.AppendAttribute("JsonConverter", "typeof(StringEnumConverter)"); 103 | builder.Append($"public enum {entry.Key} "); 104 | builder.StartBlock(); 105 | 106 | foreach(var item in entry.Value.EnumerateForRendering()) { 107 | if(!item.IsFirst) 108 | builder.AppendLine(","); 109 | 110 | builder.AppendAttribute("EnumMember", $"Value = \"{item.JavaScriptValue}\""); 111 | builder.Append($"{item.CSharpValue}"); 112 | } 113 | 114 | builder.AppendEmptyLine(); 115 | builder.EndBlock(); 116 | builder.AppendEmptyLine(); 117 | } 118 | 119 | builder.EndBlock(); 120 | 121 | WriteToFile(ns, className, builder.ToString()); 122 | } 123 | 124 | public void DeleteGeneratedFiles(string[] ns) { 125 | var dirName = Path.Combine(_outputRoot, Path.Combine(ns)); 126 | 127 | foreach(var path in Directory.GetFiles(dirName, "*.g.cs", SearchOption.AllDirectories)) 128 | File.Delete(path); 129 | } 130 | 131 | void WriteToFile(IEnumerable ns, string className, string text) { 132 | var path = Path.Combine(_outputRoot, Path.Combine(ns.ToArray())); 133 | 134 | if(!Directory.Exists(path)) 135 | Directory.CreateDirectory(path); 136 | 137 | File.WriteAllText(Path.Combine(path, className + ".g.cs"), text); 138 | } 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Generator/ClassBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace DevExtreme.AspNet.TagHelpers.Generator { 8 | 9 | class ClassBuilder { 10 | const int TAB_SIZE = 4; 11 | int _indent; 12 | bool _isLineStart = true; 13 | 14 | StringBuilder _builder = new StringBuilder(); 15 | 16 | public void Append(string text) { 17 | _builder.Append(AlignText(text)); 18 | _isLineStart = false; 19 | } 20 | 21 | public void AppendLine(string text) { 22 | _builder.AppendLine(AlignText(text)); 23 | _isLineStart = true; 24 | } 25 | 26 | public void AppendEmptyLine() { 27 | _builder.AppendLine(); 28 | _isLineStart = true; 29 | } 30 | 31 | string AlignText(string text) { 32 | if(_isLineStart) 33 | return new String(' ', TAB_SIZE * _indent) + text; 34 | 35 | return text; 36 | } 37 | 38 | public void StartBlock() { 39 | AppendLine("{"); 40 | _indent += 1; 41 | } 42 | 43 | public void EndBlock() { 44 | if(_indent > 0) 45 | _indent -= 1; 46 | 47 | AppendLine("}"); 48 | } 49 | 50 | public void AppendHeader() { 51 | AppendLine("// THIS FILE WAS GENERATED AUTOMATICALLY."); 52 | AppendLine("// ALL CHANGES WILL BE LOST THE NEXT TIME THE FILE IS GENERATED."); 53 | AppendEmptyLine(); 54 | } 55 | 56 | public void AppendUsings(IEnumerable usings) { 57 | foreach(var entry in usings) 58 | AppendLine($"using {entry};"); 59 | 60 | AppendEmptyLine(); 61 | } 62 | 63 | public void StartNamespaceBlock(IEnumerable ns) { 64 | Append($"namespace {String.Join(".", ns)} "); 65 | StartBlock(); 66 | AppendEmptyLine(); 67 | } 68 | 69 | public void AppendSummary(string summary) { 70 | AppendLine($"/// {summary}"); 71 | } 72 | 73 | public void StartClass(string className, string baseClassName, bool isPartial) { 74 | if(isPartial) 75 | Append("partial "); 76 | else 77 | Append("public "); 78 | 79 | Append($"class {className} "); 80 | 81 | if(!String.IsNullOrEmpty(baseClassName)) 82 | Append($": {baseClassName} "); 83 | 84 | StartBlock(); 85 | } 86 | 87 | public void AppendGeneratedAttribute() { 88 | AppendAttribute("Generated"); 89 | } 90 | 91 | public void AppendHtmlTargetAttribute(TargetElementInfo targetElement) { 92 | var attributeParams = new List { $"\"{targetElement.Tag}\"" }; 93 | 94 | if(!String.IsNullOrEmpty(targetElement.BindingAttribute)) 95 | attributeParams.Add($"Attributes = \"{targetElement.BindingAttribute}\""); 96 | 97 | if(targetElement.IsSelfClosing) 98 | attributeParams.Add("TagStructure = TagStructure.WithoutEndTag"); 99 | 100 | if(!String.IsNullOrEmpty(targetElement.ParentTag)) 101 | attributeParams.Add($"ParentTag = \"{targetElement.ParentTag}\""); 102 | 103 | AppendAttribute("HtmlTargetElement", attributeParams); 104 | } 105 | 106 | public void AppendAttribute(string attributeName) { 107 | AppendAttribute(attributeName, Enumerable.Empty()); 108 | } 109 | 110 | public void AppendAttribute(string attributeName, string attributeParam) { 111 | AppendAttribute(attributeName, new[] { attributeParam }); 112 | } 113 | 114 | public void AppendAttribute(string attributeName, IEnumerable attributeParams) { 115 | Append($"[{attributeName}"); 116 | 117 | if(attributeParams.Any()) 118 | Append($"({String.Join(", ", attributeParams)})"); 119 | 120 | AppendLine("]"); 121 | } 122 | 123 | public void AppendKeyProperty(string keyPropName, string key) { 124 | AppendGeneratedAttribute(); 125 | Append($"protected override string {keyPropName} "); 126 | StartBlock(); 127 | 128 | AppendLine($"get {{ return \"{key}\"; }}"); 129 | 130 | EndBlock(); 131 | AppendEmptyLine(); 132 | } 133 | 134 | public void AppendProp(TagPropertyInfo prop) { 135 | AppendSummary(prop.GetSummaryText()); 136 | 137 | var customAttr = prop.GetCustomAttrName(); 138 | if(!String.IsNullOrEmpty(customAttr)) 139 | AppendAttribute("HtmlAttributeName", $"\"{customAttr}\""); 140 | 141 | var name = prop.GetName(); 142 | var dirtyType = prop.GetClrType(); 143 | var type = PropTypeRegistry.StripSpecialType(dirtyType); 144 | 145 | AppendGeneratedAttribute(); 146 | Append($"public {type} {name} "); 147 | StartBlock(); 148 | 149 | AppendLine($"get {{ return GetConfigValue<{type}>(\"{name}\"); }}"); 150 | Append($"set {{ SetConfigValue(\"{name}\", "); 151 | 152 | if(dirtyType == PropTypeRegistry.SPECIAL_DOM_TEMPLATE) 153 | Append("Utils.WrapDomTemplateValue(value)"); 154 | else 155 | Append("value"); 156 | 157 | if(dirtyType == PropTypeRegistry.SPECIAL_RAW_STRING || dirtyType == PropTypeRegistry.SPECIAL_DOM_TEMPLATE) 158 | Append(", isRaw: true"); 159 | 160 | AppendLine("); }"); 161 | EndBlock(); 162 | AppendEmptyLine(); 163 | } 164 | 165 | public override string ToString() { 166 | return _builder.ToString(); 167 | } 168 | } 169 | 170 | } 171 | -------------------------------------------------------------------------------- /Samples/Controllers/NorthwindController.cs: -------------------------------------------------------------------------------- 1 | using DevExtreme.AspNet.Data; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.EntityFrameworkCore; 4 | using Newtonsoft.Json; 5 | using Samples.Models.Northwind; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Threading.Tasks; 10 | using DevExtreme.AspNet.TagHelpers; 11 | 12 | namespace Samples.Controllers { 13 | 14 | public class NorthwindController : Controller { 15 | NorthwindContext _nwind; 16 | 17 | public NorthwindController(NorthwindContext nwind) { 18 | _nwind = nwind; 19 | 20 | #if DEBUG 21 | // Database connection string can be changed in Models\Northwind\NorthwindContext.cs 22 | // 'NORTHWND' database can be downloaded from https://northwinddatabase.codeplex.com/ 23 | _nwind.Database.OpenConnection(); 24 | #endif 25 | } 26 | 27 | [HttpGet] 28 | public object SalesCube() { 29 | return from d in _nwind.Order_Details 30 | let p = d.Product 31 | let o = d.Order 32 | select new { 33 | o.OrderDate, 34 | p.ProductName, 35 | p.Category.CategoryName, 36 | Sum = d.Quantity * d.UnitPrice 37 | }; 38 | } 39 | 40 | [HttpGet] 41 | public object Orders(DataSourceLoadOptions options) { 42 | return DataSourceLoader.Load(_nwind.Orders, options); 43 | } 44 | 45 | [HttpGet] 46 | public object OrderDetails(int orderID, DataSourceLoadOptions options) { 47 | return DataSourceLoader.Load( 48 | from i in _nwind.Order_Details 49 | where i.OrderID == orderID 50 | select new { 51 | i.Product.ProductName, 52 | i.UnitPrice, 53 | i.Quantity, 54 | Sum = i.UnitPrice * i.Quantity 55 | }, 56 | options 57 | ); 58 | } 59 | 60 | [HttpGet] 61 | public object CustomersLookup(DataSourceLoadOptions options) { 62 | return DataSourceLoader.Load( 63 | from c in _nwind.Customers 64 | orderby c.CompanyName 65 | select new { Value = c.CustomerID, Text = $"{c.CompanyName} ({c.Country})" }, 66 | options 67 | ); 68 | } 69 | 70 | [HttpGet] 71 | public object ShippersLookup(DataSourceLoadOptions options) { 72 | return DataSourceLoader.Load( 73 | from s in _nwind.Shippers 74 | orderby s.CompanyName 75 | select new { Value = s.ShipperID, Text = s.CompanyName }, 76 | options 77 | ); 78 | } 79 | 80 | [HttpPut] 81 | public IActionResult UpdateOrder(int key, string values) { 82 | var order = _nwind.Orders.First(o => o.OrderID == key); 83 | JsonConvert.PopulateObject(values, order); 84 | 85 | if(!TryValidateModel(order)) 86 | return BadRequest(GetFullErrorString()); 87 | 88 | _nwind.SaveChanges(); 89 | 90 | return Ok(); 91 | } 92 | 93 | [HttpPost] 94 | public IActionResult InsertOrder(string values) { 95 | var order = new Order(); 96 | JsonConvert.PopulateObject(values, order); 97 | 98 | if(!TryValidateModel(order)) 99 | return BadRequest(GetFullErrorString()); 100 | 101 | 102 | _nwind.Orders.Add(order); 103 | _nwind.SaveChanges(); 104 | 105 | return Ok(); 106 | } 107 | 108 | [HttpDelete] 109 | public void DeleteOrder(int key) { 110 | var order = _nwind.Orders.First(o => o.OrderID == key); 111 | _nwind.Orders.Remove(order); 112 | _nwind.SaveChanges(); 113 | } 114 | 115 | [HttpGet] 116 | public object ShipsByMonth(string shipper) { 117 | return from o in _nwind.Orders.Include(o => o.Shipper) 118 | where o.Shipper != null 119 | orderby o.OrderDate 120 | group o by o.OrderDate.Value.ToString("yyyy'/'MM") into g 121 | select new { 122 | Month = g.Key, 123 | Amount = g.Count(o => o.Shipper.CompanyName == shipper), 124 | TotalAmount = g.Count() 125 | }; 126 | } 127 | 128 | [HttpGet] 129 | public object SalesByCategory() { 130 | #warning TODO remove ToArray() when https://github.com/aspnet/EntityFramework/issues/3676 fix is released 131 | return from d in _nwind.Order_Details.Include(d => d.Product.Category).ToArray() 132 | group d by d.Product.Category.CategoryName into g 133 | let sales = g.Sum(d => d.Quantity * d.UnitPrice) 134 | orderby sales descending 135 | select new { 136 | Category = g.Key, 137 | Sales = sales, 138 | Count = g.Count() 139 | }; 140 | } 141 | 142 | [HttpGet] 143 | public object SalesByCategoryYear() { 144 | return from d in _nwind.Order_Details.Include(d => d.Product.Category).Include(d => d.Order) 145 | let year = d.Order.OrderDate.Value.Year 146 | let category = d.Product.Category.CategoryName 147 | orderby year, category 148 | group d by new { Year = year, Category = category } into g 149 | select new { 150 | g.Key.Year, 151 | g.Key.Category, 152 | Sales = g.Sum(d => d.Quantity * d.UnitPrice) 153 | }; 154 | } 155 | 156 | string GetFullErrorString() { 157 | var messages = new List(); 158 | 159 | foreach(var entry in ModelState.Values) { 160 | foreach(var error in entry.Errors) 161 | messages.Add(error.ErrorMessage); 162 | } 163 | 164 | return String.Join(" ", messages); 165 | } 166 | 167 | } 168 | 169 | } 170 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Generator/PropTypeRegistry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace DevExtreme.AspNet.TagHelpers.Generator { 7 | 8 | static class PropTypeRegistry { 9 | public const string CLR_TYPE_OVERRIDE_ATTR = "ClrTypeOverride"; 10 | 11 | public const string 12 | CLR_BOOL = "bool", 13 | CLR_DOUBLE = "double", 14 | CLR_STRING = "string", 15 | CLR_DATE = "DateTime", 16 | CLR_OBJECT = "object", 17 | CLR_ARRAY_STRING = "IEnumerable", 18 | CLR_ARRAY_OBJECT = "IEnumerable", 19 | CLR_ARRAY_INT = "IEnumerable", 20 | 21 | SPECIAL_RAW_STRING = "sp_raw_string", 22 | SPECIAL_DOM_TEMPLATE = "sp_dom_template"; 23 | 24 | public static readonly IDictionary OverrideTable = new Dictionary { 25 | { "DevExtreme.AspNet.TagHelpers.Data.Datasource.Field.FilterValues", CLR_ARRAY_OBJECT }, 26 | { "DevExtreme.AspNet.TagHelpers.Data.Datasource.Field.SortBySummaryPath", CLR_ARRAY_STRING }, 27 | { "DevExtreme.AspNet.TagHelpers.Data.Datasource.Filter", CLR_ARRAY_OBJECT }, 28 | 29 | { "DevExtreme.AspNet.TagHelpers.dxChart.ArgumentAxis.Categories", CLR_ARRAY_OBJECT }, 30 | { "DevExtreme.AspNet.TagHelpers.dxChart.ArgumentAxis.ConstantLine.Value", CLR_OBJECT }, 31 | { "DevExtreme.AspNet.TagHelpers.dxChart.ArgumentAxis.Max", CLR_OBJECT }, 32 | { "DevExtreme.AspNet.TagHelpers.dxChart.ArgumentAxis.Min", CLR_OBJECT }, 33 | { "DevExtreme.AspNet.TagHelpers.dxChart.ArgumentAxis.MinorTickInterval", CLR_OBJECT }, 34 | { "DevExtreme.AspNet.TagHelpers.dxChart.ArgumentAxis.Strip.EndValue", CLR_OBJECT }, 35 | { "DevExtreme.AspNet.TagHelpers.dxChart.ArgumentAxis.Strip.StartValue", CLR_OBJECT }, 36 | { "DevExtreme.AspNet.TagHelpers.dxChart.ArgumentAxis.TickInterval", CLR_OBJECT }, 37 | { "DevExtreme.AspNet.TagHelpers.dxChart.CommonSeriesSettings.Label.Position", CLR_OBJECT }, 38 | { "DevExtreme.AspNet.TagHelpers.dxChart.DataPrepareSettings.SortingMethod", CLR_BOOL }, 39 | { "DevExtreme.AspNet.TagHelpers.dxChart.Series.Label.Position", CLR_OBJECT }, 40 | { "DevExtreme.AspNet.TagHelpers.dxChart.Tooltip.Container", CLR_STRING }, 41 | { "DevExtreme.AspNet.TagHelpers.dxChart.ValueAxis.Categories", CLR_ARRAY_OBJECT }, 42 | { "DevExtreme.AspNet.TagHelpers.dxChart.ValueAxis.ConstantLine.Value", CLR_OBJECT }, 43 | { "DevExtreme.AspNet.TagHelpers.dxChart.ValueAxis.Max", CLR_OBJECT }, 44 | { "DevExtreme.AspNet.TagHelpers.dxChart.ValueAxis.Min", CLR_OBJECT }, 45 | { "DevExtreme.AspNet.TagHelpers.dxChart.ValueAxis.MinorTickInterval", CLR_OBJECT }, 46 | { "DevExtreme.AspNet.TagHelpers.dxChart.ValueAxis.Strip.EndValue", CLR_OBJECT }, 47 | { "DevExtreme.AspNet.TagHelpers.dxChart.ValueAxis.Strip.StartValue", CLR_OBJECT }, 48 | { "DevExtreme.AspNet.TagHelpers.dxChart.ValueAxis.TickInterval", CLR_OBJECT }, 49 | 50 | { "DevExtreme.AspNet.TagHelpers.dxDataGrid.Column.CalculateCellValue", SPECIAL_RAW_STRING }, 51 | { "DevExtreme.AspNet.TagHelpers.dxDataGrid.Column.CalculateDisplayValue", SPECIAL_RAW_STRING }, 52 | { "DevExtreme.AspNet.TagHelpers.dxDataGrid.Column.CalculateGroupValue", CLR_STRING }, 53 | { "DevExtreme.AspNet.TagHelpers.dxDataGrid.Column.CalculateSortValue", CLR_STRING }, 54 | { "DevExtreme.AspNet.TagHelpers.dxDataGrid.Column.EditorOptions", CLR_OBJECT }, 55 | { "DevExtreme.AspNet.TagHelpers.dxDataGrid.Column.FilterValues", CLR_ARRAY_OBJECT }, 56 | { "DevExtreme.AspNet.TagHelpers.dxDataGrid.Column.FormItem", CLR_OBJECT }, 57 | { "DevExtreme.AspNet.TagHelpers.dxDataGrid.Column.HeaderFilter.GroupInterval", CLR_OBJECT }, 58 | { "DevExtreme.AspNet.TagHelpers.dxDataGrid.Column.Lookup.DisplayExpr", CLR_STRING }, 59 | { "DevExtreme.AspNet.TagHelpers.dxDataGrid.Column.ValidationRules", CLR_ARRAY_OBJECT }, 60 | { "DevExtreme.AspNet.TagHelpers.dxDataGrid.Editing.Form", CLR_OBJECT }, 61 | { "DevExtreme.AspNet.TagHelpers.dxDataGrid.FilterRow.OperationDescriptions", CLR_OBJECT }, 62 | { "DevExtreme.AspNet.TagHelpers.dxDataGrid.Height", CLR_STRING }, 63 | { "DevExtreme.AspNet.TagHelpers.dxDataGrid.Pager.AllowedPageSizes", CLR_ARRAY_INT }, 64 | { "DevExtreme.AspNet.TagHelpers.dxDataGrid.Pager.Visible", CLR_BOOL }, 65 | { "DevExtreme.AspNet.TagHelpers.dxDataGrid.Scrolling.UseNative", CLR_BOOL }, 66 | { "DevExtreme.AspNet.TagHelpers.dxDataGrid.SelectedRowKeys", CLR_ARRAY_OBJECT }, 67 | { "DevExtreme.AspNet.TagHelpers.dxDataGrid.Width", CLR_STRING }, 68 | 69 | { "DevExtreme.AspNet.TagHelpers.dxPieChart.CommonSeriesSettings.Label.Position", CLR_OBJECT }, 70 | { "DevExtreme.AspNet.TagHelpers.dxPieChart.Series.Label.Position", CLR_OBJECT }, 71 | { "DevExtreme.AspNet.TagHelpers.dxPieChart.Tooltip.Container", CLR_STRING }, 72 | 73 | { "DevExtreme.AspNet.TagHelpers.dxPivotGrid.Height", CLR_STRING }, 74 | { "DevExtreme.AspNet.TagHelpers.dxPivotGrid.Scrolling.UseNative", CLR_BOOL }, 75 | { "DevExtreme.AspNet.TagHelpers.dxPivotGrid.Width", CLR_STRING }, 76 | 77 | { "DevExtreme.AspNet.TagHelpers.dxRangeSelector.Chart.CommonSeriesSettings.Label.Position", CLR_OBJECT }, 78 | { "DevExtreme.AspNet.TagHelpers.dxRangeSelector.Chart.DataPrepareSettings.SortingMethod", CLR_BOOL }, 79 | { "DevExtreme.AspNet.TagHelpers.dxRangeSelector.Chart.Series.Label.Position", CLR_OBJECT }, 80 | { "DevExtreme.AspNet.TagHelpers.dxRangeSelector.Scale.Categories", CLR_ARRAY_STRING }, 81 | { "DevExtreme.AspNet.TagHelpers.dxRangeSelector.Scale.EndValue", CLR_OBJECT }, 82 | { "DevExtreme.AspNet.TagHelpers.dxRangeSelector.Scale.TickInterval", CLR_OBJECT }, 83 | { "DevExtreme.AspNet.TagHelpers.dxRangeSelector.Scale.MinorTickInterval", CLR_OBJECT }, 84 | { "DevExtreme.AspNet.TagHelpers.dxRangeSelector.Scale.StartValue", CLR_OBJECT }, 85 | { "DevExtreme.AspNet.TagHelpers.dxRangeSelector.SelectedRange.EndValue", CLR_OBJECT }, 86 | { "DevExtreme.AspNet.TagHelpers.dxRangeSelector.SelectedRange.StartValue", CLR_OBJECT }, 87 | 88 | { "DevExtreme.AspNet.TagHelpers.dxScheduler.CurrentDate", CLR_DATE }, 89 | { "DevExtreme.AspNet.TagHelpers.dxScheduler.Groups", CLR_ARRAY_STRING }, 90 | { "DevExtreme.AspNet.TagHelpers.dxScheduler.Height", CLR_STRING }, 91 | { "DevExtreme.AspNet.TagHelpers.dxScheduler.Resource.DisplayExpr", CLR_STRING }, 92 | { "DevExtreme.AspNet.TagHelpers.dxScheduler.Resource.ValueExpr", CLR_STRING }, 93 | { "DevExtreme.AspNet.TagHelpers.dxScheduler.Width", CLR_STRING }, 94 | 95 | { "DevExtreme.AspNet.TagHelpers.dxSparkline.Tooltip.Container", CLR_STRING } 96 | }; 97 | 98 | 99 | public static string StripSpecialType(string type) { 100 | if(type == SPECIAL_DOM_TEMPLATE || type == SPECIAL_RAW_STRING) 101 | return CLR_STRING; 102 | return type; 103 | } 104 | 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Generator/TagInfoPreProcessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.Xml.Linq; 6 | 7 | namespace DevExtreme.AspNet.TagHelpers.Generator { 8 | 9 | class TagInfoPreProcessor { 10 | XElement _commonSeriesSettingsSample; 11 | XElement _seriesSample; 12 | 13 | public void Process(TagInfo tag) { 14 | ModifyWidget(tag); 15 | ModifyCollectionItem(tag); 16 | ModifyDatasourceOwner(tag); 17 | ModifyRangeSelectorChartOptions(tag); 18 | ModifyCommonSeriesSettings(tag); 19 | TurnChildrenIntoProps(tag); 20 | ValidateEnums(tag); 21 | ModifyPropTypes(tag); 22 | } 23 | 24 | static void ModifyWidget(TagInfo tag) { 25 | if(tag.ParentTagName != null) 26 | return; 27 | 28 | tag.ExtraChildRestrictions.Add("script"); 29 | tag.BaseClassName = "WidgetTagHelper"; 30 | TargetElementsRegistry.InnerScriptTargets.Add(tag.GetTagName()); 31 | } 32 | 33 | static void ModifyCollectionItem(TagInfo tag) { 34 | if(!CollectionItemsRegistry.SuspectCollectionItem(tag.Element.Attribute("Type")?.Value)) 35 | return; 36 | 37 | if(!CollectionItemsRegistry.IsKnownCollectionItem(tag.GetFullKey())) 38 | throw new Exception($"New collection suspect detected: \"{tag.GetFullKey()}\""); 39 | 40 | tag.Element.SetName(CollectionItemsRegistry.GetModifiedElementName(tag.Element.GetName())); 41 | tag.BaseClassName = "CollectionItemTagHelper"; 42 | } 43 | 44 | static void ModifyDatasourceOwner(TagInfo tag) { 45 | var datasourceProp = tag.PropElements.FirstOrDefault(s => s.GetName() == "dataSource"); 46 | if(datasourceProp == null) 47 | return; 48 | 49 | tag.PropElements.Remove(datasourceProp); 50 | tag.ExtraChildRestrictions.Add("datasource"); 51 | 52 | TargetElementsRegistry.DatasourceTargets.Add(tag.GetTagName()); 53 | } 54 | 55 | void ModifyRangeSelectorChartOptions(TagInfo tag) { 56 | if(tag.GetFullKey() == "DevExtreme.AspNet.TagHelpers.dxChart.CommonSeriesSettings") { 57 | _commonSeriesSettingsSample = new XElement(tag.Element); 58 | return; 59 | } 60 | 61 | if(tag.GetFullKey() == "DevExtreme.AspNet.TagHelpers.dxChart.Series") { 62 | _seriesSample = new XElement(tag.Element); 63 | return; 64 | } 65 | 66 | if(tag.GetFullKey() == "DevExtreme.AspNet.TagHelpers.dxRangeSelector.Chart") { 67 | ReplaceWithClone(tag, "commonSeriesSettings", _commonSeriesSettingsSample); 68 | ReplaceWithClone(tag, "series", _seriesSample); 69 | } 70 | } 71 | 72 | static void ReplaceWithClone(TagInfo tag, string name, XElement sample) { 73 | var element = tag.PropElements.FirstOrDefault(el => el.GetName() == name); 74 | var clone = new XElement(sample); 75 | element.ReplaceWith(clone); 76 | tag.PropElements.Remove(element); 77 | tag.ChildTagElements.Add(clone); 78 | } 79 | 80 | static void ModifyCommonSeriesSettings(TagInfo tag) { 81 | if(tag.GetFullKey() != "DevExtreme.AspNet.TagHelpers.dxChart.CommonSeriesSettings" && 82 | tag.GetFullKey() != "DevExtreme.AspNet.TagHelpers.dxRangeSelector.Chart.CommonSeriesSettings") 83 | return; 84 | 85 | var seriesSettingsPrefix = "commonseriesoptions_"; 86 | 87 | var propOfData = XDocument.Load("meta/PropOf.xml").Root.Elements() 88 | .Select(el => new { 89 | DocID = el.Attribute("docid").Value, 90 | PropertyOf = el.Attribute("propertyOf").Value 91 | }) 92 | .Where(i => i.DocID.StartsWith(seriesSettingsPrefix)) 93 | .ToDictionary( 94 | i => i.DocID.Substring(seriesSettingsPrefix.Length), 95 | i => new HashSet(i.PropertyOf.Split(','), StringComparer.OrdinalIgnoreCase) 96 | ); 97 | 98 | var seriesNames = new HashSet(StringComparer.OrdinalIgnoreCase) { 99 | "area", "bar", "bubble", "candleStick", "fullStackedArea", "fullStackedBar", "fullStackedLine", 100 | "fullStackedSpline", "fullStackedSplineArea", "line", "rangeArea", "rangeBar", "scatter", "spline", 101 | "splineArea", "stackedArea", "stackedBar", "stackedLine", "stackedSpline", "stackedSplineArea", 102 | "stepArea", "stepLine", "stock" 103 | }; 104 | 105 | foreach(var prop in tag.PropElements.Where(p => seriesNames.Contains(p.GetName())).ToArray()) { 106 | prop.Remove(); 107 | tag.PropElements.Remove(prop); 108 | } 109 | 110 | var specificSeriesElement = new XElement(tag.Element); 111 | specificSeriesElement.GetPropElement("type").Remove(); 112 | 113 | foreach(var name in seriesNames) { 114 | var element = new XElement(specificSeriesElement); 115 | element.SetAttributeValue("Name", name); 116 | 117 | foreach(var prop in propOfData.Where(p => !p.Value.Contains(name + "Series"))) 118 | RemoveNestedPropElement(prop.Key.Split('_'), element); 119 | 120 | tag.ChildTagElements.Add(element); 121 | } 122 | } 123 | 124 | static void RemoveNestedPropElement(IEnumerable nameParts, XElement element) { 125 | foreach(var namePart in nameParts) { 126 | if(element == null) 127 | break; 128 | 129 | element = element.GetPropElement(namePart); 130 | } 131 | 132 | element?.Remove(); 133 | } 134 | 135 | static void TurnChildrenIntoProps(TagInfo tag) { 136 | var fullNames = new HashSet { 137 | "DevExtreme.AspNet.TagHelpers.dxChart.ArgumentAxis.TickInterval", 138 | "DevExtreme.AspNet.TagHelpers.dxChart.ValueAxis.TickInterval", 139 | "DevExtreme.AspNet.TagHelpers.dxChart.ArgumentAxis.MinorTickInterval", 140 | "DevExtreme.AspNet.TagHelpers.dxChart.ValueAxis.MinorTickInterval", 141 | "DevExtreme.AspNet.TagHelpers.dxRangeSelector.Scale.MinorTickInterval", 142 | "DevExtreme.AspNet.TagHelpers.dxRangeSelector.Scale.TickInterval" 143 | }; 144 | 145 | var migratingElements = tag.ChildTagElements 146 | .Where(el => fullNames.Contains(tag.GetFullKey() + "." + Utils.ToCamelCase(el.GetName()))) 147 | .ToArray(); 148 | 149 | foreach(var element in migratingElements) { 150 | element.Element("Values").RemoveAll(); 151 | element.Element("Properties").Remove(); 152 | tag.ChildTagElements.Remove(element); 153 | tag.PropElements.Add(element); 154 | } 155 | } 156 | 157 | static void ValidateEnums(TagInfo tag) { 158 | foreach(var propElement in tag.PropElements) { 159 | if(!EnumRegistry.SuspectEnum(propElement)) 160 | continue; 161 | 162 | EnumRegistry.ValidateEnum(propElement, tag.GetFullKey()); 163 | } 164 | } 165 | 166 | static void ModifyPropTypes(TagInfo tag) { 167 | var enumsTable = EnumRegistry.InvertedKnownEnumns; 168 | var propsTable = PropTypeRegistry.OverrideTable; 169 | 170 | foreach(var propElement in tag.PropElements) { 171 | var fullName = tag.GetFullKey() + "." + Utils.ToCamelCase(propElement.GetName()); 172 | var overridenType = String.Empty; 173 | 174 | if(enumsTable.ContainsKey(fullName)) { 175 | if(propElement.GetRawType() == "array") 176 | overridenType = $"IEnumerable<{enumsTable[fullName]}>"; 177 | else 178 | overridenType = enumsTable[fullName]; 179 | } 180 | 181 | if(propsTable.ContainsKey(fullName)) 182 | overridenType = propsTable[fullName]; 183 | 184 | if(!String.IsNullOrEmpty(overridenType)) 185 | propElement.SetAttributeValue(PropTypeRegistry.CLR_TYPE_OVERRIDE_ATTR, overridenType); 186 | } 187 | } 188 | } 189 | 190 | } 191 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Tests/RenderingTests.cs: -------------------------------------------------------------------------------- 1 | using DevExtreme.AspNet.TagHelpers.Data; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.Formatters; 4 | using Microsoft.AspNetCore.Mvc.Routing; 5 | using Microsoft.AspNetCore.Razor.TagHelpers; 6 | using Newtonsoft.Json; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Threading.Tasks; 11 | using Xunit; 12 | using System.Buffers; 13 | using Newtonsoft.Json.Serialization; 14 | 15 | namespace DevExtreme.AspNet.TagHelpers.Tests { 16 | 17 | public class RenderingTests { 18 | TestWidgetPage page = new TestWidgetPage(); 19 | 20 | [Fact] 21 | public void Sample1() { 22 | page.Widget.SetConfigValue("prop1", "value1"); 23 | page.Widget.ID = "id1"; 24 | 25 | page.ExecuteSynchronously(); 26 | 27 | AssertIgnoreWhitespaces.Equal( 28 | @"
29 | ", 35 | page.GetOutputText() 36 | ); 37 | } 38 | 39 | [Fact] 40 | public void RendersGuidForUnspecifiedId() { 41 | page.ExecuteSynchronously(); 42 | 43 | Assert.Matches("dx-[a-f0-9]{16}", page.GetOutputText()); 44 | } 45 | 46 | [Fact] 47 | public void RendersRawUnquoted() { 48 | page.Widget.SetConfigValue("abc", "OnClick", isRaw: true); 49 | 50 | page.ExecuteSynchronously(); 51 | 52 | AssertIgnoreWhitespaces.Contains("\"abc\": OnClick", page.GetOutputText()); 53 | } 54 | 55 | [Fact] 56 | public void RendersLoadActionDatasource_LoadOnly() { 57 | page.Datasource = new LoadActionDatasourceTagHelper(new UrlHelperFactoryMock()) { 58 | Controller = "Controller1", 59 | LoadAction = "Action1" 60 | }; 61 | 62 | page.ExecuteSynchronously(); 63 | 64 | AssertIgnoreWhitespaces.Contains( 65 | @"""dataSource"": { 66 | ""store"": DevExpress.data.AspNet.createStore({ 67 | ""loadUrl"": ""ActionMock(Controller1, Action1)"" 68 | }) 69 | }", 70 | page.GetOutputText()); 71 | } 72 | 73 | [Fact] 74 | public void RendersLoadActionDatasource_AllActions() { 75 | page.Datasource = new LoadActionDatasourceTagHelper(new UrlHelperFactoryMock()) { 76 | Controller = "Controller1", 77 | UpdateAction = "Action2", 78 | InsertAction = "Action3", 79 | DeleteAction = "Action4" 80 | }; 81 | 82 | page.ExecuteSynchronously(); 83 | 84 | var markup = page.GetOutputText(); 85 | 86 | AssertIgnoreWhitespaces.Contains(@"""updateUrl"": ""ActionMock(Controller1, Action2)""", markup); 87 | AssertIgnoreWhitespaces.Contains(@"""insertUrl"": ""ActionMock(Controller1, Action3)""", markup); 88 | AssertIgnoreWhitespaces.Contains(@"""deleteUrl"": ""ActionMock(Controller1, Action4)""", markup); 89 | 90 | AssertIgnoreWhitespaces.DoesNotMatch("(update|insert|delete)Method", markup); 91 | } 92 | 93 | [Fact] 94 | public void RendersLoadActionDatasource_AllActionsWithMethods() { 95 | page.Datasource = new LoadActionDatasourceTagHelper(new UrlHelperFactoryMock()) { 96 | Controller = "Controller1", 97 | UpdateMethod = "Method1", 98 | InsertMethod = "Method2", 99 | DeleteMethod = "Method3" 100 | }; 101 | 102 | page.ExecuteSynchronously(); 103 | 104 | var markup = page.GetOutputText(); 105 | 106 | AssertIgnoreWhitespaces.Contains(@"""updateMethod"": ""Method1""", markup); 107 | AssertIgnoreWhitespaces.Contains(@"""insertMethod"": ""Method2""", markup); 108 | AssertIgnoreWhitespaces.Contains(@"""deleteMethod"": ""Method3""", markup); 109 | } 110 | 111 | [Fact] 112 | public void RendersLoadActionDatasource_OnBeforeSend() { 113 | page.Datasource = new LoadActionDatasourceTagHelper(new UrlHelperFactoryMock()) { 114 | OnBeforeSend = "OnBeforeSendFunc" 115 | }; 116 | 117 | page.ExecuteSynchronously(); 118 | 119 | AssertIgnoreWhitespaces.Contains(@"""onBeforeSend"": OnBeforeSendFunc", page.GetOutputText()); 120 | } 121 | 122 | [Fact] 123 | public void ThrowsIfLoadActionDatasourceActionNotResolved() { 124 | page.Datasource = new LoadActionDatasourceTagHelper(new UrlHelperFactoryMock()) { 125 | LoadAction = UrlHelperFactoryMock.UNRESOLVABLE_ACTION 126 | }; 127 | 128 | var err = Record.Exception(() => page.ExecuteSynchronously()); 129 | 130 | Assert.StartsWith("Unable to resolve", err.Message); 131 | } 132 | 133 | [Theory] 134 | [InlineData("Field1", @"""Field1""")] 135 | [InlineData("Field1|Field2", @"[""Field1"",""Field2""]")] 136 | public void StoreDatasourceRendersKey(string rawKey, string expectedSerializedKey) { 137 | page.Datasource = new TestStoreDatasourceTagHelper() { 138 | DatasourceKey = rawKey 139 | }; 140 | 141 | page.ExecuteSynchronously(); 142 | 143 | AssertIgnoreWhitespaces.Contains($"\"key\": {expectedSerializedKey}", page.GetOutputText()); 144 | } 145 | 146 | [Fact] 147 | public void RendersItemsDatasource() { 148 | var jsonFormatter = new JsonOutputFormatter( 149 | new JsonSerializerSettings { 150 | ContractResolver = new DefaultContractResolver { 151 | NamingStrategy = new CamelCaseNamingStrategy() 152 | } 153 | }, 154 | ArrayPool.Shared 155 | ); 156 | 157 | page.Datasource = new ItemsDatasourceTagHelper(jsonFormatter) { 158 | Items = new[] { 123, 456, 789 } 159 | }; 160 | 161 | page.ExecuteSynchronously(); 162 | 163 | AssertIgnoreWhitespaces.Contains( 164 | @"""dataSource"": { 165 | ""store"": new DevExpress.data.ArrayStore({ 166 | ""data"": [ 123, 456, 789 ] 167 | }) 168 | }", 169 | page.GetOutputText()); 170 | } 171 | 172 | [Fact] 173 | public void RendersUrlDatasource() { 174 | page.Datasource = new UrlDatasourceTagHelper(new UrlHelperFactoryMock()) { 175 | Url = "LoadUrl1" 176 | }; 177 | 178 | page.ExecuteSynchronously(); 179 | 180 | AssertIgnoreWhitespaces.Contains( 181 | @"""dataSource"": { 182 | ""store"": new DevExpress.data.CustomStore({ 183 | ""load"": function() { return $.getJSON(""ContentMock(LoadUrl1)""); } 184 | }) 185 | }", 186 | page.GetOutputText()); 187 | } 188 | 189 | [Fact] 190 | public void RendersInnerScript() { 191 | page.Widget.ID = "id1"; 192 | page.InnerScript = @"EXPECTED_INNER_SCRIPT"; 193 | 194 | page.ExecuteSynchronously(); 195 | 196 | AssertIgnoreWhitespaces.Contains( 197 | @"var options = { }; 198 | EXPECTED_INNER_SCRIPT 199 | $(""#id1"")", 200 | page.GetOutputText() 201 | ); 202 | } 203 | 204 | [Theory] 205 | [InlineData(null, null)] 206 | [InlineData("#css-id", @"$(""#css-id"")")] 207 | [InlineData("non-id", "non-id")] 208 | public void SerializesDomTemplate(string value, string expectedSerializedValue) { 209 | Assert.Equal(expectedSerializedValue, Utils.WrapDomTemplateValue(value)); 210 | } 211 | 212 | class TestWidget : WidgetTagHelper { 213 | 214 | protected override string Key { 215 | get { return "dxTest"; } 216 | } 217 | 218 | protected override string FullKey { 219 | get { return "dxTest"; } 220 | } 221 | } 222 | 223 | class TestWidgetPage : TestPage { 224 | public readonly TestWidget Widget = new TestWidget(); 225 | public StoreDatasourceTagHelper Datasource; 226 | public string InnerScript; 227 | 228 | protected override void ExecuteCore() { 229 | Add(Widget, delegate { 230 | if(Datasource != null) 231 | Add(Datasource); 232 | 233 | if(!String.IsNullOrEmpty(InnerScript)) 234 | Add(new InnerScriptTagHelper(), delegate { 235 | Add(new RawStringTagHelper(InnerScript)); 236 | }); 237 | }); 238 | } 239 | } 240 | 241 | class RawStringTagHelper : TagHelper { 242 | string _contet; 243 | 244 | public RawStringTagHelper(string content) { 245 | _contet = content; 246 | } 247 | 248 | public override void Process(TagHelperContext context, TagHelperOutput output) { 249 | output.Content.AppendHtml(_contet); 250 | } 251 | } 252 | 253 | class UrlHelperFactoryMock : IUrlHelperFactory { 254 | public const string UNRESOLVABLE_ACTION = "UnresolvableAction"; 255 | 256 | public IUrlHelper GetUrlHelper(ActionContext context) { 257 | return new UrlHelperMock(); 258 | } 259 | 260 | class UrlHelperMock : IUrlHelper { 261 | 262 | public ActionContext ActionContext { 263 | get { throw new NotImplementedException(); } 264 | } 265 | 266 | public string Action(UrlActionContext actionContext) { 267 | if(actionContext.Action == UNRESOLVABLE_ACTION) 268 | return null; 269 | 270 | return $"ActionMock({actionContext.Controller}, {actionContext.Action})"; 271 | } 272 | 273 | public string Content(string contentPath) { 274 | return $"ContentMock({contentPath})"; 275 | } 276 | 277 | public bool IsLocalUrl(string url) { 278 | throw new NotImplementedException(); 279 | } 280 | 281 | public string Link(string routeName, object values) { 282 | throw new NotImplementedException(); 283 | } 284 | 285 | public string RouteUrl(UrlRouteContext routeContext) { 286 | throw new NotImplementedException(); 287 | } 288 | } 289 | } 290 | 291 | class TestStoreDatasourceTagHelper : StoreDatasourceTagHelper { 292 | 293 | protected override string FormatStoreFactory(string args) { 294 | return args; 295 | } 296 | 297 | protected override void PopulateStoreConfig(IDictionary config) { 298 | } 299 | } 300 | } 301 | 302 | } 303 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Generator/meta/IntellisenseData_15.2_spec.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | -------------------------------------------------------------------------------- /DevExtreme.AspNet.TagHelpers.Generator/meta/PropOf.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | --------------------------------------------------------------------------------