DevExpress routing appears to break with dotnet core 3.1

395 views Asked by At

I have the following DxDatagrid block in my dotnet core webapp index.cshtml page:

@(Html.DevExtreme().DataGrid<UserModel>()
    .ID("grid-container")
    .ShowBorders(true)
    .DataSource(d => d.Mvc().Controller("UserSearch").LoadAction("Get").Key("UserId"))
    .Selection(s => s
        .Mode(SelectionMode.Multiple)
        .SelectAllMode(SelectAllMode.Page)
        )

With this code in place and using dotnet core 2.2 the datasource makes a call to:

http://localhost:5000/api/UserSearch/Get?skip=0&take=10&requireTotalCount=true&_=1600859370033

Having updated to dotnet core 3.1 and updated the DevExpress references in the csproj and _Layout.cshtml files, the routing now attempts to call:

http://localhost:5000/?skip=0&take=10&requireTotalCount=true&_=1600859693687

The startup.cs is this:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using AccessUsers.Middleware;
using AccessUsers.Models;
using Microsoft.AspNetCore.HttpOverrides;

namespace WebAppTest
{
    public class Startup
    {
        private readonly IConfiguration _config;
        private readonly AppSettings _appSettings;

        public Startup(IConfiguration config)
        {
            _config = config;
            _appSettings = _config.Get<AppSettings>();
        }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => false;
                // options.MinimumSameSitePolicy = SameSiteMode.None;

                options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
                options.OnAppendCookie = cookieContext => cookieContext.CookieOptions.SameSite = SameSiteMode.Unspecified;
                options.OnDeleteCookie = cookieContext => cookieContext.CookieOptions.SameSite = SameSiteMode.Unspecified;
            });

            services.Configure<AppSettings>(_config);

            services.AddSingleton<APIService>();
            services.AddSingleton<UserService>();
            services.AddSingleton<ShipToService>();

            services.AddApplicationInsightsTelemetry();

            services.AddLocalization(options => options.ResourcesPath = "Resources");
            services.AddSession();
            services.AddMemoryCache();
            
            services.AddRazorPages().AddNewtonsoftJson(options => {
                options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver();
                options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
            }).AddXmlSerializerFormatters();

            services.UseOpenIDConnectMiddleware(new OpenIDConnectMiddlewareOptions
            {
                BaseUrl = _appSettings.API.BaseUrl,
                AppName = _appSettings.AppName,
                ClientId = _appSettings.API.ClientId,
                ClientSecret = _appSettings.API.ClientSecret,
                Secure = !_appSettings.Local
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseForwardedHeaders(new ForwardedHeadersOptions
            {
                ForwardedHeaders = ForwardedHeaders.XForwardedProto
            });

            if (_appSettings.Local)
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                app.UseHsts();
                app.UseGlobalLoginMiddleware();
                app.UseHttpsRedirection();
            }

            app.UseStaticFiles();
            app.UseCookiePolicy();

            app.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();

            app.UseSession();

            app.UseEndpoints(endpoints => {
                endpoints.MapRazorPages();
            });

            CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.AllCultures);
            string location = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location);
            var supportedCultures = allCultures.Where(c => Directory.Exists(Path.Combine(location, c.Name)) && c.LCID != 127).ToList();

            app.UseRequestLocalization(new RequestLocalizationOptions
            {
                DefaultRequestCulture = new RequestCulture("en-US"),
                SupportedCultures = supportedCultures,
                SupportedUICultures = supportedCultures
            });
        }
    }
}

The csproj contains this:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <AspNetCoreHostingModel>OutOfProcess</AspNetCoreHostingModel>
  </PropertyGroup>


  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="3.1.6" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.7" />
    <PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="6.7.1" />

    <PackageReference Include="DevExtreme.AspNet.Data" Version="2.7.1" />
    <PackageReference Include="DevExtreme.AspNet.Core" Version="20.1.7" />

    <PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.7.1" />
    <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.7.1" />

    <PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.14.0" />

    <PackageReference Include="Amazon.Lambda.AspNetCoreServer" Version="5.0.0" />
  </ItemGroup>

</Project>

The controller.cs contains this:

using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using AccessUsers.Models;
using DevExtreme.AspNet.Data;
using DevExtreme.AspNet.Mvc;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;

namespace WebAppTest.Pages
{
    [Route("api/[controller]/[action]")]
    public class UserSearchController : Controller
    {
        private readonly UserService _userService;

        public UserSearchController(UserService userService)
        {
            _userService = userService;
        }

        [HttpGet]
        public object Get(DataSourceLoadOptions loadOptions)
        {
            var result =  DataSourceLoader.Load(GetProfiles(user:new UserModel(),useDummyData: true), loadOptions);

            return result;
        }

The _Layout.cshtml contains this:

<script src="https://cdn3.devexpress.com/jslib/20.1.7/js/dx.all.js" integrity="sha384-LAn+t9UxSqkm8biNuoUbJcohKoYmbiFRfVLERIJ4I3RyEpAIBizEcIztuXPG9Cqg sha512-OAjfsw+eXv345AD9H6kDJLChXetpJD6ChGgDvjVIEumiHYulOLXIO/Do5gxljW2GUgpObic42JyS8a0wZqb1Fw==" crossorigin="anonymous"></script>
<script src="https://cdn3.devexpress.com/jslib/20.1.7/js/dx.aspnet.mvc.js" integrity="sha384-5rtF4jUX5Hez5YwkW7PHC/0XplJQS26qVUCfec8fBX0IkoR1y35EXHkZDbgeMh3x sha512-0eJebJTnN45FCtUOrVqxk5p73OMWsx94vLQpnlRtDp/CKbssiUR0j0os+0y01fvzDtdtnEKSeau32g30fgtrYQ==" crossorigin="anonymous"></script>

As specified here: https://js.devexpress.com/Documentation/Guide/Common/Distribution_Channels/CDN/

I'm sure it's the change to dotnet core 3.1 which caused the routing to break because the functionality of the application hasn't changed, but I can't see what specifically breaks it.

1

There are 1 answers

0
Panagiotis Kanavos On

Startup.ConfigureServices doesn't add support for controllers, only for Razor Pages with :

services.AddRazorPages().AddNewtonsoftJson(options => {
        ...
        }).AddXmlSerializerFormatters();

From the Remarks in the method's documentation

This method configures the MVC services for the commonly used features for pages.

To add services for controllers for APIs call AddControllers(IServiceCollection).

The controller is never registered right now, so the code that tries to generate the action URL

.DataSource(d => d.Mvc().Controller("UserSearch").LoadAction("Get")

fails to find anything and returns an empty string.

To fix this, add controller support :

services.AddControllers().AddNewtonsoftJson(options => {
        ...
        }).AddXmlSerializerFormatters();
services.AddRazorPages();

Controllers should be added in the endpoint routing code in Configure as well, with MapControllers :

app.UseEndpoints(endpoints => {
    endpoints.MapRazorPages();
    endpoints.MapControllers();
});