Я экспериментирую с OData с do tnet 3.1.
У меня есть SPA, который отправляет следующий URL:
https://localhost:44301/api/Client/Index?$top=10&$orderby=ClientNo%20desc,ClientLastName%20asc
Приложение получает запрос GET и контроллер однако обрабатывает его, не возвращая первые 10 записей, но все 146.
Как видите, результат равен 146.
Мой контроллер выглядит следующим образом:
using System.Linq;
using System.Threading.Tasks;
using JobsLedger.API.ControllerServices.API.App.ClientService.Interfaces;
using JobsLedger.DATA;
using JobsLedger.MODELS.API.App.Client;
using Microsoft.AspNet.OData;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace JobsLedger.API.Controllers.API.App {
[ApiController]
[Route("api/[controller]")]
//[AllowAnonymous]
[Authorize(Roles = "TenantAdmin,Admin,Employee")]
public class ClientController :ODataController // ControllerBase
{
private readonly IClientServices _clientServices;
private readonly DATAContext _context;
public ClientController(IClientServices clientServices, DATAContext context) {
_clientServices = clientServices;
_context = context;
}
// This is the default client index option.
[EnableQuery]
[HttpGet("Index", Name = "ClientIndex")]
public IActionResult Get() {
var result = _context.Clients.ToList();
return new OkObjectResult(result);
}
}
}
Я использовал настройку, как описано в статье Хасана Хабиба .
Вот мой StartUp.cs
using System;
using System.Diagnostics;
using System.Resources;
using System.Security.Claims;
using System.Text;
using JobsLedger.API.ControllerServices.API.App.ClientService;
using JobsLedger.API.ControllerServices.API.App.ClientService.Interfaces;
using JobsLedger.API.ControllerServices.API.App.JobService;
using JobsLedger.API.ControllerServices.API.App.JobService.Interfaces;
using JobsLedger.API.ControllerServices.Catalog.TenantService;
using JobsLedger.API.ControllerServices.Catalog.TenantService.Interfaces;
using JobsLedger.API.ControllerServices.Catalog.UserService;
using JobsLedger.API.ControllerServices.Catalog.UserService.Abstract;
using JobsLedger.API.ControllerServices.Common;
using JobsLedger.API.ControllerServices.Common.Interfaces;
using JobsLedger.AUTHORISATION;
using JobsLedger.AUTHORISATION.API.SessionMiddleware;
using JobsLedger.AUTHORISATION.API.SessionMiddleware.Interfaces;
using JobsLedger.AUTHORISATION.Interfaces;
using JobsLedger.CATALOG;
using JobsLedger.CATALOG.Repositories;
using JobsLedger.CATALOG.Repositories.Interfaces;
using JobsLedger.DATA;
using JobsLedger.DATA.ENTITIES;
using JobsLedger.DATA.Interfaces;
using JobsLedger.DATA.Repositories;
using JobsLedger.DATA.Repositories.Interfaces;
using JobsLedger.INITIALISATION;
using JobsLedger.INITIALISATION.CATALOG.Initialisations;
using JobsLedger.INITIALISATION.CATALOG.Initialisations.Interfaces;
using JobsLedger.INITIALISATION.DATA.Initialisations;
using JobsLedger.INITIALISATION.DATA.Initialisations.Interfaces;
using JobsLedger.INTERFACES;
using JobsLedger.TESTDATA;
using JobsLedger.TESTDATA.Interfaces;
using Microsoft.AspNet.OData.Builder;
using Microsoft.AspNet.OData.Extensions;
using Microsoft.OData.Edm;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
[assembly: NeutralResourcesLanguage("en")]
namespace JobsLedger.API {
public class Startup {
private const string SecretKey = "needtogetthisfromenvironment";
private readonly SymmetricSecurityKey
_signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey));
private readonly IWebHostEnvironment _env;
private readonly IConfiguration _configuration; // { get; }
public Startup(IWebHostEnvironment env, IConfiguration configuration) {
_env = env;
_configuration = configuration;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) {
// Add framework services.
services.AddCors();
services.AddControllers().AddNewtonsoftJson();
services.AddOData();
services.AddOptions();
services.AddDbContext<CATALOGContext>(options => options.UseLazyLoadingProxies().UseSqlServer(_configuration.GetConnectionString("CatalogConnection"), b => b.MigrationsAssembly("JobsLedger.CATALOG")));
services.AddDbContext<DATAContext>(options => options.UseLazyLoadingProxies().UseSqlServer(_configuration.GetConnectionString("TenantDbConnection"), a => a.MigrationsAssembly("JobsLedger.DATA")));
// Make authentication compulsory across the board (i.e. shut
// down EVERYTHING unless explicitly opened up).
// Use policy auth.
services.AddAuthorization(options => {
options.AddPolicy("TenantAdmin", policy => policy.RequireClaim(ClaimTypes.Role, "TenantAdmin"));
options.AddPolicy("Admin", policy => policy.RequireClaim(ClaimTypes.Role, "Admin"));
options.AddPolicy("Employee", policy => policy.RequireClaim(ClaimTypes.Role, "Employee"));
});
//services.ConfigureApplicationInjection();
//var tokenOptions = _configuration.GetSection("Authentication").Get<JwtIssuerOptions>();
var jwtAppSettingOptions = _configuration.GetSection(nameof(JwtIssuerOptions));
// Configure JwtIssuerOptions
services.Configure<JwtIssuerOptions>(options => {
options.Issuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
options.Audience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)];
options.SigningCredentials = new SigningCredentials(_signingKey, SecurityAlgorithms.HmacSha256);
});
// Get jwt options from app settings
services.AddAuthentication(x => {
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters {
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
RequireExpirationTime = true,
ValidIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)],
ValidAudience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)],
IssuerSigningKey = _signingKey,
ClockSkew = TimeSpan.Zero
};
});
services
.AddDistributedMemoryCache()
.AddSession()
// Repositories - DATA
.AddScoped<IClientDATARepository, ClientDATARepository>()
.AddScoped<ILoggingDATARepository, LoggingDATARepository>()
.AddScoped<IJobDATARepository, JobDATARepository>()
.AddScoped<IBrandDATARepository, BrandDATARepository>()
.AddScoped<ITypeDATARepository, TypeDATARepository>()
.AddScoped<IStateDATARepository, StateDATARepository>()
.AddScoped<IStatusDATARepository, StatusDATARepository>()
.AddScoped<ISuburbDATARepository, SuburbDATARepository>()
.AddScoped<ICounterDATARepository, CounterDATARepository>()
// Repositories - CATALOG
.AddScoped<ITenantCATALOGRepository, TenantCATALOGRepository>()
.AddScoped<IUserCATALOGRepository, UserCATALOGRepository>()
.AddScoped<IRoleCATALOGRepository, RoleCATALOGRepository>()
.AddScoped<ICounterCATALOGRepository, CounterCATALOGRepository>()
.AddScoped<ISuburbCATALOGRepository, SuburbCATALOGRepository>()
.AddScoped<IStateCATALOGRepository, StateCATALOGRepository>()
// Business services
// Services - API
.AddScoped<IClientServices, ClientServices>()
.AddScoped<IJobServices, JobServices>()
//Services - Catalog
.AddScoped<ITenantServices, TenantServices>()
.AddScoped<IUserServices, UserServices>()
//.AddScoped<IUserValidateService, UserValidateService>()
// Services - Shared
.AddScoped<IAddressDropdownServices, AddressDropdownServices>()
//Services - Auth
.AddScoped<ICryptoService, CryptoService>()
.AddScoped<IJwtServices, JwtServices>()
.AddScoped<IUserSession, UserSession>()
.AddScoped<ISessionServices, SessionServices>()
// CATALOG services - Initialisations.
.AddScoped<ICATALOGCounterInitialiser, CATALOGCounterInitialiser>()
.AddScoped<ICATALOGStateInitialiser, CATALOGStateInitialiser>()
.AddScoped<ICATALOGSuburbInitialiser, CATALOGSuburbInitialiser>()
.AddScoped<IRoleInitialiser, CATALOGRoleInitialiser>()
.AddScoped<ICATALOGTenantAndUserInitialisations, CATALOGTenantAndUserInitialiser>()
// DATA Services - Initialisations.
.AddScoped<IBrandInitialiser, BrandInitialiser>()
.AddScoped<IDATACounterInitialiser, DATACounterInitialiser>()
.AddScoped<IDATAStateInitialiser, DATAStateInitialiser>()
.AddScoped<IDATASuburbInitialiser, DATASuburbInitialiser>()
.AddScoped<INoteTypeInitialiser, NoteTypeInitialiser>()
.AddScoped<IStatusInitialiser, StatusInitialiser>()
.AddScoped<ITypeInitialiser, TypeInitialiser>()
.AddScoped<IVisitTypeInitialiser, VisitTypeInitialiser>()
// TESTDATA Services - Initialisations.
.AddScoped<ITESTDATAInitialisations, TESTDATAInitialisations>()
.AddScoped<ITESTDATATenantAndUserInitialisations, TESTDATATenantAndUserInitialisations>()
.AddTransient<IDATAContextFactory, DATAContextFactory>()
.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); // For getting the user.
}
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
else {
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseExceptionHandler(options => {
options.Run( async context => {
var ex = context.Features.Get<IExceptionHandlerPathFeature>();
if (ex?.Error != null) {
Debugger.Break();
}
});
});
app.UseRouting();
// global cors policy
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
app.UseAuthentication();
app.UseHttpsRedirection();
app.UseFileServer();
app.UseStaticFiles();
app.UseSession();
app.UseAuthorization();
app.UseConfigureSession();
//app.EnsureCATALOGMigrationAndInitialise();
//app.EnsureDATAMigrationsAndInitialise();
app.UseEndpoints(endpoints => {
endpoints.MapControllers();
endpoints.EnableDependencyInjection();
endpoints.Select().Filter().OrderBy().Expand().Count().MaxTop(10);
});
}
}
}
Я использую маршрутизацию конечной точки.
Мой запрос OData:
$top=10&$orderby=ClientNo desc,ClientLastName asc
Замечу, что ряд руководств по использованию OData
var result = _context.Clients.ToList();
или версия в том же духе, но в моем случае она не принимает топ-10 как запрошено.
Я понятия не имею, почему это не работает, и надеюсь, что кто-то, кто намного лучше знаком с OData, сможет обнаружить проблему ..
Кстати, запрос OData исходит от реализации Slickgrid, так что все должно быть в порядке. ..
Кто-нибудь заметит простую проблему с моей реализацией или запросом et c ..