From 4cf039d8a83cce0116876e7cc24b9ff4a734624e Mon Sep 17 00:00:00 2001 From: Dennis Miasoutov Date: Fri, 5 Jun 2020 22:30:09 -0400 Subject: [PATCH] Upgrading to .NET Core 3.1. (#2) * Upgrading to .NET Core 3.1. * Adding basic infra pieces. * Added several unit tests. * Update next version. * Adding initial readme. * Decreased code coverage. --- .azure-pipelines/azure-pipelines.yml | 167 ++++++++++++++++++ .azure-pipelines/pack-template.yml | 13 ++ .devcontainer/devcontainer.json | 50 ++++++ .devcontainer/docker-compose.yml | 44 +++++ .gitignore | 25 +-- Directory.Build.props | 11 ++ Directory.Build.targets | 14 ++ GitVersion.yml | 30 ++++ README.md | 14 ++ .../StyleCop.ruleset => StyleCop.ruleset | 2 +- examples/WebApp.EF/Program.cs | 1 + .../WebApp.EF/Properties/launchSettings.json | 5 +- examples/WebApp.EF/Startup.cs | 17 +- examples/WebApp.EF/WebApp.EF.csproj | 4 +- examples/WebApp/Program.cs | 2 +- examples/WebApp/Startup.cs | 24 +-- examples/WebApp/Swagger.cs | 30 ---- examples/WebApp/WebApp.csproj | 13 +- icon.png | Bin 0 -> 6156 bytes odata-api.sln | 13 +- .../CrudRepositoryProxy.cs | 11 +- .../DocumentCollectionCrudRepository.cs | 11 +- .../DocumentCrudRepository.cs | 11 +- .../DocumentRepositoryFactory.cs | 27 ++- .../Extensions.cs | 18 +- ...oding.AspNetCore.ODataApi.EasyDocDb.csproj | 26 +-- .../ODataEasyDocDbOptions.cs | 11 ++ .../CrudRepository.cs | 54 ------ .../DbSetRepository.cs | 8 +- .../Extensions.cs | 37 ++-- ...AspNetCore.ODataApi.EntityFramework.csproj | 22 +-- .../CrudRepository.cs | 22 +-- .../Extensions.cs | 22 +-- .../IndexesExtensions.cs | 6 - ...ocoding.AspNetCore.ODataApi.MongoDb.csproj | 22 +-- .../Core/CrudControllerFeatureProvider.cs | 10 +- .../Core/CrudControllerNameConvention.cs | 9 +- .../Core/DefaultEntityKeyAccessor.cs | 15 +- .../Core/EntityMetadata.cs | 29 --- .../Core/IModelMetadataProvider.cs | 7 +- .../Core/ODataApiBuilder.cs | 29 +-- .../CrudController.cs | 9 +- .../EdmExtensions.cs | 30 ++++ .../ICrudRepository.cs | 7 +- .../IEntityKeyAccessor.cs | 7 + .../IEntityKeyAccossor.cs | 11 -- .../IODataApiBuilder.cs | 5 - .../Mocoding.AspNetCore.ODataApi.csproj | 22 +-- .../ODataApiExtensions.cs | 48 +++-- .../ODataApiOptions.cs | 20 ++- src/ProjectBuildProperties.targets | 31 ++++ .../CrudControllerTests.cs | 134 ++++++++++++++ .../Factories/EasyDocDbWebAppFactory.cs | 19 ++ .../Factories/EntityFrameworkWebAppFactory.cs | 19 ++ .../Factories/Factory.cs | 20 +++ .../Factories/Startup.cs | 22 +++ .../Integration/EasyDocDbExtensionsTests.cs | 26 +++ .../Integration/ODataApiExtensionsTests.cs | 25 +++ .../Mocoding.AspNetCore.ODataApi.Tests.csproj | 40 +++++ 59 files changed, 943 insertions(+), 438 deletions(-) create mode 100644 .azure-pipelines/azure-pipelines.yml create mode 100644 .azure-pipelines/pack-template.yml create mode 100644 .devcontainer/devcontainer.json create mode 100644 .devcontainer/docker-compose.yml create mode 100644 Directory.Build.props create mode 100644 Directory.Build.targets create mode 100644 GitVersion.yml create mode 100644 README.md rename _stylecop/StyleCop.ruleset => StyleCop.ruleset (95%) delete mode 100644 examples/WebApp/Swagger.cs create mode 100644 icon.png create mode 100644 src/Mocoding.AspNetCore.ODataApi.EasyDocDb/ODataEasyDocDbOptions.cs delete mode 100644 src/Mocoding.AspNetCore.ODataApi.EntityFramework/CrudRepository.cs delete mode 100644 src/Mocoding.AspNetCore.ODataApi/Core/EntityMetadata.cs create mode 100644 src/Mocoding.AspNetCore.ODataApi/EdmExtensions.cs create mode 100644 src/Mocoding.AspNetCore.ODataApi/IEntityKeyAccessor.cs delete mode 100644 src/Mocoding.AspNetCore.ODataApi/IEntityKeyAccossor.cs create mode 100644 src/ProjectBuildProperties.targets create mode 100644 test/Mocoding.AspNetCore.ODataApi.Tests/CrudControllerTests.cs create mode 100644 test/Mocoding.AspNetCore.ODataApi.Tests/Factories/EasyDocDbWebAppFactory.cs create mode 100644 test/Mocoding.AspNetCore.ODataApi.Tests/Factories/EntityFrameworkWebAppFactory.cs create mode 100644 test/Mocoding.AspNetCore.ODataApi.Tests/Factories/Factory.cs create mode 100644 test/Mocoding.AspNetCore.ODataApi.Tests/Factories/Startup.cs create mode 100644 test/Mocoding.AspNetCore.ODataApi.Tests/Integration/EasyDocDbExtensionsTests.cs create mode 100644 test/Mocoding.AspNetCore.ODataApi.Tests/Integration/ODataApiExtensionsTests.cs create mode 100644 test/Mocoding.AspNetCore.ODataApi.Tests/Mocoding.AspNetCore.ODataApi.Tests.csproj diff --git a/.azure-pipelines/azure-pipelines.yml b/.azure-pipelines/azure-pipelines.yml new file mode 100644 index 0000000..036e6db --- /dev/null +++ b/.azure-pipelines/azure-pipelines.yml @@ -0,0 +1,167 @@ +# ASP.NET Core +# Build and test ASP.NET Core projects targeting .NET Core. +# Add steps that run tests, create a NuGet package, deploy, and more: +# https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core + +# name: $(majorVersion).$(minorVersion).$(patchVersion)$(channelVersion)$(buildVersion)$(Rev:.r) + +trigger: + branches: + include: + - master + tags: + include: + - v* + +pool: + vmImage: "ubuntu-18.04" + +variables: + buildConfiguration: "Release" + projectName: "Mocoding.AspNetCore.ODataApi" + solutionFile: "odata-api.sln" + oDataApi: $(projectName) + oDataApiEasyDocDb: $(projectName).EasyDocDb + oDataApiEntityFramework: $(projectName).EntityFramework + oDataApiMongoDb: $(projectName).MongoDb + +stages: + - stage: Build + pool: + vmImage: "ubuntu-18.04" + jobs: + - job: Package + steps: + - task: UseGitVersion@5 + displayName: "Git Version" + inputs: + versionSpec: "5.0.0" + useConfigFile: true + configFilePath: "GitVersion.yml" + + - task: UseDotNet@2 + displayName: 'Install .NET Core sdk' + inputs: + packageType: sdk + version: 2.0.0 + installationPath: $(Agent.ToolsDirectory)/dotnet + + - task: UseDotNet@2 + displayName: 'Install .NET Core sdk' + inputs: + packageType: sdk + version: 3.1.300 + installationPath: $(Agent.ToolsDirectory)/dotnet + + - task: DotNetCoreCLI@2 + displayName: "dotnet restore" + inputs: + command: restore + projects: $(solutionFile) + + - task: SonarCloudPrepare@1 + displayName: "Sonarcloud - Prepare" + inputs: + SonarCloud: 'sonarcloud' + organization: 'mocoding' + scannerMode: 'MSBuild' + projectKey: 'mocoding-software_odata-api' + extraProperties: 'sonar.cs.opencover.reportsPaths=$(Agent.TempDirectory)/coverage.opencover.xml' + + - task: DotNetCoreCLI@2 + displayName: "dotnet build" + inputs: + command: build + projects: $(solutionFile) + + - task: DotNetCoreCLI@2 + displayName: "dotnet test" + inputs: + command: test + projects: 'test/**/*.Tests.csproj' + arguments: "--no-build /p:SkipCodeCoverageReport=true /p:Threshold=33 /p:CoverletOutput=$(Agent.TempDirectory)/" + + - task: SonarCloudAnalyze@1 + displayName: "Sonarcloud Analyze" + + - task: SonarCloudPublish@1 + displayName: "Sonarcloud Publish" + inputs: + pollingTimeoutSec: '300' + + - task: PublishCodeCoverageResults@1 + displayName: "Public Code Coverage" + inputs: + codeCoverageTool: "Cobertura" + summaryFileLocation: "$(Agent.TempDirectory)/coverage.cobertura.xml" + condition: succeededOrFailed() + + - template: pack-template.yml + parameters: + project: $(oDataApi) + + - template: pack-template.yml + parameters: + project: $(oDataApiEasyDocDb) + + - template: pack-template.yml + parameters: + project: $(oDataApiEntityFramework) + + - template: pack-template.yml + parameters: + project: $(oDataApiMongoDb) + + - task: PublishBuildArtifacts@1 + displayName: "Publish Artifact: nupkg" + inputs: + PathtoPublish: "$(Build.StagingDirectory)" + ArtifactName: nupkg + - stage: Deploy + dependsOn: Build + condition: and(succeeded(), contains(variables['Build.Reason'], 'PullRequest')) + pool: + vmImage: "ubuntu-18.04" + jobs: + - deployment: DevBuild + environment: "dev-builds" + strategy: + runOnce: + deploy: + steps: + - task: NuGetCommand@2 + displayName: "Publish to nuget (dev-builds)" + inputs: + command: 'push' + packagesToPush: '$(Pipeline.Workspace)/**/*.nupkg;' + nuGetFeedType: 'internal' + publishVstsFeed: 'da7703d4-fb22-4933-b869-83f4264b7b84/e1336e71-3540-4a0c-830c-639112685b07' + allowPackageConflicts: true + - stage: Release + dependsOn: Build + condition: and(succeeded(), contains(variables['Build.SourceBranch'], 'tags/v')) + pool: + vmImage: "ubuntu-18.04" + jobs: + - deployment: Public + environment: "public" + strategy: + runOnce: + deploy: + steps: + - task: NuGetCommand@2 + displayName: "Publish" + inputs: + command: 'push' + packagesToPush: '$(Pipeline.Workspace)/**/*.nupkg;' + nuGetFeedType: 'external' + publishFeedCredentials: 'public-nuget' + - task: GitHubRelease@1 + displayName: 'Update GitHub release' + inputs: + gitHubConnection: 'mocoding-software' + repositoryName: 'mocoding-software/odata-api' + action: edit + tag: 'v$(Build.BuildNumber)' + assets: '$(Pipeline.Workspace)/**/*.nupkg' + assetUploadMode: replace diff --git a/.azure-pipelines/pack-template.yml b/.azure-pipelines/pack-template.yml new file mode 100644 index 0000000..d827ef7 --- /dev/null +++ b/.azure-pipelines/pack-template.yml @@ -0,0 +1,13 @@ +parameters: + - name: project + type: string + default: "" +steps: + - task: DotNetCoreCLI@2 + displayName: "dotnet pack ${{ parameters.project }}" + inputs: + command: pack + packagesToPack: "src/${{ parameters.project }}/${{ parameters.project }}.csproj" + configuration: $(buildConfiguration) + packDirectory: "$(Build.StagingDirectory)/${{ parameters.project }}" + buildProperties: "Version=$(Build.BuildNumber)" diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..29effd7 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,50 @@ +// If you want to run as a non-root user in the container, see .devcontainer/docker-compose.yml. +{ + "name": "Existing Docker Compose (Extend)", + + // Update the 'dockerComposeFile' list if you have more compose files or use different names. + // The .devcontainer/docker-compose.yml file contains any overrides you need/want to make. + "dockerComposeFile": [ + "docker-compose.yml" + ], + + // The 'service' property is the name of the service for the container that VS Code should + // use. Update this value and .devcontainer/docker-compose.yml to the real service name. + "service": "devbox", + + // The optional 'workspaceFolder' property is the path VS Code should open by default when + // connected. This is typically a file mount in .devcontainer/docker-compose.yml + "workspaceFolder": "/workspace", + + // Use 'settings' to set *default* container specific settings.json values on container create. + // You can edit these settings after create using File > Preferences > Settings > Remote. + "settings": { + // This will ignore your local shell user setting for Linux since shells like zsh are typically + // not in base container images. You can also update this to an specific shell to ensure VS Code + // uses the right one for terminals and tasks. For example, /bin/bash (or /bin/ash for Alpine). + "terminal.integrated.shell.linux": null + }, + + // Uncomment the next line to have VS Code connect as an existing non-root user in the container. See + // https://aka.ms/vscode-remote/containers/non-root for details on adding a non-root user if none exist. + // "remoteUser": "vscode", + + // Uncomment the next line if you want start specific services in your Docker Compose config. + // "runServices": [], + + // Uncomment the next line if you want to keep your containers running after VS Code shuts down. + // "shutdownAction": "none", + + // Uncomment the next line to run commands after the container is created - for example installing git. + "postCreateCommand": "apt-get update && apt-get install -y git", + + // Add the IDs of extensions you want installed when the container is created in the array below. + "extensions": [ + "ms-vscode.csharp", + "fudge.auto-using", + "formulahendry.dotnet-test-explorer", + "tintoy.msbuild-project-tools", + "eamodio.gitlens", + "christian-kohler.path-intellisense" + ] +} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 0000000..f4a46f6 --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,44 @@ +#------------------------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. +#------------------------------------------------------------------------------------------------------------- + +version: '2.4' +services: + # Update this to the name of the service you want to work with in your docker-compose.yml file + devbox: + image: mcr.microsoft.com/dotnet/core/sdk:3.1.300 + # You may want to add a non-root user to your Dockerfile and uncomment the line below + # to cause all processes to run as this user. Once present, you can also simply + # use the "remoteUser" property in devcontainer.json if you just want VS Code and + # its sub-processes (terminals, tasks, debugging) to execute as the user. On Linux, + # you may need to ensure the UID and GID of the container user you create matches your + # local user. See https://aka.ms/vscode-remote/containers/non-root for details. + # user: vscode + + # Uncomment if you want to add a different Dockerfile in the .devcontainer folder + # build: + # context: . + # dockerfile: Dockerfile + + # Uncomment if you want to expose any additional ports. The snippet below exposes port 3000. + ports: + - 5001:5001 + - 5002:5002 + + volumes: + # Update this to wherever you want VS Code to mount the folder of your project + - ..:/workspace + + # Uncomment the next line to use Docker from inside the container. See https://aka.ms/vscode-remote/samples/docker-in-docker-compose for details. + # - /var/run/docker.sock:/var/run/docker.sock + + # Uncomment the next four lines if you will use a ptrace-based debugger like C++, Go, and Rust. + # cap_add: + # - SYS_PTRACE + # security_opt: + # - seccomp:unconfined + + # Overrides default command so things don't shut down after the process ends. + command: /bin/sh -c "while sleep 1000; do :; done" + diff --git a/.gitignore b/.gitignore index 44f26b6..978460e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,7 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -# User-specific files -*.suo -*.user -*.sln.docstates *.lock.json -.vs - -src/Portal/dist -src/Portal/.tmp -src/Portal/bin -src/Portal/obj -src/Portal.VsoPlugin/bin -src/Portal.VsoPlugin/obj -node_modules -src/Portal/wwwroot/lib -Debug -Views -data -test_data +*.user bin obj +.vs +.codecov +coverage.*.xml diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..a9db951 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,11 @@ + + + + false + cobertura,opencover + [*.ODataApi]*,[*.ODataApi.*]* + true + line + total + + diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 0000000..7db3cac --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,14 @@ + + + + + all + + + + + + + + + diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 0000000..7224e93 --- /dev/null +++ b/GitVersion.yml @@ -0,0 +1,30 @@ +assembly-versioning-scheme: Major +mode: ContinuousDeployment +next-version: 3.0.0 +increment: Patch +legacy-semver-padding: 1 +build-metadata-padding: 1 +commits-since-version-source-padding: 1 +continuous-delivery-fallback-tag: 'ci' +branches: + master: + regex: master + mode: ContinuousDeployment + tag: '' + increment: inherit + prevent-increment-of-merged-branch-version: true + tag-number-pattern: '[/-](?\d+)[-/]' + pull-request: + regex: (pull|pull\-requests|pr)[/-] + mode: ContinuousDeployment + tag: "dev" + increment: Patch + tag-number-pattern: '[/-](?\d+)[-/]' + develop: + regex: (!master)? + mode: ContinuousDeployment + tag: useBranchName + increment: Patch +ignore: + sha: [] +merge-message-formats: {} diff --git a/README.md b/README.md new file mode 100644 index 0000000..f6df786 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# Mocoding Stack - OData API + +[![Nuget](https://img.shields.io/nuget/vpre/Mocoding.AspNetCore.ODataApi)](https://www.nuget.org/packages/Mocoding.AspNetCore.ODataApi) +[![Build Status][azure-pipelines]][azure-pipelines-url] +[![Coverage][sonar-coverage]][sonar-url] +![Nuget Downloads](https://img.shields.io/nuget/dt/Mocoding.AspNetCore.ODataApi) + + +[azure-pipelines]: https://dev.azure.com/mocoding/GitHub/_apis/build/status/mocoding-software.odata-api?branchName=master +[azure-pipelines-url]: https://dev.azure.com/mocoding/GitHub/_build/latest?definitionId=115&branchName=master + +[sonar-url]: https://sonarcloud.io/dashboard?id=mocoding-software_odata-api + +[sonar-coverage]: https://sonarcloud.io/api/project_badges/measure?project=mocoding-software_odata-api&metric=coverage diff --git a/_stylecop/StyleCop.ruleset b/StyleCop.ruleset similarity index 95% rename from _stylecop/StyleCop.ruleset rename to StyleCop.ruleset index eae2d74..31a6bd2 100644 --- a/_stylecop/StyleCop.ruleset +++ b/StyleCop.ruleset @@ -16,7 +16,7 @@ - + \ No newline at end of file diff --git a/examples/WebApp.EF/Program.cs b/examples/WebApp.EF/Program.cs index c140379..b2cf999 100644 --- a/examples/WebApp.EF/Program.cs +++ b/examples/WebApp.EF/Program.cs @@ -19,6 +19,7 @@ public static void Main(string[] args) public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) + .UseUrls("http://localhost:5002") .UseStartup(); } } diff --git a/examples/WebApp.EF/Properties/launchSettings.json b/examples/WebApp.EF/Properties/launchSettings.json index dac0716..9badb54 100644 --- a/examples/WebApp.EF/Properties/launchSettings.json +++ b/examples/WebApp.EF/Properties/launchSettings.json @@ -1,11 +1,10 @@ { "profiles": { "WebApp.EF": { - "commandName": "Project", - "applicationUrl": "https://localhost:5001;http://localhost:5000", + "commandName": "Project", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } -} \ No newline at end of file +} diff --git a/examples/WebApp.EF/Startup.cs b/examples/WebApp.EF/Startup.cs index 99f5b08..577bfd5 100644 --- a/examples/WebApp.EF/Startup.cs +++ b/examples/WebApp.EF/Startup.cs @@ -1,15 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Mocoding.AspNetCore.ODataApi; using Mocoding.AspNetCore.ODataApi.EntityFramework; -using Newtonsoft.Json.Serialization; using WebApp.EF.Models; namespace WebApp.EF @@ -20,17 +14,14 @@ public class Startup // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { - services.AddDbContext(options => + services.AddEntityFrameworkGenericRepository(options => options.UseSqlServer("Server=.;Database=EmDb;User=sa;Password=")); services .AddMvcCore() - .AddJsonFormatters(settings => settings.ContractResolver = new CamelCasePropertyNamesContractResolver()) .AddApiExplorer() .AddODataApi() - .AddEntityFramework(); - - + .AddResources(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -41,7 +32,7 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env) app.UseDeveloperExceptionPage(); } - app.UseMvc(builder => builder.UseOData(app)); + app.UseODataApi(); } } } diff --git a/examples/WebApp.EF/WebApp.EF.csproj b/examples/WebApp.EF/WebApp.EF.csproj index b7e504a..d60dca0 100644 --- a/examples/WebApp.EF/WebApp.EF.csproj +++ b/examples/WebApp.EF/WebApp.EF.csproj @@ -1,7 +1,7 @@ - netcoreapp2.1 + netcoreapp3.1 @@ -10,7 +10,7 @@ - + diff --git a/examples/WebApp/Program.cs b/examples/WebApp/Program.cs index f868c76..5eb05ed 100644 --- a/examples/WebApp/Program.cs +++ b/examples/WebApp/Program.cs @@ -12,7 +12,7 @@ public static void Main(string[] args) public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) - .UseUrls("http://localhost:5002") + .UseUrls("http://localhost:5001") .UseStartup() .Build(); } diff --git a/examples/WebApp/Startup.cs b/examples/WebApp/Startup.cs index f474c03..a4390c2 100644 --- a/examples/WebApp/Startup.cs +++ b/examples/WebApp/Startup.cs @@ -1,12 +1,13 @@ using System; -using Microsoft.AspNet.OData.Builder; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Mocoding.AspNetCore.ODataApi; using Mocoding.AspNetCore.ODataApi.EasyDocDb; -using Newtonsoft.Json.Serialization; +using Mocoding.EasyDocDb; +using Mocoding.EasyDocDb.FileSystem; +using Mocoding.EasyDocDb.Json; namespace WebApp { @@ -23,30 +24,21 @@ public Startup(IConfiguration configuration) // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { - services.AddMvc(); services + .AddSingleton() + .AddSingleton() + .AddEasyDocDbGenericRepository(options => options.Connection = "../data") .AddMvcCore() - .AddJsonFormatters(settings => settings.ContractResolver = new CamelCasePropertyNamesContractResolver()) - .AddApiExplorer() .AddODataApi() - .AddEasyDocDb() .AddResource() .AddResource("Roles") // custom Entity Name / Url .AddResource("settings") // override controller test 1 .AddResource(); // override controller test 2 - - services.AddSwaggerSpecification(); } - public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider services) + public void Configure(IApplicationBuilder app) { - app.Map("/api", apiApp => - { - apiApp.UseSwaggerUIAndSpec(); - apiApp.UseMvc(builder => builder.UseOData(apiApp)); - }); - - app.UseStaticFiles(); + app.UseODataApi(); } } } \ No newline at end of file diff --git a/examples/WebApp/Swagger.cs b/examples/WebApp/Swagger.cs deleted file mode 100644 index bcf712f..0000000 --- a/examples/WebApp/Swagger.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; -using Swashbuckle.AspNetCore.Swagger; - -namespace WebApp -{ - public static class Swagger - { - public static IServiceCollection AddSwaggerSpecification(this IServiceCollection services) - { - return services - .AddSwaggerGen(options => - { - options.SwaggerDoc("spec", new Info { Title = "Example Api", Version = "v1" }); - options.DescribeAllEnumsAsStrings(); - }); - } - - public static IApplicationBuilder UseSwaggerUIAndSpec(this IApplicationBuilder app) - { - return app - .UseSwagger(options => options.RouteTemplate = "${documentName}") - .UseSwaggerUI(options => - { - options.RoutePrefix = "_swagger-ui"; - options.SwaggerEndpoint("/api/$spec", "Example Api"); - }); - } - } -} diff --git a/examples/WebApp/WebApp.csproj b/examples/WebApp/WebApp.csproj index 87f7b02..9da15da 100644 --- a/examples/WebApp/WebApp.csproj +++ b/examples/WebApp/WebApp.csproj @@ -2,16 +2,19 @@ Exe - netcoreapp2.1 + netcoreapp3.1 ..\..\_stylecop\StyleCop.ruleset - - - + + + + - 1.0.2 + 1.1.118 + all + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/icon.png b/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b9a2fc1aec18752c1cce7064f57dfd0cb89f96b6 GIT binary patch literal 6156 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 ed~q=dDgGaWIj+OZyZqMx0000 : ICrudRepository where TEntity : class, new() { - private ICrudRepository _repository; + private readonly ICrudRepository _repository; - public CrudRepositoryProxy(DocumentRepositoryFactory factory, IEntityKeyAccossor keyAccossor) + public CrudRepositoryProxy(DocumentRepositoryFactory factory) { - _repository = factory.Get(keyAccossor); + _repository = factory.Create(); } public Task AddOrUpdate(TEntity entity) diff --git a/src/Mocoding.AspNetCore.ODataApi.EasyDocDb/DocumentCollectionCrudRepository.cs b/src/Mocoding.AspNetCore.ODataApi.EasyDocDb/DocumentCollectionCrudRepository.cs index 7e4e115..0503e71 100644 --- a/src/Mocoding.AspNetCore.ODataApi.EasyDocDb/DocumentCollectionCrudRepository.cs +++ b/src/Mocoding.AspNetCore.ODataApi.EasyDocDb/DocumentCollectionCrudRepository.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using System.Threading.Tasks; using Mocoding.EasyDocDb; @@ -11,11 +10,11 @@ namespace Mocoding.AspNetCore.ODataApi.EasyDocDb public class DocumentCollectionCrudRepository : ICrudRepository where TEntity : class, new() { - private readonly IEntityKeyAccossor _keyAccossor; + private readonly IEntityKeyAccessor _keyAccessor; - public DocumentCollectionCrudRepository(IDocumentCollection collection, IEntityKeyAccossor keyAccossor) + public DocumentCollectionCrudRepository(IDocumentCollection collection, IEntityKeyAccessor keyAccessor) { - _keyAccossor = keyAccossor; + _keyAccessor = keyAccessor; Collection = collection; } @@ -25,7 +24,7 @@ public DocumentCollectionCrudRepository(IDocumentCollection collection, public virtual async Task AddOrUpdate(TEntity entity) { - var id = _keyAccossor.GetKey(entity); + var id = _keyAccessor.GetKey(entity); if (id == null) throw new InvalidOperationException($"object key is missing"); @@ -65,6 +64,6 @@ public async Task DeleteByKey(TKey key) // } // } private IDocument GetById(TKey id) => Collection.Documents.FirstOrDefault(_ => - EqualityComparer.Default.Equals(_keyAccossor.GetKey(_.Data), id)); + EqualityComparer.Default.Equals(_keyAccessor.GetKey(_.Data), id)); } } diff --git a/src/Mocoding.AspNetCore.ODataApi.EasyDocDb/DocumentCrudRepository.cs b/src/Mocoding.AspNetCore.ODataApi.EasyDocDb/DocumentCrudRepository.cs index 9a2969c..3cd1fdb 100644 --- a/src/Mocoding.AspNetCore.ODataApi.EasyDocDb/DocumentCrudRepository.cs +++ b/src/Mocoding.AspNetCore.ODataApi.EasyDocDb/DocumentCrudRepository.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using System.Threading.Tasks; using Mocoding.EasyDocDb; @@ -11,12 +10,12 @@ namespace Mocoding.AspNetCore.ODataApi.EasyDocDb public class DocumentCrudRepository : ICrudRepository where TEntity : class { - private readonly IEntityKeyAccossor _keyAccossor; + private readonly IEntityKeyAccessor _keyAccessor; private readonly object _lock = new object(); - public DocumentCrudRepository(IDocument> collection, IEntityKeyAccossor keyAccossor) + public DocumentCrudRepository(IDocument> collection, IEntityKeyAccessor keyAccessor) { - _keyAccossor = keyAccossor; + _keyAccessor = keyAccessor; Collection = collection; } @@ -58,7 +57,7 @@ public async Task DeleteByKey(TKey key) protected virtual void AddOrUpdateInternal(TEntity entity) { - var key = _keyAccossor.GetKey(entity); + var key = _keyAccessor.GetKey(entity); if (key == null) throw new InvalidOperationException($"object key is missing"); @@ -79,7 +78,7 @@ private Func FindByKeyPredicate(TKey key) { return entity => { - var entityKey = _keyAccossor.GetKey(entity); + var entityKey = _keyAccessor.GetKey(entity); return EqualityComparer.Default.Equals(entityKey, key); }; } diff --git a/src/Mocoding.AspNetCore.ODataApi.EasyDocDb/DocumentRepositoryFactory.cs b/src/Mocoding.AspNetCore.ODataApi.EasyDocDb/DocumentRepositoryFactory.cs index 8bfc5df..99455ec 100644 --- a/src/Mocoding.AspNetCore.ODataApi.EasyDocDb/DocumentRepositoryFactory.cs +++ b/src/Mocoding.AspNetCore.ODataApi.EasyDocDb/DocumentRepositoryFactory.cs @@ -3,35 +3,30 @@ using System.Collections.Generic; using System.IO; using System.Reflection; - +using Microsoft.Extensions.Options; using Mocoding.AspNetCore.ODataApi.EasyDocDb.Helpers; using Mocoding.EasyDocDb; -using Mocoding.EasyDocDb.FileSystem; -using Mocoding.EasyDocDb.Json; namespace Mocoding.AspNetCore.ODataApi.EasyDocDb { public class DocumentRepositoryFactory { + private readonly IEntityKeyAccessor _keyAccessor; private readonly string _conn; private readonly IRepository _repository; + private readonly IDocumentSerializer _serializer; private readonly IDictionary _crudRepositories; - public DocumentRepositoryFactory(string conn) - { - _conn = conn; - _repository = new EmbeddedRepository(new JsonSerializer()); - _crudRepositories = new ConcurrentDictionary(); - } - - public DocumentRepositoryFactory(string conn, IDocumentStorage storage) + public DocumentRepositoryFactory(IEntityKeyAccessor keyAccessor, IOptions options, IRepository repository, IDocumentSerializer serializer) { - _conn = conn; - _repository = new EmbeddedRepository(new JsonSerializer(), storage); + _keyAccessor = keyAccessor; + _conn = options.Value.Connection; + _repository = repository; + _serializer = serializer; _crudRepositories = new ConcurrentDictionary(); } - public ICrudRepository Get(IEntityKeyAccossor keyAccossor) + public ICrudRepository Create() where TEntity : class, new() { var t = typeof(TEntity); @@ -41,8 +36,8 @@ public ICrudRepository Get(IEntityKeyAccossor keyA var name = t.Name.ToLower(); var attribute = t.GetCustomAttribute(typeof(ReadOptimizedAttribute)); var repo = attribute != null - ? new DocumentCrudRepository(_repository.Init>(Path.Combine(_conn, name + ".json")).Result, keyAccossor) - : new DocumentCollectionCrudRepository(_repository.InitCollection(Path.Combine(_conn, name)).Result, keyAccossor) as ICrudRepository; + ? new DocumentCrudRepository(_repository.Init>(Path.Combine(_conn, $"{name}.{_serializer.Type}")).Result, _keyAccessor) + : new DocumentCollectionCrudRepository(_repository.InitCollection(Path.Combine(_conn, name)).Result, _keyAccessor) as ICrudRepository; _crudRepositories.Add(t, repo); diff --git a/src/Mocoding.AspNetCore.ODataApi.EasyDocDb/Extensions.cs b/src/Mocoding.AspNetCore.ODataApi.EasyDocDb/Extensions.cs index fa0f50b..589dfef 100644 --- a/src/Mocoding.AspNetCore.ODataApi.EasyDocDb/Extensions.cs +++ b/src/Mocoding.AspNetCore.ODataApi.EasyDocDb/Extensions.cs @@ -1,20 +1,18 @@ -using Microsoft.Extensions.DependencyInjection.Extensions; +using System; +using Microsoft.Extensions.DependencyInjection; using Mocoding.EasyDocDb; namespace Mocoding.AspNetCore.ODataApi.EasyDocDb { public static class Extensions { - public static IODataApiBuilder AddEasyDocDb(this IODataApiBuilder oDataApiBuilder, string folder = "../data") + public static IServiceCollection AddEasyDocDbGenericRepository(this IServiceCollection services, Action configure) { - oDataApiBuilder.Services.TryAddSingleton(new DocumentRepositoryFactory(folder)); - return oDataApiBuilder; - } - - public static IODataApiBuilder AddEasyDocDb(this IODataApiBuilder oDataApiBuilder, string connection, IDocumentStorage storage) - { - oDataApiBuilder.Services.TryAddSingleton(new DocumentRepositoryFactory(connection, storage)); - return oDataApiBuilder; + return services + .Configure(configure) + .AddSingleton() + .AddSingleton() + .AddSingleton(typeof(ICrudRepository<,>), typeof(CrudRepositoryProxy<,>)); } } } \ No newline at end of file diff --git a/src/Mocoding.AspNetCore.ODataApi.EasyDocDb/Mocoding.AspNetCore.ODataApi.EasyDocDb.csproj b/src/Mocoding.AspNetCore.ODataApi.EasyDocDb/Mocoding.AspNetCore.ODataApi.EasyDocDb.csproj index 8654d8c..7e984ef 100644 --- a/src/Mocoding.AspNetCore.ODataApi.EasyDocDb/Mocoding.AspNetCore.ODataApi.EasyDocDb.csproj +++ b/src/Mocoding.AspNetCore.ODataApi.EasyDocDb/Mocoding.AspNetCore.ODataApi.EasyDocDb.csproj @@ -1,27 +1,13 @@ - + + - EasyDocDb storage provider for generic OData API implementation - netstandard2.0 - netstandard;.net core;odata;generic;easydocdb;storage - Initial Release. - https://mocoding.blob.core.windows.net/resources/odata-api/nugetIcon.png - https://github.com/mocoding-software/odata-api - https://raw.githubusercontent.com/mocoding-software/odata-api/master/LICENSE - git - https://github.com/mocoding-software/odata-api - ..\..\_stylecop\StyleCop.ruleset - Mocoding - - + EasyDocDb storage provider for generic OData API implementation + netstandard;.net core;odata;generic;easydocdb;storage + - - - - all - 1.0.2 - + diff --git a/src/Mocoding.AspNetCore.ODataApi.EasyDocDb/ODataEasyDocDbOptions.cs b/src/Mocoding.AspNetCore.ODataApi.EasyDocDb/ODataEasyDocDbOptions.cs new file mode 100644 index 0000000..ff3a5f5 --- /dev/null +++ b/src/Mocoding.AspNetCore.ODataApi.EasyDocDb/ODataEasyDocDbOptions.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Mocoding.AspNetCore.ODataApi.EasyDocDb +{ + public class ODataEasyDocDbOptions + { + public string Connection { get; set; } + } +} diff --git a/src/Mocoding.AspNetCore.ODataApi.EntityFramework/CrudRepository.cs b/src/Mocoding.AspNetCore.ODataApi.EntityFramework/CrudRepository.cs deleted file mode 100644 index 445baeb..0000000 --- a/src/Mocoding.AspNetCore.ODataApi.EntityFramework/CrudRepository.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Linq; -using System.Linq.Expressions; -using System.Threading.Tasks; -using Mocoding.AspNetCore.ODataApi.DataAccess; - -namespace Mocoding.AspNetCore.ODataApi.EntityFramework -{ - public class CrudRepository : ICrudRepository - where TData : class, IEntity, new() - { - private readonly IMongoDatabase _database; - private readonly string _tableName; - - public CrudRepository(string connectionString, string name = null) - { - var connection = new MongoUrlBuilder(connectionString); - var client = new MongoClient(connectionString); - _database = client.GetDatabase(connection.DatabaseName); - _tableName = string.IsNullOrEmpty(name) ? typeof(TData).Name : name; - Collection.EnsureIndexes(); - } - - protected IMongoCollection Collection => _database.GetCollection(_tableName); - - public virtual IQueryable QueryRecords() => Collection.AsQueryable(); - - public virtual async Task AddOrUpdate(TData entity) - { - if (!entity.Id.HasValue) - entity.Id = Guid.NewGuid(); - - await Collection.ReplaceOneAsync(new BsonDocument("_id", entity.Id), entity, new UpdateOptions() { IsUpsert = true }); - - return entity; - } - - public virtual Task Delete(Guid id) => Collection.DeleteOneAsync(new BsonDocument("_id", id)); - - public virtual Task BatchAddOrUpdate(TData[] entities) - { - var model = entities.Select(_ => - { - if (!_.Id.HasValue) - _.Id = Guid.NewGuid(); - return new ReplaceOneModel(new BsonDocument("_id", _.Id), _) { IsUpsert = true }; - }); - - return Collection.BulkWriteAsync(model); - } - - public virtual Task BatchDelete(Expression> predicate) => Collection.DeleteManyAsync(predicate); - } -} \ No newline at end of file diff --git a/src/Mocoding.AspNetCore.ODataApi.EntityFramework/DbSetRepository.cs b/src/Mocoding.AspNetCore.ODataApi.EntityFramework/DbSetRepository.cs index 1e92907..4dde7c7 100644 --- a/src/Mocoding.AspNetCore.ODataApi.EntityFramework/DbSetRepository.cs +++ b/src/Mocoding.AspNetCore.ODataApi.EntityFramework/DbSetRepository.cs @@ -11,13 +11,13 @@ class DbSetRepository : ICrudRepository { private readonly DbSet _repository; private readonly DbContext _context; - private readonly IEntityKeyAccossor _keyAccossor; + private readonly IEntityKeyAccessor _keyAccessor; - public DbSetRepository(DbContext context, IEntityKeyAccossor keyAccossor) + public DbSetRepository(DbContext context, IEntityKeyAccessor keyAccessor) { _repository = context.Set(); _context = context; - _keyAccossor = keyAccossor; + _keyAccessor = keyAccessor; } public IQueryable QueryRecords() @@ -28,7 +28,7 @@ public IQueryable QueryRecords() public async Task AddOrUpdate(TEntity entity) { // var contains = ; - //var id = _keyAccossor.GetKey(entity); + //var id = _keyAccessor.GetKey(entity); //if (id == null) // throw new InvalidOperationException($"object key is missing"); diff --git a/src/Mocoding.AspNetCore.ODataApi.EntityFramework/Extensions.cs b/src/Mocoding.AspNetCore.ODataApi.EntityFramework/Extensions.cs index 7093a15..880d416 100644 --- a/src/Mocoding.AspNetCore.ODataApi.EntityFramework/Extensions.cs +++ b/src/Mocoding.AspNetCore.ODataApi.EntityFramework/Extensions.cs @@ -1,4 +1,6 @@ -using System.Linq; +using System; +using System.Linq; +using Microsoft.AspNetCore.Authentication; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.Extensions.DependencyInjection; @@ -9,24 +11,29 @@ namespace Mocoding.AspNetCore.ODataApi.EntityFramework { public static class Extensions { - public static IODataApiBuilder AddEntityFramework(this IODataApiBuilder builder) where TContext : DbContext + public static IODataApiBuilder AddResources(this IODataApiBuilder builder) where TContext : DbContext { - var serviceProvider = builder.Services.BuildServiceProvider(); - var context = serviceProvider.GetRequiredService(); var properties = typeof(TContext).GetProperties(); - var types = context.Model.GetEntityTypes(); - foreach (var entityType in types) + var dbSetType = typeof(DbSet<>); + foreach (var property in properties.Where(_=> _.PropertyType.IsGenericType && _.PropertyType.GetGenericTypeDefinition() == dbSetType)) { - var dbSetType = typeof(DbSet<>).MakeGenericType(entityType.ClrType); - var property = properties.FirstOrDefault(_ => _.PropertyType.IsAssignableFrom(dbSetType)); - builder.AddResource(entityType.ClrType, property != null ? property.Name.ToLower() : null); + var type = property.PropertyType.GenericTypeArguments[0]; + builder.AddResource(type, property.Name.ToLower()); } - - - builder.Services.TryAddTransient(); - builder.Services.TryAddScoped(typeof(ICrudRepository<,>), typeof(DbSetRepository<,>)); - return builder; - + + + return builder; } + + public static IServiceCollection AddEntityFrameworkGenericRepository(this IServiceCollection services, Action configure) + where TContext : DbContext + { + services + .AddDbContext(configure) + .TryAddScoped(typeof(ICrudRepository<,>), typeof(DbSetRepository<,>)); + return services; + } + + } } diff --git a/src/Mocoding.AspNetCore.ODataApi.EntityFramework/Mocoding.AspNetCore.ODataApi.EntityFramework.csproj b/src/Mocoding.AspNetCore.ODataApi.EntityFramework/Mocoding.AspNetCore.ODataApi.EntityFramework.csproj index 83c41d7..666b2eb 100644 --- a/src/Mocoding.AspNetCore.ODataApi.EntityFramework/Mocoding.AspNetCore.ODataApi.EntityFramework.csproj +++ b/src/Mocoding.AspNetCore.ODataApi.EntityFramework/Mocoding.AspNetCore.ODataApi.EntityFramework.csproj @@ -1,27 +1,13 @@  + - EntityFramework storage provider for generic OData API implementation - netstandard2.0 - netstandard;.net core;odata;generic;sql;mssql,mssql server,storage; - Initial Release. - https://mocoding.blob.core.windows.net/resources/odata-api/nugetIcon.png - https://github.com/mocoding-software/odata-api - https://raw.githubusercontent.com/mocoding-software/odata-api/master/LICENSE - git - https://github.com/mocoding-software/odata-api - ..\..\_stylecop\StyleCop.ruleset - Mocoding + EntityFramework storage provider for generic OData API implementation + netstandard;.net core;odata;generic;sql;mssql,mssql server,storage; - - - - - - - + diff --git a/src/Mocoding.AspNetCore.ODataApi.MongoDb/CrudRepository.cs b/src/Mocoding.AspNetCore.ODataApi.MongoDb/CrudRepository.cs index 0b23aac..bb038b9 100644 --- a/src/Mocoding.AspNetCore.ODataApi.MongoDb/CrudRepository.cs +++ b/src/Mocoding.AspNetCore.ODataApi.MongoDb/CrudRepository.cs @@ -12,13 +12,13 @@ class CrudRepository : ICrudRepository where TEntity : class, new() { private readonly IMongoDatabase _database; - private readonly IEntityKeyAccossor _keyAccossor; + private readonly IEntityKeyAccessor _keyAccessor; private readonly string _tableName; - public CrudRepository(IMongoDatabase database, IEntityKeyAccossor keyAccossor) + public CrudRepository(IMongoDatabase database, IEntityKeyAccessor keyAccessor) { _database = database; - _keyAccossor = keyAccossor; + _keyAccessor = keyAccessor; _tableName = typeof(TEntity).Name; Collection.EnsureIndexes(); } @@ -29,7 +29,7 @@ public CrudRepository(IMongoDatabase database, IEntityKeyAccossor keyAccossor) public virtual async Task AddOrUpdate(TEntity entity) { - var id = _keyAccossor.GetKey(entity); + var id = _keyAccessor.GetKey(entity); if (id == null) throw new InvalidOperationException($"object id is missing"); await Collection.ReplaceOneAsync(GetBsonDocument(id), entity, new UpdateOptions() {IsUpsert = true}); @@ -52,19 +52,5 @@ private BsonDocument GetBsonDocument(TKey key) var bsonValue = BsonValue.Create(key); return new BsonDocument("_id", bsonValue); } - - //public virtual Task BatchAddOrUpdate(TEntity[] entities) - //{ - // var model = entities.Select(_ => - // { - // if (!_.Id.HasValue) - // _.Id = Guid.NewGuid(); - // return new ReplaceOneModel(new BsonDocument("_id", _.Id), _) { IsUpsert = true }; - // }); - - // return Collection.BulkWriteAsync(model); - //} - - //public virtual Task BatchDelete(Expression> predicate) => Collection.DeleteManyAsync(predicate); } } \ No newline at end of file diff --git a/src/Mocoding.AspNetCore.ODataApi.MongoDb/Extensions.cs b/src/Mocoding.AspNetCore.ODataApi.MongoDb/Extensions.cs index 7277be2..b1f81a4 100644 --- a/src/Mocoding.AspNetCore.ODataApi.MongoDb/Extensions.cs +++ b/src/Mocoding.AspNetCore.ODataApi.MongoDb/Extensions.cs @@ -1,21 +1,23 @@ -using Microsoft.Extensions.DependencyInjection.Extensions; +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using MongoDB.Driver; namespace Mocoding.AspNetCore.ODataApi.MongoDb { public static class Extensions { - public static IODataApiBuilder AddODataApiMongoDb(this IODataApiBuilder oDataApiBuilder, string connection) + public static IServiceCollection AddMongoDbGenericRepository(this IServiceCollection services, string connection) { MongoDefaults.GuidRepresentation = MongoDB.Bson.GuidRepresentation.Standard; - oDataApiBuilder.Services.TryAddSingleton(services => - { - var urlBuilder = new MongoUrlBuilder(connection); - var client = new MongoClient(connection); - return client.GetDatabase(urlBuilder.DatabaseName); - }); - - return oDataApiBuilder; + services.TryAddSingleton(_ => + { + var urlBuilder = new MongoUrlBuilder(connection); + var client = new MongoClient(connection); + return client.GetDatabase(urlBuilder.DatabaseName); + }); + services.TryAddScoped(typeof(ICrudRepository<,>), typeof(CrudRepository<,>)); + return services; } } } diff --git a/src/Mocoding.AspNetCore.ODataApi.MongoDb/IndexesExtensions.cs b/src/Mocoding.AspNetCore.ODataApi.MongoDb/IndexesExtensions.cs index 7550ed6..3e001a7 100644 --- a/src/Mocoding.AspNetCore.ODataApi.MongoDb/IndexesExtensions.cs +++ b/src/Mocoding.AspNetCore.ODataApi.MongoDb/IndexesExtensions.cs @@ -1,11 +1,5 @@ using System; -using System.Collections.Generic; -using System.Globalization; using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc.Internal; using MongoDB.Driver; namespace Mocoding.AspNetCore.ODataApi.MongoDb diff --git a/src/Mocoding.AspNetCore.ODataApi.MongoDb/Mocoding.AspNetCore.ODataApi.MongoDb.csproj b/src/Mocoding.AspNetCore.ODataApi.MongoDb/Mocoding.AspNetCore.ODataApi.MongoDb.csproj index 0522b69..9d6a644 100644 --- a/src/Mocoding.AspNetCore.ODataApi.MongoDb/Mocoding.AspNetCore.ODataApi.MongoDb.csproj +++ b/src/Mocoding.AspNetCore.ODataApi.MongoDb/Mocoding.AspNetCore.ODataApi.MongoDb.csproj @@ -1,23 +1,13 @@ - + + - MongoDb storage provider for generic OData API implementation - netstandard2.0 - netstandard;.net core;odata;generic;mongodb;storage;nosql - Initial Release. - https://mocoding.blob.core.windows.net/resources/odata-api/nugetIcon.png - https://github.com/mocoding-software/odata-api - https://raw.githubusercontent.com/mocoding-software/odata-api/master/LICENSE - git - https://github.com/mocoding-software/odata-api - ..\..\_stylecop\StyleCop.ruleset - Mocoding + MongoDb storage provider for generic OData API implementation + netstandard;.net core;odata;generic;mongodb;storage;nosql + - - - + - diff --git a/src/Mocoding.AspNetCore.ODataApi/Core/CrudControllerFeatureProvider.cs b/src/Mocoding.AspNetCore.ODataApi/Core/CrudControllerFeatureProvider.cs index afa1679..01786eb 100644 --- a/src/Mocoding.AspNetCore.ODataApi/Core/CrudControllerFeatureProvider.cs +++ b/src/Mocoding.AspNetCore.ODataApi/Core/CrudControllerFeatureProvider.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Reflection; +using Microsoft.AspNet.OData; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.OData.Edm; namespace Mocoding.AspNetCore.ODataApi.Core { @@ -22,13 +25,14 @@ public CrudControllerFeatureProvider(IModelMetadataProvider metadataProvider) public void PopulateFeature(IEnumerable parts, ControllerFeature feature) { - var model = _metadataProvider.GetModelMetadata(); + var model = _metadataProvider.GetEdmModel(); + var entities = model.GetEntityKeyMapping(); // There's no 'real' controller for this entity, so add the generic version. - foreach (var entityType in model) + foreach (var entity in entities) { var controllerType = typeof(CrudController<,>) - .MakeGenericType(entityType.EntityType, entityType.EntityKey.PropertyType).GetTypeInfo(); + .MakeGenericType(entity.Key, entity.Value.PropertyType).GetTypeInfo(); feature.Controllers.Add(controllerType); } } diff --git a/src/Mocoding.AspNetCore.ODataApi/Core/CrudControllerNameConvention.cs b/src/Mocoding.AspNetCore.ODataApi/Core/CrudControllerNameConvention.cs index 15b408b..c28948a 100644 --- a/src/Mocoding.AspNetCore.ODataApi/Core/CrudControllerNameConvention.cs +++ b/src/Mocoding.AspNetCore.ODataApi/Core/CrudControllerNameConvention.cs @@ -1,5 +1,6 @@ using System.Linq; using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.OData.Edm; namespace Mocoding.AspNetCore.ODataApi.Core { @@ -26,10 +27,10 @@ public void Apply(ControllerModel controller) return; var entityType = controllerType.GenericTypeArguments[0]; - var metadata = _metadataProvider.GetModelMetadata(); - var entityMetadata = metadata.FirstOrDefault(_ => _.EntityType == entityType); - if (entityMetadata != null) - controller.ControllerName = entityMetadata.Route; + var model = _metadataProvider.GetEdmModel(); + var entitySet = model.EntityContainer.EntitySets().FirstOrDefault(_ => _.Type.AsElementType().FullTypeName() == entityType.FullName); + if (entitySet != null) + controller.ControllerName = entitySet.Name; } } } diff --git a/src/Mocoding.AspNetCore.ODataApi/Core/DefaultEntityKeyAccessor.cs b/src/Mocoding.AspNetCore.ODataApi/Core/DefaultEntityKeyAccessor.cs index 7788313..645eeb2 100644 --- a/src/Mocoding.AspNetCore.ODataApi/Core/DefaultEntityKeyAccessor.cs +++ b/src/Mocoding.AspNetCore.ODataApi/Core/DefaultEntityKeyAccessor.cs @@ -1,24 +1,25 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; +using System.Reflection; +using Mocoding.AspNetCore.ODataApi.Core; -namespace Mocoding.AspNetCore.ODataApi.Core +namespace Mocoding.AspNetCore.ODataApi.DataAccess { - internal class DefaultEntityKeyAccessor : IEntityKeyAccossor + internal class DefaultEntityKeyAccessor : IEntityKeyAccessor { - private readonly IEnumerable _metadata; + private readonly IDictionary _mapping; public DefaultEntityKeyAccessor(IModelMetadataProvider metadataProvider) { - _metadata = metadataProvider.GetModelMetadata(); + _mapping = metadataProvider.GetEdmModel().GetEntityKeyMapping().ToDictionary(_ => _.Key, _ => _.Value); } public TKey GetKey(TEntity entity) { var entityType = typeof(TEntity); - var entityMetadata = _metadata.First(_ => _.EntityType == entityType); - return (TKey)entityMetadata.EntityKey.GetValue(entity); + var keyProperty = _mapping[entityType]; + return (TKey)keyProperty.GetValue(entity); } } } diff --git a/src/Mocoding.AspNetCore.ODataApi/Core/EntityMetadata.cs b/src/Mocoding.AspNetCore.ODataApi/Core/EntityMetadata.cs deleted file mode 100644 index d804267..0000000 --- a/src/Mocoding.AspNetCore.ODataApi/Core/EntityMetadata.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Reflection; -using System.Text; -using Microsoft.OData.Edm; - -namespace Mocoding.AspNetCore.ODataApi.Core -{ - internal class EntityMetadata - { - public EntityMetadata(Type entityType, string route) - { - EntityType = entityType; - Route = route; - } - - public Type EntityType { get; } - public PropertyInfo EntityKey { get; private set; } - public string Route { get; } - - public void SetKey(IEdmEntityType edmType) - { - var key = edmType.DeclaredKey.First(); - EntityKey = EntityType.GetProperty(key.Name) ?? EntityType.GetProperty(CultureInfo.CurrentCulture.TextInfo.ToTitleCase(key.Name)); - } - } -} diff --git a/src/Mocoding.AspNetCore.ODataApi/Core/IModelMetadataProvider.cs b/src/Mocoding.AspNetCore.ODataApi/Core/IModelMetadataProvider.cs index e38d8dc..e9e9b77 100644 --- a/src/Mocoding.AspNetCore.ODataApi/Core/IModelMetadataProvider.cs +++ b/src/Mocoding.AspNetCore.ODataApi/Core/IModelMetadataProvider.cs @@ -1,7 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Text; +using System.Collections.Generic; using Microsoft.OData.Edm; +using Mocoding.AspNetCore.ODataApi.DataAccess; namespace Mocoding.AspNetCore.ODataApi.Core { @@ -9,6 +8,6 @@ internal interface IModelMetadataProvider { IEdmModel GetEdmModel(); - IEnumerable GetModelMetadata(); + // IEnumerable GetModelMetadata(); } } diff --git a/src/Mocoding.AspNetCore.ODataApi/Core/ODataApiBuilder.cs b/src/Mocoding.AspNetCore.ODataApi/Core/ODataApiBuilder.cs index dad1505..a4c1cc9 100644 --- a/src/Mocoding.AspNetCore.ODataApi/Core/ODataApiBuilder.cs +++ b/src/Mocoding.AspNetCore.ODataApi/Core/ODataApiBuilder.cs @@ -4,34 +4,25 @@ using Microsoft.AspNet.OData.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.OData.Edm; +using Mocoding.AspNetCore.ODataApi.DataAccess; namespace Mocoding.AspNetCore.ODataApi.Core { internal class ODataApiBuilder : IODataApiBuilder, IModelMetadataProvider { private readonly ODataConventionModelBuilder _modelBuilder; - private readonly IList _metadata; private IEdmModel _model; - public ODataApiBuilder(IServiceCollection services, bool enableLowerCamelCase) + public ODataApiBuilder(bool enableLowerCamelCase) { - Services = services; _modelBuilder = new ODataConventionModelBuilder(); if (enableLowerCamelCase) _modelBuilder.EnableLowerCamelCase(); - _metadata = new List(); } - public IServiceCollection Services { get; } - public IEdmModel GetEdmModel() { - return _model ?? (_model = GetEdmModelInternal()); - } - - public IEnumerable GetModelMetadata() - { - return _metadata; + return _model ??= _modelBuilder.GetEdmModel(); } public IODataApiBuilder AddResource(string customRoute = null) @@ -44,23 +35,9 @@ public IODataApiBuilder AddResource(string customRoute = null) public IODataApiBuilder AddResource(Type type, string customRoute = null) { var route = customRoute ?? type.Name.ToLower(); - _metadata.Add(new EntityMetadata(type, route)); var entityType = _modelBuilder.AddEntityType(type); _modelBuilder.AddEntitySet(route, entityType); return this; } - - private IEdmModel GetEdmModelInternal() - { - var model = _modelBuilder.GetEdmModel(); - var edmTypes = model.SchemaElements.Where(_ => _ is IEdmEntityType).Cast(); - foreach (var edmType in edmTypes) - { - var metadataType = _metadata.First(_ => _.EntityType.FullName == edmType.FullTypeName()); - metadataType?.SetKey(edmType); - } - - return model; - } } } \ No newline at end of file diff --git a/src/Mocoding.AspNetCore.ODataApi/CrudController.cs b/src/Mocoding.AspNetCore.ODataApi/CrudController.cs index 4959c9f..2594567 100644 --- a/src/Mocoding.AspNetCore.ODataApi/CrudController.cs +++ b/src/Mocoding.AspNetCore.ODataApi/CrudController.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.OData; @@ -36,7 +35,7 @@ public virtual async Task Get([FromODataUri] TKey key) public virtual async Task> Post([FromBody]TEntity entity) { await Repository.AddOrUpdate(entity); - return base.Created(entity); + return Created(entity); } public virtual async Task> Put(TKey key, [FromBody]Delta patch) @@ -46,7 +45,7 @@ public virtual async Task> Put(TKey key, [FromBody]D throw new KeyNotFoundException(); patch.Put(entity); await Repository.AddOrUpdate(entity); - return base.Updated(entity); + return Updated(entity); } public virtual async Task> Patch(TKey key, [FromBody]Delta patch) @@ -57,7 +56,7 @@ public virtual async Task> Patch(TKey key, [FromBody patch.Patch(entity); await Repository.AddOrUpdate(entity); - return base.Updated(entity); + return Updated(entity); } public virtual async Task Delete(TKey key) diff --git a/src/Mocoding.AspNetCore.ODataApi/EdmExtensions.cs b/src/Mocoding.AspNetCore.ODataApi/EdmExtensions.cs new file mode 100644 index 0000000..95cb398 --- /dev/null +++ b/src/Mocoding.AspNetCore.ODataApi/EdmExtensions.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Text; +using Microsoft.AspNet.OData; +using Microsoft.OData.Edm; + +namespace Mocoding.AspNetCore.ODataApi +{ + public static class EdmExtensions + { + public static IEnumerable> GetEntityKeyMapping(this IEdmModel model) + { + var entities = model.SchemaElements.Where(_ => _ is IEdmEntityType).Cast(); + + // There's no 'real' controller for this entity, so add the generic version. + foreach (var entity in entities) + { + var key = entity.DeclaredKey.First(); + var annotation = model.GetAnnotationValue(entity); + var entityType = annotation.ClrType; + var keyProperty = entityType?.GetProperty(key.Name) ?? entityType?.GetProperty(CultureInfo.CurrentCulture.TextInfo.ToTitleCase(key.Name)); + + yield return new KeyValuePair(entityType, keyProperty); + } + } + } +} diff --git a/src/Mocoding.AspNetCore.ODataApi/ICrudRepository.cs b/src/Mocoding.AspNetCore.ODataApi/ICrudRepository.cs index cb44eb6..3b3abb2 100644 --- a/src/Mocoding.AspNetCore.ODataApi/ICrudRepository.cs +++ b/src/Mocoding.AspNetCore.ODataApi/ICrudRepository.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using System.Linq.Expressions; +using System.Linq; using System.Threading.Tasks; namespace Mocoding.AspNetCore.ODataApi @@ -12,8 +10,5 @@ public interface ICrudRepository Task AddOrUpdate(T entity); Task FindByKey(TKey key); Task DeleteByKey(TKey key); - - // Task BatchAddOrUpdate(T[] entities); - // Task BatchDelete(Expression> predicate); } } diff --git a/src/Mocoding.AspNetCore.ODataApi/IEntityKeyAccessor.cs b/src/Mocoding.AspNetCore.ODataApi/IEntityKeyAccessor.cs new file mode 100644 index 0000000..773dea4 --- /dev/null +++ b/src/Mocoding.AspNetCore.ODataApi/IEntityKeyAccessor.cs @@ -0,0 +1,7 @@ +namespace Mocoding.AspNetCore.ODataApi +{ + public interface IEntityKeyAccessor + { + TKey GetKey(TEntity entity); + } +} diff --git a/src/Mocoding.AspNetCore.ODataApi/IEntityKeyAccossor.cs b/src/Mocoding.AspNetCore.ODataApi/IEntityKeyAccossor.cs deleted file mode 100644 index 45b63d3..0000000 --- a/src/Mocoding.AspNetCore.ODataApi/IEntityKeyAccossor.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Mocoding.AspNetCore.ODataApi -{ - public interface IEntityKeyAccossor - { - TKey GetKey(TEntity entity); - } -} diff --git a/src/Mocoding.AspNetCore.ODataApi/IODataApiBuilder.cs b/src/Mocoding.AspNetCore.ODataApi/IODataApiBuilder.cs index 9b4ffe9..6e39ebb 100644 --- a/src/Mocoding.AspNetCore.ODataApi/IODataApiBuilder.cs +++ b/src/Mocoding.AspNetCore.ODataApi/IODataApiBuilder.cs @@ -1,15 +1,10 @@ using System; -using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; -using Microsoft.OData.Edm; -using Mocoding.AspNetCore.ODataApi.Core; namespace Mocoding.AspNetCore.ODataApi { public interface IODataApiBuilder { - IServiceCollection Services { get; } - IODataApiBuilder AddResource(string customRoute = null) where T : class; IODataApiBuilder AddResource(Type type, string customRoute = null); diff --git a/src/Mocoding.AspNetCore.ODataApi/Mocoding.AspNetCore.ODataApi.csproj b/src/Mocoding.AspNetCore.ODataApi/Mocoding.AspNetCore.ODataApi.csproj index 6a43632..dd8a7f1 100644 --- a/src/Mocoding.AspNetCore.ODataApi/Mocoding.AspNetCore.ODataApi.csproj +++ b/src/Mocoding.AspNetCore.ODataApi/Mocoding.AspNetCore.ODataApi.csproj @@ -1,25 +1,13 @@ - + + Generic OData API v4+ implementation for .NET Standard. - netstandard2.0 - netstandard;.net core;odata;generic;api - Initial Reelase. - https://mocoding.blob.core.windows.net/resources/odata-api/nugetIcon.png - https://github.com/mocoding-software/odata-api - https://raw.githubusercontent.com/mocoding-software/odata-api/master/LICENSE - git - https://github.com/mocoding-software/odata-api - ..\..\_stylecop\StyleCop.ruleset - Mocoding - + netstandard;.net core;odata;generic;api + - - - all - 1.0.2 - + diff --git a/src/Mocoding.AspNetCore.ODataApi/ODataApiExtensions.cs b/src/Mocoding.AspNetCore.ODataApi/ODataApiExtensions.cs index 1daf944..1349f8b 100644 --- a/src/Mocoding.AspNetCore.ODataApi/ODataApiExtensions.cs +++ b/src/Mocoding.AspNetCore.ODataApi/ODataApiExtensions.cs @@ -1,44 +1,66 @@ -using System.Linq; +using System; using Microsoft.AspNet.OData.Extensions; -using Microsoft.AspNet.OData.Formatter; -using Microsoft.AspNet.OData.Query; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Net.Http.Headers; using Mocoding.AspNetCore.ODataApi.Core; +using Mocoding.AspNetCore.ODataApi.DataAccess; namespace Mocoding.AspNetCore.ODataApi { public static class ODataApiExtensions { + /// + /// Adds required services to support OData API. + /// + /// The MVC. + /// if set to true [enable lower camel case]. + /// Builder to manipulate resources. public static IODataApiBuilder AddODataApi(this IMvcCoreBuilder mvc, bool enableLowerCamelCase = true) { var services = mvc.Services; - var modelBuilder = new ODataApiBuilder(services, enableLowerCamelCase); + var modelBuilder = new ODataApiBuilder(enableLowerCamelCase); services.AddOData(); services.AddSingleton(modelBuilder); - services.TryAddSingleton(); + services.TryAddSingleton(); mvc.AddMvcOptions(options => options.Conventions.Add(new CrudControllerNameConvention(modelBuilder))) .ConfigureApplicationPartManager(p => p.FeatureProviders.Add(new CrudControllerFeatureProvider(modelBuilder))); return modelBuilder; } - public static IRouteBuilder UseOData(this IRouteBuilder routeBuilder, IApplicationBuilder app, string routePrfix = ODataApiOptions.DefaultRoute) + /// + /// Adds OData API middleware to the pipeline. + /// + /// The to add the middleware to. + /// The route prefix. + /// A reference to this instance after the operation has completed. + public static IApplicationBuilder UseODataApi(this IApplicationBuilder app, string routePrefix = ODataApiOptions.DefaultRoute) { - return routeBuilder.UseOData(app, new ODataApiOptions() { RoutePrfix = routePrfix }); + return app.UseODataApi(new ODataApiOptions() { RoutePrefix = routePrefix }, null); } - public static IRouteBuilder UseOData(this IRouteBuilder routeBuilder, IApplicationBuilder app, ODataApiOptions options) + /// + /// Adds OData API middleware to the pipeline with additional customization options + /// + /// The to add the middleware to. + /// The options. + /// An to configure the provided . + /// A reference to this instance after the operation has completed. + public static IApplicationBuilder UseODataApi(this IApplicationBuilder app, ODataApiOptions options, Action configure) { var apiBuilder = app.ApplicationServices.GetRequiredService(); - routeBuilder.Filter().Select().Expand().Count().OrderBy().MaxTop(null); - routeBuilder.MapODataServiceRoute("OData", options.RoutePrfix, apiBuilder.GetEdmModel()); - - return routeBuilder; + return app + .UseRouting() + .UseEndpoints(endpoints => + { + endpoints.Filter().Select().Expand().Count().OrderBy().MaxTop(null); + endpoints.MapODataRoute("OData", options.RoutePrefix, apiBuilder.GetEdmModel()); + endpoints.MapControllers(); + configure?.Invoke(endpoints); + }); } } } \ No newline at end of file diff --git a/src/Mocoding.AspNetCore.ODataApi/ODataApiOptions.cs b/src/Mocoding.AspNetCore.ODataApi/ODataApiOptions.cs index 741b33a..05978e0 100644 --- a/src/Mocoding.AspNetCore.ODataApi/ODataApiOptions.cs +++ b/src/Mocoding.AspNetCore.ODataApi/ODataApiOptions.cs @@ -1,19 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Microsoft.AspNet.OData.Builder; - -namespace Mocoding.AspNetCore.ODataApi +namespace Mocoding.AspNetCore.ODataApi { + /// + /// ODataApi Options + /// public class ODataApiOptions { public const string DefaultRoute = "odata"; public ODataApiOptions() { - RoutePrfix = DefaultRoute; + RoutePrefix = DefaultRoute; } - public string RoutePrfix { get; set; } + /// + /// Gets or sets the route prefix. + /// + /// + /// The route prefix. + /// + public string RoutePrefix { get; set; } } } diff --git a/src/ProjectBuildProperties.targets b/src/ProjectBuildProperties.targets new file mode 100644 index 0000000..0f23a45 --- /dev/null +++ b/src/ProjectBuildProperties.targets @@ -0,0 +1,31 @@ + + + netcoreapp3.1 + + https://mocoding.blob.core.windows.net/resources/odata-api/nugetIcon.png + https://github.com/mocoding-software/odata-api + https://raw.githubusercontent.com/mocoding-software/odata-api/master/LICENSE + git + https://github.com/mocoding-software/odata-api + ..\..\StyleCop.ruleset + MOCODING LLC,Dennis Miasoutov + Full + true + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/test/Mocoding.AspNetCore.ODataApi.Tests/CrudControllerTests.cs b/test/Mocoding.AspNetCore.ODataApi.Tests/CrudControllerTests.cs new file mode 100644 index 0000000..8a9c631 --- /dev/null +++ b/test/Mocoding.AspNetCore.ODataApi.Tests/CrudControllerTests.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNet.OData; +using Microsoft.AspNetCore.Mvc; +using NSubstitute; +using WebApp; +using Xunit; + +namespace Mocoding.AspNetCore.ODataApi.Tests +{ + public class CrudControllerTests + { + [Fact] + public void GetTest() + { + var repo = Substitute.For>(); + var controller = new CrudController(repo); + + controller.Get(); + + repo.Received().QueryRecords(); + } + + [Fact] + public async void GetByIdTest() + { + var repo = Substitute.For>(); + var controller = new CrudController(repo); + + var user = new User() { Name = "Test"}; + repo.FindByKey(Arg.Is(Guid.Empty)).Returns(user); + + var result = await controller.Get(Guid.Empty) as OkObjectResult; + + Assert.Same(user, result?.Value); + } + + [Fact] + public async void GetByIdNotFoundTest() + { + var repo = Substitute.For>(); + var controller = new CrudController(repo); + + User user = null; + // ReSharper disable once ExpressionIsAlwaysNull + repo.FindByKey(Arg.Is(Guid.Empty)).Returns(user); + + var result = await controller.Get(Guid.Empty) as NotFoundResult; + + Assert.NotNull(result); + } + + [Fact] + public async void PostTest() + { + var repo = Substitute.For>(); + var controller = new CrudController(repo); + + var user = new User(); + + var result = await controller.Post(user); + + Assert.NotNull(result); + await repo.Received().AddOrUpdate(Arg.Is(user)); + } + + [Fact] + public async void PutTest() + { + var repo = Substitute.For>(); + var controller = new CrudController(repo); + + var user = new User(); + repo.FindByKey(Arg.Is(Guid.Empty)).Returns(user); + var result = await controller.Put(Guid.Empty, new Delta(typeof(User))); + + Assert.NotNull(result); + await repo.Received().AddOrUpdate(Arg.Is(user)); + } + + [Fact] + public async void PutNotFoundTest() + { + var repo = Substitute.For>(); + var controller = new CrudController(repo); + + User user = null; + // ReSharper disable once ExpressionIsAlwaysNull + repo.FindByKey(Arg.Is(Guid.Empty)).Returns(user); + await Assert.ThrowsAsync(async () => + await controller.Put(Guid.Empty, new Delta(typeof(User)))); + } + + [Fact] + public async void PatchTest() + { + var repo = Substitute.For>(); + var controller = new CrudController(repo); + + var user = new User(); + repo.FindByKey(Arg.Is(Guid.Empty)).Returns(user); + var result = await controller.Patch(Guid.Empty, new Delta(typeof(User))); + + Assert.NotNull(result); + await repo.Received().AddOrUpdate(Arg.Is(user)); + } + + [Fact] + public async void PatchNotFoundTest() + { + var repo = Substitute.For>(); + var controller = new CrudController(repo); + + User user = null; + // ReSharper disable once ExpressionIsAlwaysNull + repo.FindByKey(Arg.Is(Guid.Empty)).Returns(user); + await Assert.ThrowsAsync(async () => + await controller.Patch(Guid.Empty, new Delta(typeof(User)))); + } + + [Fact] + public async void DeleteTest() + { + var repo = Substitute.For>(); + var controller = new CrudController(repo); + + + await controller.Delete(Guid.Empty); + + await repo.Received().DeleteByKey(Guid.Empty); + } + } +} diff --git a/test/Mocoding.AspNetCore.ODataApi.Tests/Factories/EasyDocDbWebAppFactory.cs b/test/Mocoding.AspNetCore.ODataApi.Tests/Factories/EasyDocDbWebAppFactory.cs new file mode 100644 index 0000000..dd0e662 --- /dev/null +++ b/test/Mocoding.AspNetCore.ODataApi.Tests/Factories/EasyDocDbWebAppFactory.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; + +namespace Mocoding.AspNetCore.ODataApi.Tests.Factories +{ + public class EasyDocDbWebAppFactory : WebApplicationFactory + { + protected override IWebHostBuilder CreateWebHostBuilder() + { + return WebHost.CreateDefaultBuilder(); + } + + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + builder.UseStartup(); + } + } +} diff --git a/test/Mocoding.AspNetCore.ODataApi.Tests/Factories/EntityFrameworkWebAppFactory.cs b/test/Mocoding.AspNetCore.ODataApi.Tests/Factories/EntityFrameworkWebAppFactory.cs new file mode 100644 index 0000000..4839929 --- /dev/null +++ b/test/Mocoding.AspNetCore.ODataApi.Tests/Factories/EntityFrameworkWebAppFactory.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; + +namespace Mocoding.AspNetCore.ODataApi.Tests.Factories +{ + public class EntityFrameworkWebAppFactory : WebApplicationFactory + { + protected override IWebHostBuilder CreateWebHostBuilder() + { + return WebHost.CreateDefaultBuilder(); + } + + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + builder.UseStartup(); + } + } +} diff --git a/test/Mocoding.AspNetCore.ODataApi.Tests/Factories/Factory.cs b/test/Mocoding.AspNetCore.ODataApi.Tests/Factories/Factory.cs new file mode 100644 index 0000000..56be27e --- /dev/null +++ b/test/Mocoding.AspNetCore.ODataApi.Tests/Factories/Factory.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; + +namespace Mocoding.AspNetCore.ODataApi.Tests.Factories +{ + public class Factory : WebApplicationFactory + { + protected override IWebHostBuilder CreateWebHostBuilder() + { + return WebHost.CreateDefaultBuilder(); + } + + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + builder.UseContentRoot("."); + builder.UseStartup(); + } + } +} diff --git a/test/Mocoding.AspNetCore.ODataApi.Tests/Factories/Startup.cs b/test/Mocoding.AspNetCore.ODataApi.Tests/Factories/Startup.cs new file mode 100644 index 0000000..d0d1fa5 --- /dev/null +++ b/test/Mocoding.AspNetCore.ODataApi.Tests/Factories/Startup.cs @@ -0,0 +1,22 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using WebApp; + +namespace Mocoding.AspNetCore.ODataApi.Tests.Factories +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services + .AddMvcCore() + .AddODataApi() + .AddResource(); + } + + public void Configure(IApplicationBuilder app) + { + app.UseODataApi(); + } + } +} diff --git a/test/Mocoding.AspNetCore.ODataApi.Tests/Integration/EasyDocDbExtensionsTests.cs b/test/Mocoding.AspNetCore.ODataApi.Tests/Integration/EasyDocDbExtensionsTests.cs new file mode 100644 index 0000000..28de33a --- /dev/null +++ b/test/Mocoding.AspNetCore.ODataApi.Tests/Integration/EasyDocDbExtensionsTests.cs @@ -0,0 +1,26 @@ +using System.Net.Http; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.Client; +using Mocoding.AspNetCore.ODataApi.EasyDocDb; +using Mocoding.AspNetCore.ODataApi.Tests.Factories; +using Xunit; + +namespace Mocoding.AspNetCore.ODataApi.Tests.Integration +{ + public class EasyDocDbExtensionsTests : IClassFixture + { + private readonly EasyDocDbWebAppFactory _factory; + + public EasyDocDbExtensionsTests(EasyDocDbWebAppFactory factory) + { + _factory = factory; + } + + [Fact] + public void EasyDocDbServicesTest() + { + Assert.NotNull(_factory.Services.GetRequiredService()); + } + } +} diff --git a/test/Mocoding.AspNetCore.ODataApi.Tests/Integration/ODataApiExtensionsTests.cs b/test/Mocoding.AspNetCore.ODataApi.Tests/Integration/ODataApiExtensionsTests.cs new file mode 100644 index 0000000..b0f1663 --- /dev/null +++ b/test/Mocoding.AspNetCore.ODataApi.Tests/Integration/ODataApiExtensionsTests.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.Extensions.DependencyInjection; +using Mocoding.AspNetCore.ODataApi.Tests.Factories; +using Xunit; + +namespace Mocoding.AspNetCore.ODataApi.Tests.Integration +{ + public class ODataApiExtensionsTests : IClassFixture + { + private readonly Factory _factory; + + public ODataApiExtensionsTests(Factory factory) + { + _factory = factory; + } + + [Fact] + public void ServicesTest() + { + Assert.NotNull(_factory.Services.GetRequiredService()); + } + } +} diff --git a/test/Mocoding.AspNetCore.ODataApi.Tests/Mocoding.AspNetCore.ODataApi.Tests.csproj b/test/Mocoding.AspNetCore.ODataApi.Tests/Mocoding.AspNetCore.ODataApi.Tests.csproj new file mode 100644 index 0000000..47fc8c5 --- /dev/null +++ b/test/Mocoding.AspNetCore.ODataApi.Tests/Mocoding.AspNetCore.ODataApi.Tests.csproj @@ -0,0 +1,40 @@ + + + + MOCODING LLC, Dennis Miasoutov + netcoreapp3.1 + ..\..\StyleCop.ruleset + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + +