diff --git a/Mvc.JQuery.DataTables.AspNetCore/DataTablesModelBinder.cs b/Mvc.JQuery.DataTables.AspNetCore/DataTablesModelBinder.cs index 8a4ab8b..81bae70 100644 --- a/Mvc.JQuery.DataTables.AspNetCore/DataTablesModelBinder.cs +++ b/Mvc.JQuery.DataTables.AspNetCore/DataTablesModelBinder.cs @@ -1,5 +1,4 @@ using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.ModelBinding.Internal; using System; using System.Threading.Tasks; diff --git a/Mvc.JQuery.DataTables.AspNetCore/Extensions.cs b/Mvc.JQuery.DataTables.AspNetCore/Extensions.cs index 073eb43..e9ef853 100644 --- a/Mvc.JQuery.DataTables.AspNetCore/Extensions.cs +++ b/Mvc.JQuery.DataTables.AspNetCore/Extensions.cs @@ -5,6 +5,9 @@ using System.Reflection; using Microsoft.AspNetCore.Mvc; using Mvc.JQuery.DataTables; +#if NETCOREAPP3_1 +using Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation; +#endif namespace Microsoft.Extensions.DependencyInjection { @@ -20,7 +23,12 @@ public static IServiceCollection AddMvcJQueryDataTables(this IServiceCollection dataTablesViewModelType.Namespace + ".Common"), }; services.AddSingleton(settings); +#if NETCOREAPP3_1 + services.Configure(s => s.FileProviders.Add(settings.FileProvider)); +#elif NETSTANDARD2_0 services.Configure(s => s.FileProviders.Add(settings.FileProvider)); +#endif + services.AddMvc(options => { options.UseHtmlEncodeModelBinding(); }); return services; @@ -46,7 +54,7 @@ public static class MvcJQueryDataTablesExtensions public static IApplicationBuilder UseMvcJQueryDataTables(this IApplicationBuilder app) { var settings = app.ApplicationServices.GetService(); - if(settings == null) + if (settings == null) { throw new InvalidOperationException("Unable to find the required services. Please add all the required services by calling 'IServiceCollection.{}' inside the call to 'ConfigureServices(...)' in the application startup code."); } diff --git a/Mvc.JQuery.DataTables.AspNetCore/ModelBindingHelper.cs b/Mvc.JQuery.DataTables.AspNetCore/ModelBindingHelper.cs new file mode 100644 index 0000000..aa4637d --- /dev/null +++ b/Mvc.JQuery.DataTables.AspNetCore/ModelBindingHelper.cs @@ -0,0 +1,181 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.ComponentModel; +using System.Globalization; +using System.Reflection; +using System.Runtime.ExceptionServices; + +namespace Mvc.JQuery.DataTables +{ + internal static class ModelBindingHelper + { + /// + /// Converts the provided to a value of . + /// + /// The for conversion. + /// The value to convert."/> + /// The for conversion. + /// + /// The converted value or the default value of if the value could not be converted. + /// + public static T ConvertTo(object value, CultureInfo culture) + { + var converted = ConvertTo(value, typeof(T), culture); + return converted == null ? default(T) : (T)converted; + } + + /// + /// Converts the provided to a value of . + /// + /// The value to convert."/> + /// The for conversion. + /// The for conversion. + /// + /// The converted value or null if the value could not be converted. + /// + public static object ConvertTo(object value, Type type, CultureInfo culture) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (value == null) + { + // For value types, treat null values as though they were the default value for the type. + return type.GetTypeInfo().IsValueType ? Activator.CreateInstance(type) : null; + } + + if (type.IsAssignableFrom(value.GetType())) + { + return value; + } + + var cultureToUse = culture ?? CultureInfo.InvariantCulture; + return UnwrapPossibleArrayType(value, type, cultureToUse); + } + + private static object UnwrapPossibleArrayType(object value, Type destinationType, CultureInfo culture) + { + // array conversion results in four cases, as below + var valueAsArray = value as Array; + if (destinationType.IsArray) + { + var destinationElementType = destinationType.GetElementType(); + if (valueAsArray != null) + { + // case 1: both destination + source type are arrays, so convert each element + var converted = (IList)Array.CreateInstance(destinationElementType, valueAsArray.Length); + for (var i = 0; i < valueAsArray.Length; i++) + { + converted[i] = ConvertSimpleType(valueAsArray.GetValue(i), destinationElementType, culture); + } + return converted; + } + else + { + // case 2: destination type is array but source is single element, so wrap element in + // array + convert + var element = ConvertSimpleType(value, destinationElementType, culture); + var converted = (IList)Array.CreateInstance(destinationElementType, 1); + converted[0] = element; + return converted; + } + } + else if (valueAsArray != null) + { + // case 3: destination type is single element but source is array, so extract first element + convert + if (valueAsArray.Length > 0) + { + value = valueAsArray.GetValue(0); + return ConvertSimpleType(value, destinationType, culture); + } + else + { + // case 3(a): source is empty array, so can't perform conversion + return null; + } + } + + // case 4: both destination + source type are single elements, so convert + return ConvertSimpleType(value, destinationType, culture); + } + + private static object ConvertSimpleType(object value, Type destinationType, CultureInfo culture) + { + if (value == null || destinationType.IsAssignableFrom(value.GetType())) + { + return value; + } + + // In case of a Nullable object, we try again with its underlying type. + destinationType = UnwrapNullableType(destinationType); + + // if this is a user-input value but the user didn't type anything, return no value + if (value is string valueAsString && string.IsNullOrWhiteSpace(valueAsString)) + { + return null; + } + + var converter = TypeDescriptor.GetConverter(destinationType); + var canConvertFrom = converter.CanConvertFrom(value.GetType()); + if (!canConvertFrom) + { + converter = TypeDescriptor.GetConverter(value.GetType()); + } + if (!(canConvertFrom || converter.CanConvertTo(destinationType))) + { + // EnumConverter cannot convert integer, so we verify manually + if (destinationType.GetTypeInfo().IsEnum && + (value is int || + value is uint || + value is long || + value is ulong || + value is short || + value is ushort || + value is byte || + value is sbyte)) + { + return Enum.ToObject(destinationType, value); + } + + throw new InvalidOperationException($"NoConverterExists: {value.GetType()} -> {destinationType}"); + } + + try + { + return canConvertFrom + ? converter.ConvertFrom(null, culture, value) + : converter.ConvertTo(null, culture, value, destinationType); + } + catch (FormatException) + { + throw; + } + catch (Exception ex) + { + if (ex.InnerException == null) + { + throw; + } + else + { + // TypeConverter throws System.Exception wrapping the FormatException, + // so we throw the inner exception. + ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); + + // This code is never reached because the previous line will always throw. + throw; + } + } + } + + private static Type UnwrapNullableType(Type destinationType) + { + return Nullable.GetUnderlyingType(destinationType) ?? destinationType; + } + } +} \ No newline at end of file diff --git a/Mvc.JQuery.DataTables.AspNetCore/Mvc.JQuery.DataTables.AspNetCore.csproj b/Mvc.JQuery.DataTables.AspNetCore/Mvc.JQuery.DataTables.AspNetCore.csproj index f741182..523ea5d 100644 --- a/Mvc.JQuery.DataTables.AspNetCore/Mvc.JQuery.DataTables.AspNetCore.csproj +++ b/Mvc.JQuery.DataTables.AspNetCore/Mvc.JQuery.DataTables.AspNetCore.csproj @@ -1,21 +1,27 @@  - netstandard2.0 + netcoreapp3.1;netstandard2.0 Mvc.JQuery.DataTables Mvc.JQuery.DataTables.AspNetCore Mvc.JQuery.DataTables.AspNetCore - 1.0.0 + 2.0.0 Harry McIntyre Popular lib for using DataTables.net with IQueryable. Install this package to use with AspNetCore false https://github.com/mcintyre321/mvc.jquery.datatables Harry McIntyre jquery datatables iqueryable razor asp mvc mvc5 - https://github.com/mcintyre321/mvc.jquery.datatables/blob/master/License.txt + https://github.com/mcintyre321/mvc.jquery.datatables/blob/master/License.txt + - + + + + + + diff --git a/Mvc.JQuery.DataTables.Common/DataTablesFiltering.cs b/Mvc.JQuery.DataTables.Common/DataTablesFiltering.cs index 1a9cb4f..0d2ce61 100644 --- a/Mvc.JQuery.DataTables.Common/DataTablesFiltering.cs +++ b/Mvc.JQuery.DataTables.Common/DataTablesFiltering.cs @@ -7,7 +7,7 @@ namespace Mvc.JQuery.DataTables { - internal class DataTablesFiltering + public class DataTablesFiltering { public IQueryable ApplyFiltersAndSort(DataTablesParam dtParameters, IQueryable data, DataTablesPropertyInfo[] columns) { @@ -30,7 +30,11 @@ public IQueryable ApplyFiltersAndSort(DataTablesParam dtParameters, IQuery } } var values = parts.Where(p => p != null); - data = data.Where(string.Join(" or ", values), parameters.ToArray()); + var filterClause = string.Join(" or ", values); + if (string.IsNullOrWhiteSpace(filterClause) == false) + { + data = data.Where(filterClause, parameters.ToArray()); + } } for (int i = 0; i < dtParameters.sSearchValues.Count; i++) { @@ -114,7 +118,7 @@ private static ReturnedFilteredQueryForType Guard(Func(GuardedFilter filter) { - Filters.Add(Guard(arg => arg is T, filter)); + Filters.Add(Guard(arg => arg.Type == typeof(T), filter)); } private static string GetFilterClause(string query, DataTablesPropertyInfo column, List parametersForLinqQuery) diff --git a/Mvc.JQuery.DataTables.Common/Processing/TypeFilters.cs b/Mvc.JQuery.DataTables.Common/Processing/TypeFilters.cs index c230ff7..0ba2111 100644 --- a/Mvc.JQuery.DataTables.Common/Processing/TypeFilters.cs +++ b/Mvc.JQuery.DataTables.Common/Processing/TypeFilters.cs @@ -111,12 +111,14 @@ public static string DateTimeOffsetFilter(string query, string columnname, DataT DateTimeOffset start, end; if (DateTimeOffset.TryParse(parts[0] ?? "", out start)) { + start = start.ToUniversalTime(); filterString = columnname + " >= @" + parametersForLinqQuery.Count; parametersForLinqQuery.Add(start); } if (DateTimeOffset.TryParse(parts[1] ?? "", out end)) { + end = end.ToUniversalTime(); filterString = (filterString == null ? null : filterString + " and ") + columnname + " <= @" + parametersForLinqQuery.Count; parametersForLinqQuery.Add(end); } @@ -161,12 +163,14 @@ public static string DateTimeFilter(string query, string columnname, DataTablesP DateTime start, end; if (DateTime.TryParse(parts[0] ?? "", out start)) { + start = start.ToUniversalTime(); filterString = columnname + " >= @" + parametersForLinqQuery.Count; parametersForLinqQuery.Add(start); } if (DateTime.TryParse(parts[1] ?? "", out end)) { + end = end.ToUniversalTime(); filterString = (filterString == null ? null : filterString + " and ") + columnname + " <= @" + parametersForLinqQuery.Count; parametersForLinqQuery.Add(end); } @@ -247,11 +251,17 @@ public static string StringFilter(string q, string columnname, DataTablesPropert public static string EnumFilter(string q, string columnname, DataTablesPropertyInfo propertyInfo, List parametersForLinqQuery) { - - if (q.StartsWith("^")) q = q.Substring(1); - if (q.EndsWith("$")) q = q.Substring(0, q.Length - 1); - parametersForLinqQuery.Add(ParseValue(q, propertyInfo.Type)); - return columnname + " == @" + (parametersForLinqQuery.Count - 1); + try + { + if (q.StartsWith("^")) q = q.Substring(1); + if (q.EndsWith("$")) q = q.Substring(0, q.Length - 1); + parametersForLinqQuery.Add(ParseValue(q, propertyInfo.Type)); + return columnname + " == @" + (parametersForLinqQuery.Count - 1); + } + catch (Exception) + { + return null; + } } } -} \ No newline at end of file +}