Skip to content

Support asp.net core 3.1 #178

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Mvc.JQuery.DataTables.AspNetCore/DataTablesModelBinder.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Internal;
using System;
using System.Threading.Tasks;

Expand Down
10 changes: 9 additions & 1 deletion Mvc.JQuery.DataTables.AspNetCore/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -20,7 +23,12 @@ public static IServiceCollection AddMvcJQueryDataTables(this IServiceCollection
dataTablesViewModelType.Namespace + ".Common"),
};
services.AddSingleton(settings);
#if NETCOREAPP3_1
services.Configure<MvcRazorRuntimeCompilationOptions>(s => s.FileProviders.Add(settings.FileProvider));
#elif NETSTANDARD2_0
services.Configure<RazorViewEngineOptions>(s => s.FileProviders.Add(settings.FileProvider));
#endif

services.AddMvc(options => { options.UseHtmlEncodeModelBinding(); });

return services;
Expand All @@ -46,7 +54,7 @@ public static class MvcJQueryDataTablesExtensions
public static IApplicationBuilder UseMvcJQueryDataTables(this IApplicationBuilder app)
{
var settings = app.ApplicationServices.GetService<global::Mvc.JQuery.DataTables.Settings>();
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.");
}
Expand Down
181 changes: 181 additions & 0 deletions Mvc.JQuery.DataTables.AspNetCore/ModelBindingHelper.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Converts the provided <paramref name="value"/> to a value of <see cref="Type"/> <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The <see cref="Type"/> for conversion.</typeparam>
/// <param name="value">The value to convert."/></param>
/// <param name="culture">The <see cref="CultureInfo"/> for conversion.</param>
/// <returns>
/// The converted value or the default value of <typeparamref name="T"/> if the value could not be converted.
/// </returns>
public static T ConvertTo<T>(object value, CultureInfo culture)
{
var converted = ConvertTo(value, typeof(T), culture);
return converted == null ? default(T) : (T)converted;
}

/// <summary>
/// Converts the provided <paramref name="value"/> to a value of <see cref="Type"/> <paramref name="type"/>.
/// </summary>
/// <param name="value">The value to convert."/></param>
/// <param name="type">The <see cref="Type"/> for conversion.</param>
/// <param name="culture">The <see cref="CultureInfo"/> for conversion.</param>
/// <returns>
/// The converted value or <c>null</c> if the value could not be converted.
/// </returns>
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;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1;netstandard2.0</TargetFrameworks>
<RootNamespace>Mvc.JQuery.DataTables</RootNamespace>
<AssemblyName>Mvc.JQuery.DataTables.AspNetCore</AssemblyName>
<PackageId>Mvc.JQuery.DataTables.AspNetCore</PackageId>

<PackageVersion>1.0.0</PackageVersion>
<PackageVersion>2.0.0</PackageVersion>
<Authors>Harry McIntyre</Authors>
<Description>Popular lib for using DataTables.net with IQueryable. Install this package to use with AspNetCore</Description>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageProjectUrl>https://github.com/mcintyre321/mvc.jquery.datatables</PackageProjectUrl>
<Copyright>Harry McIntyre</Copyright>
<PackageTags>jquery datatables iqueryable razor asp mvc mvc5</PackageTags>
<PackageLicenseUrl>https://github.com/mcintyre321/mvc.jquery.datatables/blob/master/License.txt</PackageLicenseUrl> </PropertyGroup>
<PackageLicenseUrl>https://github.com/mcintyre321/mvc.jquery.datatables/blob/master/License.txt</PackageLicenseUrl>
</PropertyGroup>

<ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'">
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.*" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.*" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.0.*" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="2.0.*" />
Expand Down